본문 바로가기
Java/study

NIO기반 입출력

by avvin 2019. 4. 24.


NIO(New Input/Output)


IO와 NIO의 차이점


   구분

   IO 

  NIO 

 입출력 방식

 스트림 방식

 채널 방식 

 버퍼 방식

 넌버퍼 

 버퍼 

 비동기 방식 

 지원 안 함 

 지원 

 블로킹 / 넌블로킹 방식 

 블로킹 방식만 지원 

 블로킹 / 넌블로킹 방식 모두 지원 


스트림(Stream) / 채널(Channel)

IO는 스트림 기반. 스트림은 입력 스트림과 출력스트림으로 구분되어 있기 때문에 입,출력 스트림 별도 생성

NIO는 채널 기반. 양방향 입출력 가능해서 데이터 읽고 저장하는 데에 채널 하나만 생성해도 작업 가능  


넌버퍼(nin-buffer) / 버퍼(buffer)

IO는 1바이트 쓰면 스트림이 1바이트 읽는식으로, 느리다. Buffered입출력 보조스트림을 연결해서 쓰기도 한다.

NIO는 기본적으로 버퍼 사용. IO보다 입출력 성능이 좋다. 


블로킹(blocking) / 넌블로킹(non-blocking)

IO블로킹을 빠져나오는 유일한 방법은 스트림을 닫는것

NIO블로킹은 스레드를 인터럽트(interrupt)함으로써 빠져나올 수 있다.

넌블로킹 : 입출력 작업 시 스레드가 블로킹되지 않는 것.

NIO 넌블로킹는 준비가 완료된 채널(지금 바로 읽고 쓸 수 있는 상태)만 선택해서 작업 스레드가 처리.

멀티플렉서(multiplexor)인 셀렉터(Selector : NIO 넌블로킹의 핵심 객체 )는 복수 개의 채널 중에서 준비완료된 채널을 선택하는 방법을 제공해준다.


IO / NIO 사용 기준


IO

연결 클라이언트 수가 적고, 전송되는 데이터가 대용량이면서, 순차적으로 처리될 필요성이 있을 경우


NIO  

과도한 스레드 생성 피하고 스레드를 효과적으로 재사용 / 버퍼의 할당 크기도 문제, 입출력작업에 버퍼를 무조건 사용하므로 복잡

연결 클라이언트 수가 많고 하나의 입출력 작업이 오래 걸리지 않는 경우(작업해야 할 데이터량이 적은 경우) 사용하는 것이 좋다. 

//불특정 다수의 파일 및 대용량 파일의 입출력 작업을 위해서 비동기 파일 채널(AsynchronousFileChannel)을 별도로 제공




파일과 디렉토리


IO는 파일의 속성 정보를 읽기 위해 File 클래스만 제공


NIO는 좀 더 다양한 파일의 속성 정보를 제공해주는 클래스와 인터페이스 제공

java.nio.file

java.nio.file.attribute



1. 경로 정의(Path)


path 인터페이스 구현객체 생성 : java.file.Paths 클래스의 정적 메서드 get() 호출하여 Path 구현 객체 리턴


Path path = Paths.get( 경로 문자열 or URI )


Path 메서드 정리


 리턴타임 메서드(매개변수)

 설명

 int compare(Path other) 

파일 경로가 동일하면 0을 리턴

상위 경로면 음수, 하위 경로면 양수를 리턴,

음수와 양수 값의 차이나는 문자열의 수(?????)<<??

 Path getFileName()

부모 경로 제외하고 파일 또는 디렉토리 이름만 가진 Path리턴 

 FileSystem getFileSystem()

FileSystem 객체 리턴 

 Path getName(int Index)

경로의 각 디렉토리에 순서대로 인덱스 부여하여 Path 객체로 리턴

ex) C:\Temp\dir\file.txt일 경우

