파일 채널,비동기 파일 채널

Back-End/Java 2019. 4. 24. 01:05
728x90
반응형

- 파일 채널 -

 

파일 채널을 이용하면 파일 읽기와 쓰기를 할 수 있다.

FileChannel은 동기화 처리가 되어 있기 때문에 멀티 스레드 환경에서 사용해도 안전하다.

FileChannel은 정적 메소드인 open()을 호출해서 얻을수도 있지만, IO의 FileInputStream,

FileOutputStream의 getChannel() 메소드를 호출해서 얻을 수도 있다.

다음은 open() 메소드로 FileChannel을 얻는 방법을 보여준다.

 

 

  FileChannel fileChannel = FileChannel.open(Path path, OpenOption...options);

 

 

첫 번째 path 매개값은 열거나, 생성하고자 하는 파일의 경로를 Path 객체로 생성해서 지정하고,

 

두 번째 options 매개값은 열기 옵션 값인데 StandardOpenOption의 열거 상수를 나열해주면 된다.

 

 열거 상수

설명 

 READ

 읽기용으로 파일을 연다.

 WRITE

쓰기용으로 파일을 연다. 

 CREATE

파일이 없다면 새 파일을 생성한다. 

 CREATE_NEW

새 파일을 만든다. 이미 파일이 있으면 예외와 함께 실패한다. 

 APPEND

파일 끝에 데이터를 추가한다(WRITE 나 CREATE와 함께 사용됨) 

 DELETE_ON_CLOSE

채널을 닫을 때 파일을 삭제한다(임시 파일을 삭제할 때 사용) 

 TRUNCATE_EXISTING

파일을 0바이트로 잘라낸다(WRITE 옵션과 함께 사용됨) 

 

FileChannel을 더이상 이용하지 않을 경우에는 close() 메소드를 호출해서 닫아주어야 한다.

 

 

  fileChannel.close();

 

 

 

 

- 파일 쓰기와 읽기 -

 

파일에 바이트를 쓰려면 FileChannel의 write() 메소드를 호출하면 된다.

매개값으로 ByteBuffer 객체를 주면 되는데, 파일에 쓰여지는 바이트는 ByteBuffer의 position부터 limit까지이다.

position이 0이고 limit가 capacity와 동일하다면 ByteBuffer의 모든 바이트가 파일에 쓰여진다.

write() 메소드의 리턴값은 ByteBuffer에서 파일로 쓰여진 바이트 수이다.

 

 

 

-예제 및 출력 결과-

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.hs.chap19;
 
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
 
public class FileChannelWriteExample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("C:/Temp/file.txt"); // C:/Temp/file.txt경로를 객체로 얻는다.
        Files.createDirectories(path.getParent()); // path 경로에 있는 부모 디렉터리를 생성 한다.
 
        FileChannel fileChannel = FileChannel.open( // open()메소드를 사용해 파일의 경로를 지정하고, 파일이 없다면 새 파일을 생성하고 쓰기용으로 파일을 연다.
                path, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
 
        String data = "안녕하세요";
        Charset charset = Charset.defaultCharset();
        ByteBuffer byteBuffer = charset.encode(data); // encode()메소드를 사용해 data를 byteBuffer로 변환한다.
 
        int byteCount = fileChannel.write(byteBuffer); // write() 메소드로 byteBuffer를 쓰고, 그 바이트의 수를 count에 저장
        System.out.println("file.txt : " + byteCount + "bytes written");
        fileChannel.close();
    }
}
 
cs

 

 

 

 

- 예제 및 출력 결과 -

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.hs.chap19;
 
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
 
public class FileChannelReadExample {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("C:/Temp/file.txt"); // get()메소드로 파일이 저장될 경로를 얻는다.
 
        FileChannel fileChannel = FileChannel.open( // open()메소드를 사용해 path경로를 열고, 읽기용으로 파일을 연다.
                path, StandardOpenOption.READ);
 
        ByteBuffer byteBuffer = ByteBuffer.allocate(100); // allocate() 메소드를 이용, 100개의 바이트를 저장할수 있는 넌다이렉트 버퍼를 생성한다.
 
        Charset charset = Charset.defaultCharset();
        String data = "";
        int byteCount;
 
