본문 바로가기

WhiteShip Java Study : 자바 처음부터 멀리까지

NIO

선장님과 함께하는 자바 스터디입니다.

자바 스터디 Github

github.com/whiteship/live-study

 

whiteship/live-study

온라인 스터디. Contribute to whiteship/live-study development by creating an account on GitHub.

github.com

나의 Github

github.com/cmg1411/whiteShip_live_study

 

cmg1411/whiteShip_live_study

✍ 자바 스터디할래. Contribute to cmg1411/whiteShip_live_study development by creating an account on GitHub.

github.com

 

 

 

 

  • 스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O
  • InputStream과 OutputStream
  • Byte와 Character 스트림
  • 표준 스트림 (System.in, System.out, System.err)
  • 파일 읽고 쓰기

 

 

NIO

  • 새로운 IO (New IO) 라는 의미로 java 4 버전에 추가된 java.nio 패키지를 말한다.
  • 이후 버전이 올라가며 지속적으로 개선되어 왔다. 아래는 java.nio 패키지 안의 패키지들이다.

 

서비스 제공자 클래스

내부적으로 ServiceLoader 를 이용하여 다른 드라이버를 사용할 수 있게 해준다.

(본인 ServiceLoader 관련 글 alkhwa-113.tistory.com/entry/ServiceLoader)