index가 0이면 ( getName(0)이면 ) "Temp"의 Path객체 리턴 

 int getNameCount()

중첩 경로 수 리턴

 ex) C:\Temp\dir\file.txt일 경우 3리턴

 Path getParent()

 상위 폴더 Path 객체 리턴

 Path getRo0t()

루트 디렉토리(최상위 디렉토리)의 Path리턴

 Iterator<Path> //Iterator<E>인터페이스

 iterator()

경로에 있는 모든 디렉토리와 파일을 Path 객체로 생성하고 

반복자를 리턴  

 Path normalize() //정규화

 상대 경로로 표기할 때 불필요한 요소를 제거

 WatchKey register(...)

WatchService를 등록 (아직 안나온 내용) 

 File toFile()

파일 경로를 java.io.File 객체로 리턴 

(java.nio.Path에서 java.io.File로) 

 String toString

파일 경로를 문자열로 리턴 

 URI toUri()

파일 경로를 URI 객체로 리턴 




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
package testIO;
 
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Iterator;
 
public class ExampleMain {
    public static void main(String[] args) {
 
        Path path = Paths.get("C:\\Workspace\\study01\\src\\testIO\\ExampleMain.java"); // Path 구현객체 생성
        System.out.println("[파일명]" + path.getFileName()); // Path의 파일명만 가진 Path 리턴. 파일명 출력된다.
        System.out.println("[상위 디렉토리 명]" + path.getParent()); // 부모디렉토리 Path 리턴
        System.out.println("[중첩 경로 수]" + path.getNameCount()); // 중첩 경로 수
        System.out.println();
 
        for (int i = 0; i < path.getNameCount(); i++) {
 
            System.out.println(path.getName(i)); // Path의 최상위 경로(인덱스 0)부터 차례대로 Path리턴하여 출력
 
        }
        System.out.println();
 
        // ★★★경로에 있는 모든 디렉토리, 파일을 Path객체로 생성하고 반복자 리턴
        Iterator<Path> iterator = path.iterator();// 경로에 있는 모든 디렉토리와 파일을 Path객체로 생성하고 반복자 리턴
        while (iterator.hasNext()) {
 
            Path temp = iterator.next();
            System.out.println(temp.getFileName());
 
        }
 
    }
 
}
 
cs




2. 파일 시스템 정보(FileSystem)


OS의 파일시스템은 FileSystem 인터페이스로 접근. FileSystems의 getDefault()가 FileSystem 구현객체 리턴


FileSystem fileSystem = FileSystems.getDefault();


lterable<FileStore> getFileStores() : 드라이버 정보를 가진 FileStore 객체들을 리턴


FileStore 객체 : 드라이버 정보를 가짐

드라이버 : 다양한 하드웨어와 운영체제를 연결해주는 장치


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
package testIO;
 
import java.io.IOException;
import java.nio.file.FileStore;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
 
public class ExampleMain {
    public static void main(String[] args) throws IOException {
 
        FileSystem fileSystem = FileSystems.getDefault();
        for (FileStore store : fileSystem.getFileStores()) {
            // 드라이버 정보를 가진 FileStore 객체들을 리턴 // FileStore 객체에는 드라이버 정보가 들어있다.
            System.out.println("드라이버명 : " + store.name()); // 드라이버 이름이 없는건지 콘솔에는 빈칸으로 표시된다.
            System.out.println("파일시스템 종류 : " + store.type());
            System.out.println("전체 공간 : " + store.getTotalSpace() + "바이트"); // 예외 던지기
            System.out.println("사용 중인 공간 : " + (store.getTotalSpace() - store.getUnallocatedSpace()) + "바이트");
            System.out.println("사용 가능한 공간 : " + store.getUnallocatedSpace() + "바이트");
            System.out.println();
        }
 
        System.out.println("파일 구분자 : " + fileSystem.getSeparator());
 
        for (Path path : fileSystem.getRootDirectories()) {
            System.out.println(path); // path.toString()로 안써도 경로 출력된다.
        }
 
    }
 
}
 





