elevne's Study Note
Java - NIO (3) 본문
java.nio.channels.FileChannel 을 이용하면 파일 읽기와 쓰기를 할 수 있다. FileChannel 은 동기화 처리가 되어있기 때문에 멀티스레드 환경에서 사용해도 안전하다고 한다.
FileChannel 의 write() 메소드로 파일에 바이트를 작성, read() 메소드로 바이트를 읽는다. 이 두 메소드 다 매개값으로 ByteBuffer 객체를 받게된다. write() 메소드에서는 ByteBuffer 의 position 부터 limit 까지 파일에 바이트를 작성하고 read() 메소드에서는 파일에서 읽혀지는 바이트가 ByteBuffer 의 position 부터 저장된다. write() 메소드의 리턴값은 ByteBuffer 에서 파일로 쓰여진 바이트의 수, read() 의 리턴값은 ByteBuffer 로 읽혀진 바이트의 수이다.
public static void main(String[] args) throws Exception {
// FILE 생성하기
Path path = Paths.get("/Users/wonil/Documents/file.txt");
FileChannel fileChannel = FileChannel.open(
path, StandardOpenOption.CREATE, StandardOpenOption.WRITE
);
String data = "안녕하세요";
Charset charset = Charset.defaultCharset();
ByteBuffer byteBuffer = charset.encode(data);
int byteCount = fileChannel.write(byteBuffer);
System.out.println("file.txt: "+byteCount+" bytes written");
fileChannel.close();
// FILE 읽기
FileChannel fileChannel2 = FileChannel.open(
path, StandardOpenOption.READ
);
ByteBuffer byteBuffer2 = ByteBuffer.allocate(100);
Charset charset2 = Charset.defaultCharset();
String data2 = "";
int byteCount2;
while (true) {
byteCount2 = fileChannel2.read(byteBuffer2);
if (byteCount2 == -1) break;
byteBuffer2.flip();
data2 += charset.decode(byteBuffer2).toString();
byteBuffer2.clear();
}
fileChannel2.close();
System.out.println("file.txt: "+data2);
// FILE 복사
Path to = Paths.get("/Users/wonil/Documents/fileCopy.txt");
Files.copy(path, to, StandardCopyOption.REPLACE_EXISTING);
System.out.println("FILE COPY COMPLETE");
}
파일을 복사할 때는 ByteBuffer 과 FileChannel 2 개를 직접 생성해서 복사를 구현할 수도 있지만, 위 코드와 같이 NIO Files 클래스의 copy() 메소드를 사용하여 쉽게 복사를 진행할 수도 있다.
FileChannel 의 read(), write() 메소드는 파일 입출력 작업 동안 블로킹된다. 따라서 별도의 작업 스레드를 생성해서 이 메소드들을 호출해줘야 한다. 만약 동시에 처리해야할 파일의 수가 많다면 스레드의 수도 증가하기 때문에 문제가 될 수 있다. 이러한 문제점을 보완하기 위해 NIO 에서는 불특정 다수의 파일 및 대용량 파일의 입출력 작업을 위해 비동기 파일 채널, AsynchronousFileChannel 을 별도로 제공한다.
AsynchronousFileChannel 은 파일의 입출력을 위해 read(), write() 메소드를 호출하면 스레드풀에게 작업 처리를 요청하고 이 메소드들을 즉시 리턴시킨다. 실질적인 입출력 작업 처리는 스레드풀의 작업 스레드가 담당하고, 작업 스레드가 파일 입출력을 완료하면 콜백 메소드가 자동적으로 호출되기 때문에 작업 완료 후 실행해야할 코드가 있다면 콜백 메소드에 작성하면 된다고 한다. (AsynchronousFileChannel 객체를 생성할 때 인자로 따로 스레드풀을 넘겨주지 않으면 내부적으로 생성되는 기본 스레드풀을 이용해서 스레드를 관리한다. 최대 스레드 개수를 따로 지정해주기 위해 스레드풀을 넘겨줄 수 있다)
read() 와 write() 메소드를 수행할 때에는 매개값을 읽거나 쓰기 위한 ByteBuffer, position 그리고 attachment 와 CompletionHandler 객체가 필요하다. attachment 매개값은 콜백 메소드로 전달할 첨부 객체로, 첨부 객체는 콜백 메소드에서 결과값 외에 추가적인 정보를 얻고자 할 때 사용되는 객체를 말한다. (만약 첨부 객체가 필요 없다면 null 을 넣어줘도 된다) CompletionHandler<Integer, A> 구현 객체도 필요한데, Integer 은 입출력 작업의 결과 타입으로 read() 나 write() 가 읽거나 쓴 바이트 수이다. A 는 첨부 객체 타입으로 개발자가 CompletionHandler 구현 객체를 작성할 때 임의로 지정 가능하다. 만약 첨부 객체가 필요 없는 경우라면 Void 가 들어간다. CompletionHandler<Integer, A> 구현 객체는 비동기 작업이 정상적으로 완료된 경우와 예외 발생으로 실패된 경우에 자동 콜백되는 completed, failed 메소드를 가지고 있어야 한다.
package nio;
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(
Runtime.getRuntime().availableProcessors()
);
for (int i = 0; i < 10; i++){
Path path = Paths.get("/Users/wonil/Documents/Temp/file"+i+".txt");
Files.createDirectories(path.getParent());
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(
path,
EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE),
executorService
);
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<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());
}
@Override
public void failed(Throwable exc, Attachment attachment) {
exc.printStackTrace();
try { attachment.fileChannel.close(); } catch (IOException e) {}
}
};
fileChannel.write(byteBuffer, 0, attachment, completionHandler);
}
Thread.sleep(1000);
executorService.shutdown();
}
}
package nio;
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.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 AsynchronousFileChannelReadExample {
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()
);
for (int i = 0; i < 10; i++) {
Path path = Paths.get("/Users/wonil/Documents/Temp/file"+i+".txt");
AsynchronousFileChannel asynchronousFileChannel = AsynchronousFileChannel.open(
path,
EnumSet.of(StandardOpenOption.READ),
executorService
);
ByteBuffer byteBuffer = ByteBuffer.allocate((int)asynchronousFileChannel.size());
class Attachment {
Path path;
AsynchronousFileChannel fileChannel;
ByteBuffer byteBuffer;
}
Attachment attachment = new Attachment();
attachment.path = path;
attachment.fileChannel = asynchronousFileChannel;
attachment.byteBuffer = byteBuffer;
CompletionHandler<Integer, Attachment> completionHandler =
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 { asynchronousFileChannel.close();} catch (IOException e) {}
}
@Override
public void failed(Throwable exc, Attachment attachment) {
exc.printStackTrace();
try {asynchronousFileChannel.close();} catch (IOException e) {}
}
};
asynchronousFileChannel.read(byteBuffer, 0, attachment, completionHandler);
}
executorService.shutdown();
}
}
Reference:
이것이 자바다
'Backend > Java' 카테고리의 다른 글
Java - ScheduledExecutorService (0) | 2023.06.16 |
---|---|
Java - CompletableFuture (0) | 2023.06.15 |
Java - NIO (2) (0) | 2023.05.29 |
Java - NIO (1) (0) | 2023.05.28 |
Java - Reflection (1) | 2023.05.27 |