예를 들어, java.nio.files.spi.FileSystemProvider 는 아래와 같은 메서드를 가지고 있다.

 

    private static List<FileSystemProvider> loadInstalledProviders() {
        List<FileSystemProvider> list = new ArrayList<FileSystemProvider>();

        ServiceLoader<FileSystemProvider> sl = ServiceLoader
            .load(FileSystemProvider.class, ClassLoader.getSystemClassLoader());

        // ServiceConfigurationError may be throw here
        for (FileSystemProvider provider: sl) {
            String scheme = provider.getScheme();

            // add to list if the provider is not "file" and isn't a duplicate
            if (!scheme.equalsIgnoreCase("file")) {
                boolean found = false;
                for (FileSystemProvider p: list) {
                    if (p.getScheme().equalsIgnoreCase(scheme)) {
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    list.add(provider);
                }
            }
        }
        return list;
    }

 

만약 Dropbox 와 같은 서비스를 개발한다고 할 떄 새로운 파일 시스템을 정의 한다고 치자. 그럼 새로운 FileSystem 클래스 구조가 필요할 것이고, 이 정보를 읽어와서 새로운 메서드를 제공할 수 있게 하는 것이 서비스 제공자 클래스이다.

새로운 파일 시스템을 만드는 개발자는 FileSystemProvider 클래스를 상속받아서 개발을 하면 된다.

(사실 잘 모르겠다. ㅎ 영어공부를 해야하나..)

 

아래는 Oracle 에서 사용자 정의 FileSystemProvider 구현 튜토리얼

docs.oracle.com/javase/8/docs/technotes/guides/io/fsp/filesystemprovider.html 

 

Developing a Custom File System Provider

Developing a Custom File System Provider Introduction The NIO.2 API introduced in the Java SE 7 release provides the ability to develop a custom file system provider that can be used to manage file system objects. A file system is essentially a container w

docs.oracle.com

 

아래는 Ftp 에서의 새로운 FileSystem 을 구현한 예제

github.com/fge/java7-fs-ftp

 

fge/java7-fs-ftp

A Java 7 FileSystem implementation for the FTP protocol - fge/java7-fs-ftp

github.com

 

 

IO vs NIO

새로운 단어가 많이 등장했다. 

하나하나 알아보자.

 

 

 

스트림 vs 채널

  • java.io 는 스트림 기반이라 했고, 스트림의 특성은 단방향이라 했다. 따라서 입력 스트림, 출력 스트림을 따로 만들었었다.
  • java.nio 는 채널 기반이다.
  • 채널의 특징은 양방향이다. 따라서 입출력 채널이 나워져있지 않다.
  • 양방향 : 하나의 채널로 입출력이 가능.

 

non - buffer vs buffer

  • buffer : 데이터를 한 곳에서 다른 한 곳으로 전송하는 동안 일시적으로 그 데이터를 보관하는 메모리의 영역
  • 버퍼를 사용하면 입출력 데이터를 버퍼에 모아놓고 한꺼번에 보내는 일이 가능하기 때문에, 입력단과 출력단 사이의 데이터 이동 횟수를 줄일 수 있고, 전체 시간이 더 빨라진다.
  • 여기서는 바이트의 입출력을 버퍼에 임시 저장해놨다가 한꺼번에 입출력하는 용도로 쓰인다.
  • NIO - buffer
    • 기본적으로 버퍼를 사용한다.
    • 출력시 버퍼에 쌓다가 버퍼에 쌓인 데이터를 출력 채널로 보낸다.
    • 입력시 데이터를 버퍼에 쌓는다.
    • 버퍼 내에서 데이터의 위치를 이동해 가면서 필요한 부분만 읽고 쓸 수 있다.
  • IO - non buffer
    • 버퍼를 사용하지 않는다.
    • 출력 스트림이 1 바이트를 쓰면 입력 스트림이 1 바이트를 읽는다.
    • 버퍼를 사용하지 않기 때문에 읽은 데이터를 즉시 처리한다.
    • 따라서 입력된 데이터의 위치를 이동하거나 하는 작업은 불가능하다.
    • 이런 단점을 보완하기 위해 보조스트림 bufferedInputStream 등을 사용한다.

 

 

 

non - blocking vs blocking

  • [블로킹] 은 프로그램 제어권에 대한 단어이다.
  • 블로킹
    • 호출자에서 메서드를 호출 했을때 메서드가 끝날 때 까지 프로그램의 제어권을 호출자에게 반납하지 않는 것을 말한다. 따라서 작업하는 메서드 이외에는 호출하는 쪽의 동작이 멈추고 메서드의 실행이 끝날 때 까지 기다린다.
    • 따라서 하나의 호출마다 하나의 스레드가 필요하게 된다.
  • 논블로킹
    • 호출받은 메서드가 프로그램의 제어권은 바로 반납하고, 호출자 쪽은 호출자 대로, 메서드는 메서드대로 멀티스레드 같은 방식으로 동작하는 것을 말한다. 
    • 따라서 요청을 하는 스레드는 하나만 있으면 된다.

 

  • IO 는 블로킹 : 만약 입력 스트림의 read() 를 실행하면 데이터 입력이 완료될때 까지 IO 스레드는 동작을 멈추고 대기한다. 이를 블로킹이라 하며 블로킹을 바져나오는 방법은 스트림을 close() 하는 방법뿐이다.
  •  
  • NIO : 블로킹과 논블로킹을 모두 지원. 논블로킹의 예로는 thread는 channel에 데이터를 쓰도록 요청할 수 있다. 하지만 write 동작이 끝날때까지 기다리지 않는다. 따라서 스레드는 준비가 완료된 채널들을 바로바로 처리할 수 있는데, 이 역할을 하는 것이 Selector 이다.

 

번외) 동기(Synchronous) vs 비동기(Asynchronous)

  • 이 두 개념은 위의 블로킹 / 넌블로킹과 매우 헷갈린다. 나도 정확하게는 모르지만 겉핥기식으로 기록을 남긴다.
  • 블로킹 / 넌블로킹이 [제어권] 에 대한 개념이었다면, 동기와 비동기는 [시간] 에 대한 개념이다.
  • 동기라는 것은 같은 시간에 모든 정보가 같게 유지되는것을 말하며 그를 위해서는 요청과 동시에 결과가 반환되어야 한다.
  • 그에 반해 비동기는 요청과 결과의 반환이 동시에 이뤄지지 않는다. 즉 요청한 자리에서 결과가 반환되지 않는다는 말이다.

 

블로킹&동기 : java

논블로킹&비동기 : javascript

 

 

 

 