3.  Files : 파일 속성 읽기 / 파일, 디렉토리 생성 / 삭제


java.nio.file.Files 클래스는 파일과 디렉토리의 생성 및 삭제, 이들의 속성을 읽는 메서드 제공


Files.정적메서드() //정적 메서드 API 도큐먼트 참고


파일의 속성 읽고 출력하기  /디렉토리 내용 얻기  추가

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
package testIO;
 
import java.io.IOException;
import java.nio.file.FileStore;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
 
public class ExampleMain {
    public static void main(String[] args) throws IOException {
        // 경로 정보를 담는 Path 객체 생성
        Path path = Paths.get("C:\\Workspace\\study01\\src\\testIO\\ExampkeMain.java");
        System.out.println("디렉토리 여부" + Files.isDirectory(path));
        System.out.println("파일 여부" + Files.isRegularFile(path));
        System.out.println("마지막 수정 시간" + Files.getLastModifiedTime(path));
        System.out.println("파일 크기" + Files.size(path));
        System.out.println("소유자" + Files.getOwner(path));
        System.out.println("숨김 파일 여부" + Files.isHidden(path));
        System.out.println("읽기 가능 여부" + Files.isReadable(path));
        System.out.println("쓰기 가능 여부" + Files.isWritable(path));
    }
 
}
 
cs




와치 서비스(WatchService)


디렉토리(폴더) 내부의 파일 변경 통지 메커니즘


와치 서비스 객체 생성 : FileSystem객체에서 newWatchService()로 WatchService 객체 생성

WatchService watchService = FileSystems.getDefault.newWatchService()


와치 서비스 실행

1. FileSystem객체에서 newWatchService()로 WatchService 객체 생성 

2. Path객체 directory 생성하여 경로 지정해주고

2. directory(Path)의 register(watchService, WatchEventKind) 메서드에 watchService, 감시event목록 매개값으로 주어 와치서비스 등록

(감시 event 목록은 StandardWatchEventKinds.상수 이용)

3. 무한루프로 watchService의 take() 메서드를 실행, 

4. watchService는 event가 발생하면 watchKey 생성. take()는 watchKey가 Queue에 들어오는 것 감지하고 WatchKey객체 리턴

5. watchkey의 pollEvent() 메서드가 이벤트 발생목록 List<WatchEvent<?>> 객체 리턴

6. 루프로 list에 있는 이벤트 항목을 꺼내어 kind() 메서드로 Kind(이벤트 종류)객체 얻기

7. if 매개값 자리에서 pollevent()로 얻어낸 리스트에서 뽑아낸 Kind 객체와 StandardWatchEventKinds.상수 비교하여 

   실행문에서 각 이벤트 발생 시의 실행 코드 작성



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
package testIO;
 
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.List;
 
import javafx.application.Application;
import javafx.stage.Stage;
 
public class ExampleMain extends Application {
    public static void main(String[] args) {
 
        launch();
        // launch 메서드는 메인클래스의 객체를 생성하고 메인 윈도우를 생성한 다음 start() 메서드를 호출한다.
        // start() 메서드는 run() 메서드를 호출한다.
    }
 
    class WatchServiceThread extends Thread {// 스레드 하위 클래스 생성 //main의 Thread하위클래스객체.start()가 run() 실행
 
