elevne's Study Note

Java - NIO (3) 본문

Backend/Java

Java - NIO (3)

elevne 2023. 5. 30. 22:52

java.nio.channels.FileChannel 을 이용하면 파일 읽기와 쓰기를 할 수 있다. FileChannel 은 동기화 처리가 되어있기 때문에 멀티스레드 환경에서 사용해도 안전하다고 한다. 

 

 

FileChannelwrite() 메소드로 파일에 바이트를 작성, 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");
}

 

 

result

 

 

파일을 복사할 때는 ByteBuffer 과 FileChannel 2 개를 직접 생성해서 복사를 구현할 수도 있지만, 위 코드와 같이 NIO Files 클래스의 copy() 메소드를 사용하여 쉽게 복사를 진행할 수도 있다.

 

 

 

FileChannelread(), write() 메소드는 파일 입출력 작업 동안 블로킹된다. 따라서 별도의 작업 스레드를 생성해서 이 메소드들을 호출해줘야 한다. 만약 동시에 처리해야할 파일의 수가 많다면 스레드의 수도 증가하기 때문에 문제가 될 수 있다. 이러한 문제점을 보완하기 위해 NIO 에서는 불특정 다수의 파일 및 대용량 파일의 입출력 작업을 위해 비동기 파일 채널, AsynchronousFileChannel 을 별도로 제공한다.

 

 

AsynchronousFileChannel 은 파일의 입출력을 위해 read(), write() 메소드를 호출하면 스레드풀에게 작업 처리를 요청하고 이 메소드들을 즉시 리턴시킨다. 실질적인 입출력 작업 처리는 스레드풀의 작업 스레드가 담당하고, 작업 스레드가 파일 입출력을 완료하면 콜백 메소드가 자동적으로 호출되기 때문에 작업 완료 후 실행해야할 코드가 있다면 콜백 메소드에 작성하면 된다고 한다. (AsynchronousFileChannel 객체를 생성할 때 인자로 따로 스레드풀을 넘겨주지 않으면 내부적으로 생성되는 기본 스레드풀을 이용해서 스레드를 관리한다. 최대 스레드 개수를 따로 지정해주기 위해 스레드풀을 넘겨줄 수 있다)

 

 

read()write() 메소드를 수행할 때에는 매개값을 읽거나 쓰기 위한 ByteBuffer, position 그리고 attachmentCompletionHandler 객체가 필요하다. 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();
}

}

 

 

result

 

 

 

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();
}

}

 

 

result

 

 

 

 

 

 

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