언제 IO 언제 NIO?

  • NIO 가 new 라고, 양방향이 되는 채널이라고, 버퍼를 사용한다고, 블로킹/넌블로킹을 모두 지원한다고 무조건적으로 좋은 것은 아니다.
  • NIO 의 특징들을 되짚어보면, 다음과 같은 사실을 알 수 있다.
    • 넌블로킹 방식으로 처리할 수 있기 때문에, 다수의 클라이언트 연결이나 멀티 파일의 처리가 필요할 시에 스레드를 과도하게 만들 필요가 없다.
    • 버퍼를 사용하기 때문에 입출력 성능이 좋다.
    • 하지만 입출력 처리 자체가 오래 걸리는 경우에는 하나의 스레드에서 처리하기 때문에 뒤에서 대기하는 작업이 많아질 수 있다.
    • 따라서 동시 요청이 많고, 입출력 처리작업의 시간이 적은 경우 적절하다.
  • 그에반해, IO 의 특징을 되짚어보자.
    • 대용량 데이터라 입출력 시간이 긴 경우에, 멀티 스레딩을 이용하여 처리할 수 있고 버퍼를 사용하지 않으므로 하나의 작업에 대해서는 성능이 낫다. (대용량 데이터의 경우 NIO 에서 버퍼의 크기가 문제가 될 수 있다.)
    • 따라서 데이터가 대용량이고 연결 클라이언트 수가 적은 경우 IO 가 더 적절할 수 있다.

 

 

 

 

 

파일, 디렉토리

io 의 File 클래스와 같이 nio 에도 파일의 속성 정보를 제공하는 클래스와 인터페이스들이 있다. io 보다 더 다양한 기능을 제공한다.

java.nio.file 패키지와 java.nio.file.attribute 패키지에서 제공한다.

 

 

 

 

java.nio.file.Path

  • java.io.File 클래스에 대응되는 인터페이스.
  • 파일의 경로를 지정하기 위해 사용.

경로로 Path 객체 생성하기

  • java.nio.file.Paths 클래스의 get() 정적 메서드를 사용.
Path path = Paths.get(String first, String...more);
Path path = Paths.get(URI uri);
  • 경로를 매개변수의 인자로 넘기게 되는데, 절대경로 상대경로 모두 사용 가능하다.
  • String first 와 가변인자로 구성되어 있는 부분은, 상위 디렉토리와 하위 디렉토리를 나눠서 인자로 넘길 수 있다는 뜻이다. 아래 모두 가능하다.
Path path = Paths.get("C:/Temp/dir/file.txt");
Path path = Paths.get("C:/Temp/dir", "file.txt");
Path path = Paths.get("C:", "Temp", "dir", "file.txt");

 

  • Path 인터페이스에는 파일 경로에서 얻을 수 있는 여러가지 메서드를 제공한다.

docs.oracle.com/javase/8/docs/api/

 

Java Platform SE 8

 

docs.oracle.com

 

 

 

java.nio.file.FileSystem

  • 운영체제의 파일 시스템을 얻을 수 있다.
  • FileSystem 인터페이스를 이용한다.
  • FileSystem 타입의 객체는 FileSystems 의 정적 메서드 getDefault() 를 사용한다.
FileSystem fileSystem = FileSystems.getDefault();

 

  • FileSystem 인터페이스에는 다음과 같은 메서드들이 있다.

 

 

 

 

java.nio.file.FileStore

  • 드라이버를 표현한 클래스이다.

getUnallocatedSpace() 와 getUsableSpace() 는 같은 값을 리턴한다.

 

 

 

FileSystem, FileStore 예제

public class FileSystemEx {
    public static void main(String[] args) throws IOException {
        FileSystem fileSystem = FileSystems.getDefault();

        Iterable<FileStore> fileStores = fileSystem.getFileStores();

        for (FileStore fileStore : fileStores) {
            System.out.println("드라이버 명 : " + fileStore.name());
            System.out.println("파일시스템 : " + fileStore.type());
            System.out.println("전체 공간 : " + fileStore.getTotalSpace());
            System.out.println("사용중인 공간 : " + (fileStore.getTotalSpace() - fileStore.getUnallocatedSpace()));
            System.out.println("할당되지 않은 공간 : " + fileStore.getUnallocatedSpace());
            System.out.println("사용가능한 공간 : " + fileStore.getUsableSpace());

        }
    }
}

 

 

출력으로 드라이버별 정보를 얻을 수 있을 것이다.

 

 

 

java.nio.file.Files

  • 파일과 디렉토리의 생성 및 삭제, 속성을 읽는 메서드를 제공.
  • 속성 - 숨김상태인지, 디렉토리인지, 크기가 얼마인지, 소유자가 누구인지
  • 생성자가 private 인 유틸클래스이다.
  • 따라서 모든 메서드는 정적(static) 메서드로 제공된다.

Files 의 메서드

docs.oracle.com/javase/8/docs/api/

 

Java Platform SE 8

 

docs.oracle.com

Files 클래스 메서드 예제

    public static void main(String[] args) throws IOException {
        Path path = Paths.get("파일 경로/abc.txt");

        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).getName());
        System.out.println("숨김 파일 여부 : " + Files.isHidden(path));
    }

 

 

 

 

 

 

 

 

java.nio.files.WatchService

  • 디렉토리 내부에서 파일 생성, 삭제, 수정 등의 내용 변화를 감시하는데 사용
  • 예를 들어, 에디터에서 파일을 편집하고 있을 때, 다른 곳에서 파일을 변경하면 파일 내용이 변했으니 다시 불러올 것인지를 묻는 경우가 있다.
  • WatchService 객체를 만들기 위해서는 FileSystem의 정적 메서드를 호출한다.
WatchService watchService = FileSystem.getDefault().newWatchService();

 

 

 

 

 

 

 

 

 

버퍼

  • NIO 에서는 입출력 성능을 향상시키기 위해 버퍼를 기본으로 사용한다.
  • 버퍼는 읽고 쓰기가 가능한 메모리 배열이다.

 

 

 

 

Buffer 종류

 

어떤 데이터 타입이 저장되느냐 - 데이터 타입에 따른 분류

  • Buffer 는 추상클래스로 많은 하위타입들을 가진다.
  • 다음은 Buffer 의 계층도이다.

 

 

http://eincs.com/2009/08/java-nio-bytebuffer-channel/

 

 

어떤 메모리(위치) 를 사용하느냐 - 넌다이렉트 / 다이렉트 버퍼

  • non-direct 버퍼 : JVM 이 관리하는 힙 메모리 공간을 이용하는 버퍼, 운영체제 - JVM 을 거치므로 논다이렉트
  • direct 버퍼 : 운영체제가 관리하는 메모리 공간을 이용하는 버퍼, 운영체제가 다이렉트.

  • 논 다이렉트 버퍼는 힙영역에서 생성되므로 빠르게 생성된다.
  • 반면, 다이렉트 버퍼는 운영체제에서 생성되므로 native 함수 (C언어) 를 실행하고 여러가지 처리를 하므로 느리다.

 

  • 논 다이렉트 버퍼는 JVM 이 할당한 제한된 힙 메모리 영역을 사용하므로 크기가 작다.
  • 다이렉트 버퍼는 운영체제의 메모리를 사용하므로 큰 영역을 할당할 수 있다.

 

  • 넌 다이렉트 버퍼는 동작할 떄, OS 에 임시 다이렉트 버퍼를 생성하고 거기에 넌 다이렉트 버퍼의 내용을 복사하여 임시 다이렉트 버퍼가 IO 기능을 하기 때문에 입출력 성능이 낮다. (느리다)

 

    private static final int MEGA_BYTE = 14336;

    public static void main(String[] args) {
        ByteBuffer directBuffer = ByteBuffer.allocateDirect(MEGA_BYTE * 1024 * 1024);
        System.out.println(" 다이렉트 버퍼 생성 완료");

        ByteBuffer nonByteBuffer = ByteBuffer.allocate(MEGA_BYTE*1024*1024);
        System.out.println("넌다이렉트 버서 생성 성공");
    }

사실 이론 자체는 위와 같지만 요즘은 워낙에 컴퓨터가 좋아져서 그런지 내 컴퓨터에서는 다이렉트, 넌다이렉트 버퍼 크기 제한이 거의 같은것 같았다.

 

다이렉터 버퍼 - 14335MB 까지는 정상 생성, 14336MB 버퍼생성은 위와같은 오류
넌다이렉터 버퍼 - 14335MB 까지는 정상 생성, 14336MB 버퍼생성은 위와같은 오류

 

 

 

 

버퍼의 생성

  • 버퍼의 생성은 Buffer 추상 클래스를 확장한 각 타입 버퍼에 정의된 각각의 정적 메서드들을 사용하면 된다.
  • 아래의 메서드들이 각각의 타입 버퍼 클래스에 정의되어 있다. (추상 클래스 buffer 에 추상메서드로 정의된 것이 아니라 각각 정의되어 있다.)
  • non-direct 버퍼 : allocate(), wrap()
  • direct 버퍼 : allocateDirect()

 

allocate()

각 리턴타입인 클래스에 정적 메서드로 allocate() 가 정의되어 있다.

 

CharBuffer charBuffer = CharBuffer.allocate(100);
// 100개의 char 형 데이터를 저장할 수 있는 버퍼 생성

 

 

 

wrap()

이미 생성된 자바 배열을 buffer 로 래핑하여 버퍼 객체를 생성한다.

자바 배열은 힙 영역에 생성되므로 wrap() 로 반환되는 버퍼도 넌다이렉트 버퍼이다.

 

int[] intArray = new int[100];
IntBuffer intBuffer = IntBuffer.wrap(intArray);
// int 형 데이터 100개를 저장할 수 있는 넌다이렉트 버퍼 생성

IntBuffer intBuffer = IntBuffer.wrap(intArray, 0, 50);
// intArray 100개 인덱스중 0 ~ 50만 넌다이렉트 버퍼 생성에 사용

 

 

 

allocateDirect()

이 메서드는 각 타입 버퍼 클래스에 정의된 것이 아니고, ByteBuffer 클래스에만 있다.

따라서 byte 타입이 아닌 다이렉트 버퍼를 만들고 싶다면

ByteBuffer 를 먼저 만들고, ByteBuffer 클래스에서 제공하는 asCharBuffer(), asIntBuffer() 같은 변환 메서드로 변환해야 한다.

 

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100);
// 100 개의 byte 값을 저장할 수 있는 다이렉트 바이트 버퍼

CharBuffer charBuffer = ByteBuffer.allocateDirect(100).asCharBuffer();
// 100 개의 byte 값을 저장할 수 있는 다이렉트 바이트 버퍼를 먼저 만들고
// CharBuffer 로 변환
// char 형은 2Bytes 이므로 50 개의 char 데이터를 저장할 수 있는 다이렉트 버퍼 생성

IntBuffer intBuffer = ByteBuffer.allocateDirect(100).asIntBuffer();
// 100 개의 byte 값을 저장할 수 있는 다이렉트 바이트 버퍼를 먼저 만들고
// IntBuffer 로 변환
// int 형은 4Bytes 이므로 25 개의 int 데이터를 저장할 수 있는 다이렉트 버퍼 생성

 

 

 

 

 

버퍼의 위치 속성

  • position : 현재 쓰거나 읽는 위치 인덱스값. 처음은 0부터 시작.
  • limit : 버퍼에서 읽거나 쓸 수 있는 한계 인덱스값. 최초 버퍼를 만들었을 때에는 capacity 와 같은 값을 가진다. 읽기를 시작할 때 flip() 메서드로 limit을 position 의 위치로 이동하고, position 은 0으로 바꾼다. 이후 position 을 limit 까지 순회하며 읽는다.
  • capacity : 버퍼의 최대 갯수를 말한다. 고정된 값이다.
  • mark : reset() 을 실행했을 때 돌아올 인덱스를 가진다. mark() 로 지정한다.

 

  • rewind() : limit 은 변하지 않고, position 을 다시 0으로 돌려서 읽은 값을 다시 읽을 수 있다.
  • clear() : 데이터는 삭제되지 않고, position 은 0 으로, capacity와 limit 은 최댓값으로 초기화된다.

각 위치 속성은 아래의 크기 순서를 무조건 만족한다.

0 <= mark <= position <= limit <= capacity

 

 

 

Buffer 클래스의 메서드

  • 버퍼 추상 클래스에서 제공하는 메서드와 각 타입 버퍼의 메서드가 따로 있다.

 

공통 메서드

  • 이 메서드들은 모든 버퍼 클래스의 조상인 추상 클래스 Buffer 에 있다.
  • 따라서 모든 버퍼 클래스들이 사용할 수 있다.

docs.oracle.com/javase/8/docs/api/

 

Java Platform SE 8

 

docs.oracle.com

 

 