        @Override
        public void run() {
            // FileSystem객체.newWatchService()로 와치서비스 객체 생성
            try {
                WatchService watchService = FileSystems.getDefault().newWatchService();
                Path direction = Paths.get("C:\\test");
                // 경로를 지정한 Path객체의 register()메서드로 와치 서비스를 등록할 수 있다.
                // path에 와치서비스를 등록한다: 와치서비스 객체와 이벤트 종류를 매개값으로 준다.
                // 디렉토리 경로를 감시하는 서비스 등록
                direction.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
                        StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY); // StandardWatchEventKinds
                                                                                                        // Enum은 아니고
                                                                                                        // class
 
                while (true) { // 와치 서비스가 등록되면, 무한 루프로 와치 서비스를 실행(watch키를 받아 이벤트 처리하는 작업)한다.
                    // take()메서드는 watchService가 생성한 Watch키 객체가 Queue에 들어오면 WatchKey객체를 리턴
                    WatchKey watchKey = watchService.take(); // InterruptedException 발생할 수 있다.
 
                    List<WatchEvent<?>> eventList = watchKey.pollEvents();// watchKey 객체의 이벤트 조사 메서드를 실행하여 이벤트 목록을 얻는다.
 
                    for (WatchEvent event : eventList) { // 이벤트 목록에서 이벤트 항목 꺼내오기
 
                        // 이벤트 상수는 Kind타입. Kind는 WatchEvent의 하위 인터페이스로,
                        // event.kind()로 Kind타입 리턴 안해도 비교는 가능
                        if (event == StandardWatchEventKinds.ENTRY_CREATE) {
 
                            // 새로운 파일 생성 시 실행문
 
                        } else if (event == StandardWatchEventKinds.ENTRY_DELETE) {
 
                            // 파일 삭제 시, 실행문
                        } else if (event == StandardWatchEventKinds.ENTRY_MODIFY) {
 
                            // 파일 수정 시, 실행문
                        }
 
                    }
                    boolean valid = watchKey.reset(); //와치 서비스 등록을 해지해준다.
                    if(!valid) {break;} // 해지 실패시, 무한루프(와치서비스 실행) break;
 
                }
 
            } catch (IOException | InterruptedException e) {
 
                e.printStackTrace();
            }
 
        }
 
    }
 
    @Override
    public void start(Stage arg0) throws Exception {
        // TODO Auto-generated method stub
 
    }
 
}
 // 뒤 코드 추가 //뒷부분 보고 코드 끝까지 다시 작성해보기
cs




파일 채널


java.nio.channel.FileChannel 클래스를 이용하여 파일 읽기와 쓰기를 할 수 있다.


FileChannel 생성과 닫기


FileChannel fileChannel = FileChannel.open(Path path, OpenOption ... options); //StandardOpenOption의 열거 상수


  StandardOpenOption의 열거 상수

READ / WRITE / CREATE / CREATE_NEW / 

APPEND : 파일 끝에 데이터 추가(WRITE나 CREATE와 함께 사용됨) / 

DELETE_ON_CLOSE : 채널을 닫을 때 파일을 삭제(임시 파일 삭제할 때 사용)/

TRUNCATE_EXISTING : 파일을 0바이트로 잘라냄(WRITE와 함께 사용됨)



새파일 생성하여 쓰기 전용으로 열기

Path path = Paths.get("C:\\test\\test.txt");

 FileChannel filechannel = FileChannel.open(path, 

StandardOpenOption.CREATE_NEW,

StandardOpenOption.WRITE);        


FileChannel을 더 이상 이용하지 않을 경우에는 colse() 메서드로 닫아주어야한다.




파일 쓰기와 읽기