        while (true) {
            byteCount = fileChannel.read(byteBuffer); // read()메소드를 사용해 byteBuffer 파일로부터 데이터를 읽음
            if (byteCount == -1// 더이상 읽을 바이트가 없으면.
                break;
            byteBuffer.flip(); // position의 위치를 0번인덱스로 설정, (0이어야 ByteBuffer의 첫 바이트 부터 저장되기 대문)
            data += charset.decode(byteBuffer).toString(); // byteBuffer을 String로 변환해 하나씩 data에 담는다.
            byteBuffer.clear(); // 모든 위치 속성을 초기화
        }
 
        fileChannel.close();
        System.out.println("file.txt : " + data);
    }
 
}
 
cs

 




- 콜백 메소드 -


콜백이란 애플리케이션이 스레드에게 작업 처리를 요청한 후, 스레드가 작업을 완료하면 특정 메소드를 자동 실행하는 기법을 말한다.

이때 자동 실행되는 메소드를 콜백 메소드라고 한다.


블로킹 방식은 작업 처리를 요청한 후 작업이 완료될 때까지 블로킹되지만, 콜백 방식은 작업 처리를 요청한 후 결과를 기다릴 필요없이 다른 기능을 수행할 수 있다.

그 이유는 작업 처리가 완료되면 자동적으로 콜백 메소드가 실행되어 결과를 알 수 있기 때문이다.


콜백 메소드를 만들때는 직접 정의하거나 java.nio.channels.CompletionHandler를 이용해도 좋다.

CompletionHandler를 사용하면 콜백 객체를 만들수 있다.

다음은 CompletionHandler 객체를 생성하는 코드이다.


 

  CompletionHandler<V,A> callback = new CompletionHander<V,A>( ) {   //CompletionHander의 V 타입 파라미터는 결과값의 타입이고 

  @Override                                                                                     // A는 첨부값의 타입이다. 

  public void completed(V result, A attachment){                                   // 첨부값은 콜백 메소드에 결과값 이외에 추가적으로 전달하는 객체이다.

  }                                                                                                  // 첨부값이 필요 없다면 A는 Void로 지정해주면 된다.

  @Override

  public void failed(Throwable exc, A attachment){

  }

  }; 



- 콜백 메소드 종류 -


completed( ) : 작업을 정상 처리 완료했을 때 호출되는 콜백 메소드


failed( ) : 작업 처리 도중 예외가 발생했을 때 호출되는 콜백 메소드




- 파일 비동기 채널 -


FileChannel의 read( )와 write( ) 메소드는 파일 입출력 작업 동안 블로킹 된다.

만약 UI 및 이벤트를 처리하는 스레드에서 이 메소드들을 호출하면 블로킹되는 동안에 UI 갱신이나 이벤트 처리를 할 수 없다.

따라서 별도의 작업 스레드를 생성해서 이 메소드들을 호출해야 하는데, 파일의 수가 많으면 스레드의 수도 많아지기 때문에 문제가 될 수 있다.

그렇기 때문에 불특정 다수의 파일 및 대용량 파일의 입출력 작업을 위해 비동기 파일 채널을 제공하고 있다.


 

  - 파일 비동기 채널 파일 입출력 순서 -


  1. 파일의 데이터 입출력을 위해 read( )와  write( ) 메소드를 호출


  2. 스레드풀에게 작업 처리를 요청하고 이 메소드들을 즉시 리턴시킨다.


  3. 비동기채널에서 스레드풀로 작업처리 요청을 한다.


  4. 스레드풀의 작업큐에서 비동기채널로 부터 받은 작업을 실행대기 시킨다.


  5. 각 스레드는 스레드풀의 큐에서 작업을 가져와 입출력을 시킨다.


  6. 작업 스레드가 파일 입출력을 완료하게 되면 콜백 메소드가 자동 호출된다.




 




- AsynchronousFileChannel 생성과 닫기 -

AsynchronousFileChannel는 두가지 정적 메소드인 open( )을 호출해서 얻을 수 있다.
첫번째 open( ) 메소드는 파일의 Path 객체와 열기 옵션 값을 매개값으로 받는다.


  AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open (

     Path file,

     OpenOption...options

  )';