데이터를 읽고 저장하는 메서드

  • 이 메서드들은 각 타입 버퍼 클래스에만 있다.
  • 버퍼에 데이터를 저장하는 put(), 버퍼에서 데이터를 읽어오는 get() 이 있다.
  • 이 메서드들은 다양하게 오버로딩되어 있는데, 절대적인 메서드와 상대적인 메서드로 나눌 수 있다.
  • 절대적 메서드 : 매개변수로 int 값인 인덱스를 넘겨서 position 에 상관없이 메서드를 읽거나 쓴다.
    • get(int idx), put(int idx)
  • 상대적 메서드 : 버퍼 내의 현재 위치 position 에서 데이터를 읽거나 쓴다.
    • get(), get(byte[] dst), putDouble(doueble value)

 

 

 

 

버퍼 관련 예외 종류

  • BufferOverflowException : position 이 limit 과 같은 상태에서 put() 으로 데이터를 쓰려할 떄 발생
  • BufferUnderflowException : position 이 limit 과 같을 때 get() 으로 데이터를 읽으려 할 때 발생
  • InvaildMarkException : mark 를 지정하지 않은 상태에서 reset() 을 호출했을 때 발생
  • ReadOnlyBufferException : 읽기 전용 버퍼에서 put() 으로 쓰기 시도시 발생

 

 

 

 

 

 

NIO 파일 입출력 - 파일 채널

  • java.nio.channels.FileChannel 클래스를 이용하면 파일 읽기와 쓰기가 가능하다.
  • 동기화 처리가 되어있기 때문에 Thread-safe 하다.

 

 

 

 

FileChannel 생성과 닫기.

  • java.io 의 FileInputStream, FIleOutputStream 의 getChannel() 을 이용할 수 도 있다.
  • FileChannel 클래스의 정적 메서드 open() 를 이용하면된다.
public static FileChannel open(Path path, OpenOption... options) { ... }

 

OpenOption 인터페이스를 상속한 StandardOpenOption enum의 상수들을 나열해서 인자로 넘기면 FileChannel 객체의 옵션을 부여할 수 있다.

 

 

다음은 java.nio.file 패키지의 StandardOpenOption enum 이다.

package java.nio.file;

public enum StandardOpenOption implements OpenOption {
    /**
     * 읽기 전용으로 열기
     */
    READ,

    /**
     * 쓰기 전용으로 열기
     */
    WRITE,

    /**
     * 파일 끝에 데이터를 추가 (WRITE, CREATE 와 함께 사용)
     */
    APPEND,

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

    /**
     * 파일이 없다면 새 파일로 생성.
     */
    CREATE,

    /**
     * 새 파일을 만든다. 이미 있으면 예외를 발생시킨다.
     */
    CREATE_NEW,

    /**
     * 파일 채널을 닫을때 파일을 삭제 (임시 파일용)
     */
    DELETE_ON_CLOSE,

    /**
     * Sparse 파일
     */
    SPARSE,

    /**
	 * 파일의 내용과 메타 데이터의 모든수정사항을 동기화해야 함.
     */
    SYNC,

    /**
     * 파일 내용의 모든 수정사항을 동기화해야 함
     */
    DSYNC;
}

 

 

예를 들어 파일을 생성하고 쓰기 전용으로 만들고 싶으면 다음과 같이 하면 된다.

 

public class CreatFileChannel {
    public static void main(String[] args) {
        try(FileChannel fileChannel = FileChannel.open(
            Paths.get("/Users/tomas/Documents/gitLocalRepository/white_ship_study/src/main/java/com/whiteship/white_ship_study/week13/abcd.txt"),
            StandardOpenOption.CREATE_NEW,
            StandardOpenOption.WRITE
        )) {
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

FileChannel 도 사용이 끝나면 close() 로 닫아주어야 하기 때문에 try-with-resource 로 쓰면 좋다.

객체를 생성하는 부분을 메서드로 빼면 더 깔끔해질 수 있을 것이다.

 

 

 

 

FileChannel 파일 읽기와 쓰기

  • FileChannel 의 read(), write() 를 사용할 수 있따.
  • write() 는 매개값으로 쓸 ByteBuffer 객체를 주면 된다.
  • wirte() 의 리턴값은 ByteBuffer 의 데이터 바이트로, 작성된 바이트이다.
int bytesCount = fileChannel.write(ByteBuffer 객체);

 

  • read() 는 매개값으로 크기를 설정한 ByteBuffer 객체를 주면 된다.
  • read() 의 리턴값은 읽어온 데이터의 바이트값이다.
  • 더이상 읽어올 데이터가 없으면 -1 을 반환한다.
int byteCount = fileChannel.read(ByteBuffer 객체);

 

 

 

 

 

 

 

파일 비동기 채널 - AsynchronousFileChannel

  • FileChannel 의 read() write() 는 파일 입출력동안 블로킹이 된다. 따라서 다른 작업을 하기 위해서는 별도의 스레드를 만들어서 작업해야 한다.
  • 작업의 수가 많으면 스레드의 수가 많아져 문제가 될 수 있어서 자바에서는 비동기 파일 채널을 따로 제공한다.
  • AsynchronousFileChannel 클래스.
  • 스레드 풀을 만들어서, 블로킹이 필요한 read(), write() 작업은 스레드 풀이 담당한다. write(), read() 메서드는 스레드풀에게 작업을 맡기고 바로 리턴한다.
  • 스레드 풀을 사용하면 과도한 스레드의 생성을 방지할 수 있다.
  • 스레드풀의 작업 스레드가 파일 입출력을 완료하게 되면 Callback() 가 자동으로 호출된다.

write(), read() 메서드를 호출하자 마자 return 을 하여 제어권을 넘겨주므로 논 블로킹이라 할 수 있다.

 

 

 

 

 

TCP 논블로킹 채널

  • read(), write(), accept() 같은 메서드는 블로킹이 되는 메서드들이다.
  • 따라서 ServerSocketChannel 과 연결된 SocketChannel 당 하나의 스레드가 할당되어야 하고, 과도한 스레드의 생성위험이 생긴다.
  • 따라서 블로킹 채널에서는 ExecuteorService 의 스레드 풀 방식을 사용한다.
  • 이와는 다른 방식으로 논블로킹 채널을 이용할 수 있다.

 

Selector

  • java.nio.channels 의 클래스
  • 논 블로킹 방식은 read(), write(), accept() 에 블로킹이 없다.
  • 클라이언트의 연결 요청이 없으면 accept() 는 바로 null 을 반환하고,  클라이언트가 데이터를 보내지 않으면 read() 는 즉시 0을 반환한다.
  • 소켓 통신에서는 클라이언트의 연결을 받아들이기 위해 다음과 같은 코드를 작성한다.
while(true) {
    SocketChannel socketChannel = serverChannel.accept();
    ...
}
  • 만약 사용자의 요청이 없다면, accept() 에서 null 을 반환하게 되고, while 문이 계~속 돌게 된다. 
  • 이는 CPU 자원이 과도하게 소비되는 문제점을 가진다.
  • 따라서 Selector 라는게 등장하였다.
  • 넌 블로킹 채널에 Selector 를 등록해 놓으면 클라이언트의 연결 요청이 들어오거나 데이터가 들어올 경우 채널이 Selector 에게 이를 알린다.
  • Selector 는 작업 스레드가 요청이 들어온 채널의 read() 나 accept() 를 바로 실행할 수 있게 한다.

 

논블로킹, Selector 작동 순서

  1. 채널은 Selector에 자신을 등록할 떄 작업 유형을 키(SelectorKey) 로 생성하고, Selector의 관심키셋에 저장시킨다.
  2. 클라이언트가 처리 요청을 하면
  3. Selector는 관심키셋에 등록된 키 중에서 작업 처리가 준비된 키를 선택된 키셋에 별도로 저장한다.
  4. 작업 스레드는 선택된 키셋에 있는 키를 하나씩 꺼내어 키와 연관된 채널 작업을 처리한다.
  5. 선택된 키셋에 있는 모든 키를 처리하면 선택된 키셋은 비워지고, Selector 는 다시 관심 키셋에서 작업 처리 준비가 완료된 키들을 선택된 키셋으로 옮긴다.

 

 

 

 

 

 

참고

victorydntmd.tistory.com/8

m.blog.naver.com/PostView.nhn?blogId=joebak&logNo=220063974083&proxyReferer=https:%2F%2Fwww.google.com%2F

 

이것이 자바다 - 신용권

 

 

 

 

'WhiteShip Java Study : 자바 처음부터 멀리까지' 카테고리의 다른 글

람다식(feat. 익명 구현 클래스 vs 람다식)  (0) 2021.03.07
제네릭  (0) 2021.02.24
I/O  (0) 2021.02.17
ServiceLoader  (0) 2021.02.14
Annotation  (0) 2021.02.13