파일에 쓰기


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
package networking;
 
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 ExampleMain {
    public static void main(String[] args) throws IOException {
        
        Path path = Paths.get("C:\\test\\test.txt");
        FileChannel fileChannel 
        = FileChannel.open(path, 
                StandardOpenOption.CREATE,
                StandardOpenOption.WRITE); //새파일 생성하여 쓰기전용으로 열기
        
        String data = "안녕하세요";
        Charset charset = Charset.defaultCharset(); //디폴트 문자셋 객체 생성
        //문자열을 인코딩하여 바이트 버퍼에 저장
        ByteBuffer byteBuffer = charset.encode(data); 
        //파일채널로 파일에 문자열 출력(저장)
        int BytesCount = fileChannel.write(byteBuffer);
        
        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
39
40
41
42
43
44
45
46
package networking;
 
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 ExampleMain {
    public static void main(String[] args) throws IOException {
 
        Path path = Paths.get("C:\\test\\test.txt");
        FileChannel fileChannel = 
                FileChannel.open(path, 
                        StandardOpenOption.READ); // 기존 파일 읽기전용으로 열기
 
        Charset charset = Charset.defaultCharset(); // 디폴트 문자셋 객체 생성
        String data = null// 디코딩하고 문자열 저장할 data 문자열 변수
        // 파일채널로 바이트 버퍼 데이터를 읽거나 저장하고나서 메서드가 리턴하는 바이트 수를 담을 변수
        int byteCount;
 
        // 읽어온 데이터 저장 할바이트 버퍼 생성
        ByteBuffer byteBuffer = ByteBuffer.allocate(100);
 
        while (true) {
            //버퍼 capacity 내에서 파일 읽기 
            byteCount = fileChannel.read(byteBuffer); 
            if (byteCount ==-1break;
            byteBuffer.flip();
 
            // 제대로 읽었는지 디코딩하여 문자열로 받기
            data = charset.decode(byteBuffer).toString();
 
            byteBuffer.clear(); // 재사용 가능하도록 저장 데이터 삭제, 초기화
        }
 
        fileChannel.close(); //무한루프를 빠져나올 장치를 설치하지 않으면 루트 뒤쪽은 에러가 난다
 
        System.out.println(data);
        
    }
 
}
 
cs



byteCount ==-1 // read()메서드는 더이상 읽을 바이트가 없을 때 -1을 리턴한다.

read()는 마지막 바이트의 위치를 리턴하는데, 버퍼에 저장한 마지막 바이트의 위치는 position-1이므로

position의 위치가 0일때(더이상 읽을 바이트가 없을 때)는 0-1 = -1 을 리턴한다.



파일 복사


FileChannel 객체를 읽기용, 쓰기용으로 각각 직접 생성하여 파일 복사


1. 읽기 FileChannel과 쓰기 FileChannel 각각 생성

2. 무한루프 안에서 fileChannel_from.read()로 읽은 바이트를 버퍼에 저장하고  (read() 메서드가 -1을 리턴하면 break;)


NIO의 Files 클래스의 copy() 메서드를 이용하여 파일 복사


Files.copy( 복사할 파일 경로, 대상이 되는 파일 경로, 복사 옵션 )


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package networking;
 
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
 
public class ExampleMain {
    public static void main(String[] args) throws IOException {
 
        Path from = Paths.get("C:\\test\\test.txt");
        Path to = Paths.get("C:\\test\\test2.txt");
        
        Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING);
        System.out.println("Files 클래스의 copy()메서드로 간단하게 파일 복사 완료~");
    }
 
}
 
cs

파일을 복사할 때 Files 클래스의 copy()메서드를 이용하면 코드가 훨씬 짧아진다.


StandardCopyOption의 열거 상수

REPLACE_EXCISTING : 타겟파일이 존재하면 대체한다. 

COPY_ATTRIBUTES : 파일의 속성까지도 복사

NOFOLLOW_LINK : 링크 파일일 경우 링크 파일만 복사하고 링크된 파일은 복사하지 않는다.



파일 비동기 채널


FileChannel의 read() write()는 파일 입출력 작업 동안 블로킹 -> 호출하는 동안 UI갱신이나 이벤트 처리 불가능

 -> 그래서 별도의 스레드 생성하여 read() write() 메서드 호출 

 -> 하지만 이 경우, 처리해야할 파일 수가 많으면 스레드 수 폭증 (병렬작업 폭증)->메모리 사용량 늘어남 

 -> 어플리케이션 성능 저하 //( Q. 그래서 사용하는게 스레드풀(큐로 작업 스레드 수 제한) 아닌가? /  A. 맞다)


NIO는 불특정 다수의 파일 / 대용량 파일의 입출력 작업을 위해 비동기 파일 채널(AsynchronousFileChannel) 별도 제공

AsynchronousFileChannel은 파일의 데이터 입출력을 위해 read()와 write() 메서드 호출하면 스레드풀에 작업처리 요청하고 

이 메서드들을 즉시 리턴, 실질적인 입출력 작업 처리는 스레드풀의 작업 스레드가 담당 

// 리턴값을 어떻게 바로 낼까 입출력한 바이트 수를 리턴하려면 입출력 작업이 완료될 때까지 기다려야하지 않나??



AsynchronousFileChannel 생성과 닫기


방법 1) open() 메서드 매개값으로 Path 객체, OpenOption을 주어 FileChannel 생성방법과 동일한 방법으로 비동기 객체 생성


AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(

Path file,

OpenOption ...options

)


방법 2) 

기본 스레드풀의 최대 스레드 개수는 개발자가 지정할 수 없기 때문에 별도의 스레드풀 만든 후 이 를 매개값으로 파일채널 생성 

open() 메서드 매개값으로 

Path, Set<? extends OpenOption>, ExecutorService executorService, FileAttribute<?>...attr 를 주어 객체 생성


AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(

//비동기 채널 객체 생성 시 필요한 매개값들

Path file,

Set<? extends OpenOption>,

ExecutorService executorService,

FileAttribute<?>...attr (생략 가능)


)


AsynchronousFileChannelfmf 를 더이상 사용하지 않을 때에는 close()메서드를 호출하여 닫아준다.



비동기 파일 읽기와 쓰기


FileChannel과 같이 read(), write() 메서드로 입출력


비동기 파일의 읽고 쓰는 메서드에 필요한 매개값 : 

읽거나 저장할 바이트버퍼 : ByteBuffer bytebuffer 

읽거나 쓸 위치 : long position 

콜백메서드로 전달할 첨부 객체 : A(타입파라미터) attachment

컴플리션핸들러 구현객체( 작업완료결과를 알려줄 콜백메서드 호출) : CompletionHandler<Integer, A> 

//integer는 write(), read() 메서드의 결과 타입, 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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package testIO;
 
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 ExampleMain {
    public static void main(String[] args) throws Exception {
        
        //[비동기로 파일 생성 및 쓰기]
        
        //스레드풀 생성
        ExecutorService executorService = Executors.newFixedThreadPool(
                Runtime.getRuntime().availableProcessors());
        
        //파일채널 객체는 입출력시에 필요하므로 입출력(루프 한 번)이 끝나면(블럭 내의 실행문이 끝나면) 사라져도 되는 객체
        for(int i = 0; i < 5; i++) {
            
            Path path = Paths.get("C:\\test2folder\\file" + i +".txt"); 
            //경로가 유효하지 않으면(없는 폴더나 파일) 에러는 안난다
            //System.out.println(path.toString()); 
            //유효한 경로가 아니면 출력 되지 않는다.
            
            Files.createDirectories(path.getParent()); //path에 포함된 모든 경로를 디렉토리로 생성
            //Files.createFile(path); //경로에 있는 파일 생성
            //이 예제에서는 채널 생성시에 오픈옵션으로 파일을 생성한다.
            //path를 매개값으로 줘버리면 파일까지 디렉토리로 인식해서 n.txt라는 이름의 폴더가 생성된다. 
            
            //비동기 파일 채널 생성
            AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
                    path, 
                    EnumSet.of(StandardOpenOption.CREATE,
                            StandardOpenOption.WRITE),
                    executorService
                    ); //두번째 매개값으로 열거셋을 주면 스레드 객체를 매개값으로 받는 open()메서드를 호출하는 것
            
            //파일에 저장할 데이터를 ByteBuffer에 저장
            Charset charset = Charset.defaultCharset(); //인코딩을 위해 문자셋 객체 생성
            ByteBuffer byteBuffer= charset.encode("안녕하세요"); //문자열을 인코딩하여 바이트버퍼로 리턴
            
            //첨부객체 생성
            class Attachment {
                Path path; //파일 경로 저장할 변수
                AsynchronousFileChannel fileChannel; //비동기파일채널 저장할 변수
            }
            Attachment attachment = new Attachment();//첨부 객체 생성
            attachment.path = path; //첨부 객체에 경로 저장
            attachment.fileChannel = fileChannel; //첨부 객체에 비동기파일채널 저장
            //CompletionHandler(비동기채널 입출력의 실행 결과를 알려주는 객체)의 
            //콜백(callback)메서드에 줄 정보들을 Attachment라는 사용자 지정 타입의 필드에 저장한 것
 
            
            //CompletionHandler 인터페이스 구현 객체 생성
            CompletionHandler<Integer, Attachment> completionHandler = 
                    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) {e.printStackTrace();}
                            
                        }
 
                        @Override
                        public void failed(Throwable exc, Attachment attachment) {
                            //Throwable은 예외 타입!
                            
                            exc.printStackTrace();
                            
                            try { attachment.fileChannel.close();
                            } catch (IOException e) {e.printStackTrace();}
                        }
                
                
            };
                    
            //int writeMethodReturnValue;
            //writeMethodReturnValue = 
            //*교재에 비동기 파일 채널은 입출력 메서드 호출시에 
            //'스레드풀에 작업처리를 요청하고 메서드들을 즉시 리턴 시킨다'라고 써있어서 리턴 값이 있는 줄 알았는데 
            //비동기 채널의 입출력 메서드는 리턴타입이 void이므로, 리턴값이 없다.
            
            fileChannel.write(byteBuffer, 0, attachment, completionHandler);
            //콜백메서드는 자동으로 호출되기 때문에 호출 시 직접 매개값을 써줄 수가 없으니 ★★★
            //첨부할 객체가 있다면 입출력 메서드에 매개값으로 주어야한다. ★★★
            //매개값 순서대로 바이트버퍼 / 출력 시작인덱스 / 참조 객체 /CompletionHandler 구현 객체
    
        }
        
    Thread.sleep(1000); 
    // 1초 기다리는 이유? : 메인스레드가 끝나기 전에 callback메서드가 호출되기(스레드풀에서 입출력 작업이 끝나기를)를 기다림
    //스레드풀이 작업을 완료해야 컴플리션핸들러의 메서드에서 파일채널을 닫는다.
    //메인에서 쓴 입출력 메서드는 스레드풀에 작업을 맡기므로 바로 리턴된다고 입출력이 끝난게 아니다
    //-> 입출력 메서드 호출한 곳에선 파일채널 닫으면 안된다. 
    
    // 스레드풀 종료
    executorService.shutdown();
        
    }
 
}
 



*정상적으로 실행완료 = 리턴한다 =/= 반환값이 있다

메서드의 리턴타입이 void여도 실행 완료 시 메서드가 리턴된다고 표현한다.



write()로 데이터를 저장할 때는 ByteBuffer의 참조 변수에 문자셋객체.인코딩(문자열)로 바이트버퍼 객체를 리턴하여 저장하지만

read()로 파일을 읽을 때는

파일 capacity만큼 할당된 버퍼를 생성해서 read()의 첫번째 매개값으로 주면 된다. 이하 동일

ByteBuffer byteBuffer = ByteBuffer.allocate( (int) fileChannel.size() );





'Java > study' 카테고리의 다른 글

java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for​ ~  (0) 2019.06.13
NIO기반 네트워킹  (0) 2019.04.25
스레드풀(ThreadPool)  (0) 2019.04.24
IO기반 네트워킹  (0) 2019.04.22
IO기반 입출력  (0) 2019.04.22