생성된 AsynchronousFileChannel는 내부적으로 생성되는 기본 스레드풀을 이용해서 스레드를 관리한다.

기본 스레드풀의 최대 스레드 개수는 개발자가 지정할 수 없기 때문에 두번째 open( ) 메소드로 AsynchronousFileChannel 를 만들수도 있다.


 

  AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(

        Path file,

        Set<? extends OpenOption> options,

        ExecutorService executor,

        FileAttribute<?>...attrs

  ); 





- 파일 읽기와 쓰기 -

AsynchronousFileChannel이 생성되었다면 read( ), write( ) 메소드를 이용해서 입출력할 수 있다.


  read(ByteBuffer dst, long position, A attachment, CompletionHandler<Integer, A> handler);


  write(ByteBuffer src, long position, A attachment, CompletionHandler<Integer, A> handler);



이 메소드들을 호출하면 즉시 리턴되고, 스레드풀의 스레드가 입출력 작업을 진행한다.


dst와 src 매개값 : 읽거나 쓰기 위한 ByteBuffer

position 매개값 : 파일에서 읽을 위치이거나 쓸 위치이다. 

                       파일의 첫 번째 바이트부터 읽거나 첫 번째 위치에 바이트를 쓰고 싶다면 position을 0으로 주면 된다.

attachment 매개값 : 콜백 메소드로 전달할 첨부 객체.

첨부 객체 : 콜백 메소드에서 결과값 이외에 추가적인 정보를 얻고자 할 때 사용하는 객체이고 필요없다면 null을 대입해도 된다.
handler 매개값 : CompletionHandler<Integer, A> 구현 객체를 지정한다.
Integer : 입출력 작업의 결과 타입으로, read( ) 와 write( ) 가 읽거나 쓴 바이트 수이다.

A는 첨부 객체 타입으로 개발자가 CompletionHandler 구현 객체를 작성할 때 임의로 지정이 가능하다. 만약 첨부 객체가 필요 없다면 A는 Void가 된다.

CompletionHandler<Integer, A> 구현 객체는 비동기 작업이 정상적으로 완료된 경우와 예외 발생으로 실패된 경우에 자동 콜백되는 다음 두 가지
메소드를 가지고 있어야 한다.

리턴타입 

 메소드명 (매개 변수)

 설명

void

 completed(Integer result, A attachment)

 작업이 정상적으로 완료된 경우 콜백

void

 failed(Throwable exc, A attachment)

 예외 때문에 작업이 실패된 경우 콜백




- 예제 및 출력 결과 (파일 생성 및 쓰기) -



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package com.hs.chap19;
 
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.EnumSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class AsynchronousFileChannelWriteExample {
    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool( // cpu코어의 수 만큼 스레드풀을 생성한다.
                Runtime.getRuntime().availableProcessors() // cpu코어의 수를 리턴
        );
 
        for (int i = 0; i < 10; i++) {
            Path path = Paths.get("C:/Temp/file" + i + ".txt");
            Files.createDirectories(path.getParent()); // 해당 디렉터리의 경로에 디렉터리(폴더)를 생성한다.
 
            // 비동기 파일 채널 생성
            AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open( // 정적 메소드인 open()을 비동기채널을 만든다.
                    path, // 작업해야될 파일의 경로
                    EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE), // 열기옵션(생성하고 쓸수있게)들을 담아서 리턴한다.
                    executorService // 위에서 만든 스레드풀
            );
 
            Charset charset = Charset.defaultCharset();
            ByteBuffer byteBuffer = charset.encode("안녕하세요."); // encode()메소드를 사용해 문자열을 인코딩해 Buffer에 넣는다.
 
            // 첨부 객체 생성 (콜백 메소드에서 결과값 이외에 추가적으로 보여주고싶은 값)
            class Attachment {
                Path path; // 파일의 경로를 저장
                AsynchronousFileChannel fileChannel; // 비동기 파일 채널을 저장
            }
            Attachment attachment = new Attachment();
            attachment.path = path;
            attachment.fileChannel = fileChannel;
 
            CompletionHandler<Integer, Attachment> completionHander = new CompletionHandler<Integer, Attachment>() { // 콜백
                                                                                                                        // 메소드를
 
                @Override
                public void completed(Integer result, Attachment attachment) { // 작업을 정상 처리 완료했을때 호출되는 콜백 메소드.
                    System.out.println(attachment.path.getFileName() + ":" + result + "bytes written :"
                            + Thread.currentThread().getName());
                    // 첨부객체의 파일의 이름을 출력하고, 결과값, 현재 실행중인 스레드의 이름을 출력한다.
                    try {
                        attachment.fileChannel.close();
                    } catch (IOException e) {
                    }
                }
 
                @Override
                public void failed(Throwable exc, Attachment attachment) {
                    exc.printStackTrace();
                    try {
                        attachment.fileChannel.close();
                    } catch (IOException e) {
                    }
 
                }
 
            };
 
            // 파일 읽기
            fileChannel.write(byteBuffer, 0, attachment, completionHander);
            // (읽은데이터를 저장할 버퍼 객체, 버퍼인덱스, 첨부 객체, 콜백 메소드를 가진 객체)
 
            // 스레드풀 종료
            Thread.sleep(1000);
            executorService.isShutdown();
        }
    }
}
 
cs





- 예제 및 출력 결과 (파일 읽기) -



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
ppackage com.hs.chap19;
 
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.EnumSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class AsynchronousFileChannelWriteExample {
    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool( // cpu코어의 수 만큼 스레드풀을 생성한다.
                Runtime.getRuntime().availableProcessors() // cpu코어의 수를 리턴
        );
 
        for (int i = 0; i < 10; i++) {
            Path path = Paths.get("C:/Temp/file" + i + ".txt"); // 해당 경로에있는 파일을 읽는다.
        
 
            // 비동기 파일 채널 생성
            AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open( // 정적 메소드인 open()을 비동기채널을 만든다.
                    path, // 작업해야될 파일의 경로
                    EnumSet.of(StandardOpenOption.READ), // 열기옵션(파일을 읽을수 있게)을 담아서 리턴한다.
                    executorService // 위에서 만든 스레드풀
            );
            
            //파일의 크기와 동일한 capacity를 갖는 버퍼 생성
            ByteBuffer byteBuffer = ByteBuffer.allocate((int)fileChannel.size());
            
 
            // 첨부 객체 생성 (콜백 메소드에서 결과값 이외에 추가적으로 보여주고싶은 값)
            class Attachment {
                Path path; // 파일의 경로를 저장
                AsynchronousFileChannel fileChannel; // 비동기 파일 채널을 저장
                ByteBuffer byteBuffer;
            }
            Attachment attachment = new Attachment();
            attachment.path = path;
            attachment.fileChannel = fileChannel;
            attachment.byteBuffer = byteBuffer;
            
            //CompletionHandler 객체 생성
            CompletionHandler<Integer, Attachment> completionHanderlernew = new CompletionHandler<Integer, Attachment>() { // 콜백
                                                                                                                        // 메소드를
 
                @Override
                public void completed(Integer result, Attachment attachment) { 
                    attachment.byteBuffer.flip(); // 작업을 정상 처리 완료했을때 호출되는 콜백 메소드.
                    Charset charset = Charset.defaultCharset();
                    String data = charset.decode(attachment.byteBuffer).toString();
                    
                    System.out.println(attachment.path.getFileName() + ":" + data + ":"
                            + Thread.currentThread().getName());
                    // 첨부객체의 파일의 이름을 출력하고, 결과값, 현재 실행중인 스레드의 이름을 출력한다.
                    try {
                        fileChannel.close();
                    } catch (IOException e) {
                    }
                }
 
                @Override
                public void failed(Throwable exc, Attachment attachment) {
                    exc.printStackTrace();
                    try {
                        fileChannel.close();
                    } catch (IOException e) {
                    }
 
                }
 
            };
 
            // 파일 읽기
            fileChannel.read(byteBuffer, 0, attachment, completionHanderlernew);
            // (읽은데이터를 저장할 버퍼 객체, 버퍼인덱스, 첨부 객체, 콜백 메소드를 가진 객체)
 
            // 스레드풀 종료
            executorService.isShutdown();
        }
    }
}
 
cs



728x90
반응형
: