Java NIO는 표준 IO와 다른 IO 작동 방식을 제공합니다.
채널 및 버퍼: 표준 IO는 바이트 스트림 및 문자 스트림을 기반으로 작동하는 반면, NIO는 채널(Channel) 및 버퍼(Buffer)를 기반으로 작동합니다. 데이터는 항상 채널에서 버퍼 영역으로 읽혀집니다. 채널.
비동기식 IO: Java NIO를 사용하면 IO를 비동기식으로 사용할 수 있습니다. 예를 들어 스레드가 채널에서 버퍼로 데이터를 읽어도 스레드는 여전히 다른 작업을 수행할 수 있습니다. 데이터가 버퍼에 기록되면 스레드는 계속해서 데이터를 처리할 수 있습니다. 버퍼에서 채널에 쓰는 것은 비슷합니다.
선택기: Java NIO는 여러 채널에서 이벤트(예: 연결 열기, 데이터 도착)를 수신하는 데 사용되는 선택기 개념을 도입합니다. 따라서 단일 스레드는 여러 데이터 채널을 수신할 수 있습니다.
Java NIO 관련 지식을 자세하게 소개하겠습니다.
자바 NIO 개요
Java NIO는 다음과 같은 핵심 부분으로 구성됩니다.
채널
버퍼
선택기
Java NIO에는 다른 많은 클래스와 구성 요소가 있지만 내 생각에는 Channel, Buffer 및 Selector가 핵심 API를 구성합니다. Pipe 및 FileLock과 같은 다른 구성 요소는 세 가지 핵심 구성 요소와 함께 사용되는 유틸리티 클래스일 뿐입니다. 따라서 이 개요에서는 이 세 가지 구성 요소에 중점을 둘 것입니다. 다른 구성 요소는 별도의 장에서 다룹니다.
채널 및 버퍼
기본적으로 NIO의 모든 IO는 채널에서 시작됩니다. 채널은 스트림과 약간 비슷합니다. 데이터는 채널에서 버퍼로 읽혀지거나 버퍼에서 채널로 쓰여질 수 있습니다. 다음은 예시입니다.
채널과 버퍼에는 여러 유형이 있습니다. 다음은 JAVA NIO의 일부 주요 채널 구현입니다.
파일채널
데이터그램채널
소켓채널
서버소켓채널
보시다시피 이러한 채널은 UDP 및 TCP 네트워크 IO는 물론 파일 IO도 처리합니다.
이러한 클래스와 함께 몇 가지 흥미로운 인터페이스가 있지만 단순성을 위해 개요에서는 언급하지 않았습니다. 관련이 있는 이 튜토리얼의 다른 장에서 이에 대해 설명하겠습니다.
다음은 Java NIO의 주요 버퍼 구현입니다.
바이트버퍼
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
이러한 버퍼는 IO를 통해 보낼 수 있는 기본 데이터 유형(byte, short, int, long, float, double 및 char)을 다룹니다.
Java NIO에는 메모리 매핑된 파일을 나타내는 데 사용되는 Mappyteuffer도 있습니다. 개요에서는 설명하지 않겠습니다.
선택자
선택기를 사용하면 단일 스레드가 여러 채널을 처리할 수 있습니다. 애플리케이션이 여러 연결(채널)을 열지만 각 연결의 트래픽이 매우 낮은 경우 Selector를 사용하는 것이 편리할 수 있습니다. 예를 들어 채팅 서버에서.
다음은 단일 스레드에서 3개 채널을 처리하기 위해 선택기를 사용하는 방법을 보여줍니다.
선택기를 사용하려면 선택기에 채널을 등록한 다음 해당 select() 메서드를 호출해야 합니다. 이 메소드는 등록된 채널에 이벤트가 준비될 때까지 차단됩니다. 이 메서드가 반환되면 스레드는 이러한 이벤트를 처리할 수 있습니다. 이벤트의 예로는 새 연결 수신, 데이터 수신 등이 있습니다.
자바 NIO 대 IO
(이 부분의 원본 주소, 저자: Jakob Jenkov, 번역자: Guo Lei, 교정자: Fang Tengfei)
Java NIO 및 IO API에 대해 배운 후 즉시 다음 질문이 떠올랐습니다.
인용하다
언제 IO를 사용해야 하며 언제 NIO를 사용해야 합니까? 이 기사에서는 Java NIO와 IO의 차이점, 사용 시나리오 및 코드 디자인에 미치는 영향을 명확하게 설명하려고 합니다.
Java NIO와 IO의 주요 차이점
다음 표에는 Java NIO와 IO의 주요 차이점이 요약되어 있습니다. 표의 각 부분에 대한 차이점을 더 자세히 설명하겠습니다.
아이오니오
스트림 지향 버퍼 지향
블로킹 IO 비블로킹 IO
선택기
스트림 지향 및 버퍼 지향
Java NIO와 IO의 첫 번째 가장 큰 차이점은 IO는 스트림 지향이고 NIO는 버퍼 지향이라는 것입니다. Java IO는 스트림 지향적입니다. 즉, 한 번에 하나 이상의 바이트를 스트림에서 읽고 모든 바이트를 읽을 때까지 어디에도 캐시되지 않습니다. 또한 스트림의 데이터를 앞이나 뒤로 이동할 수 없습니다. 스트림에서 읽은 데이터를 앞뒤로 이동해야 하는 경우 먼저 버퍼에 캐시해야 합니다. Java NIO의 버퍼 지향 접근 방식은 약간 다릅니다. 데이터는 나중에 처리할 버퍼로 읽혀지며 필요에 따라 버퍼에서 앞뒤로 이동합니다. 이는 처리의 유연성을 증가시킵니다. 그러나 버퍼에 처리해야 하는 모든 데이터가 포함되어 있는지도 확인해야 합니다. 또한 더 많은 데이터를 버퍼로 읽어 들일수록 버퍼의 처리되지 않은 데이터를 덮어쓰지 않도록 해야 합니다.
차단 및 비차단 IO
Java IO의 다양한 스트림이 차단됩니다. 즉, 스레드가 read() 또는 write()를 호출하면 일부 데이터를 읽거나 데이터가 완전히 기록될 때까지 스레드가 차단됩니다. 이 기간 동안 스레드는 다른 작업을 수행할 수 없습니다. Java NIO의 비차단 모드를 사용하면 스레드가 특정 채널에서 데이터를 읽으라는 요청을 보낼 수 있지만 현재 사용 가능한 데이터만 얻을 수 있으며, 현재 사용할 수 있는 데이터가 없으면 아무 것도 얻을 수 없습니다. 스레드를 차단된 상태로 유지하는 대신 스레드는 데이터를 읽을 수 있게 될 때까지 계속해서 다른 작업을 수행할 수 있습니다. 비차단 쓰기에도 마찬가지입니다. 스레드는 일부 데이터를 채널에 쓰도록 요청하지만 데이터가 완전히 기록될 때까지 기다릴 필요는 없습니다. 그 동안 스레드는 다른 작업을 수행할 수 있습니다. 스레드는 일반적으로 비차단 IO의 유휴 시간을 사용하여 다른 채널에서 IO 작업을 수행하므로 이제 단일 스레드가 여러 입력 및 출력 채널을 관리할 수 있습니다.
선택기
Java NIO의 선택기를 사용하면 단일 스레드가 여러 입력 채널을 모니터링할 수 있습니다. 선택기를 사용하여 여러 채널을 등록한 다음 별도의 스레드를 사용하여 채널을 "선택"할 수 있습니다. 이러한 채널에는 이미 처리할 수 있는 입력이 있습니다. 글을 쓸 준비가 되었습니다. 이 선택 메커니즘을 사용하면 단일 스레드가 여러 채널을 쉽게 관리할 수 있습니다.
NIO 및 IO가 애플리케이션 설계에 미치는 영향
IO 도구 상자를 선택하든 NIO 도구 상자를 선택하든 애플리케이션 설계에 영향을 미칠 수 있는 여러 측면이 있습니다.
NIO 또는 IO 클래스에 대한 API 호출.
데이터 처리.
데이터를 처리하는 데 사용되는 스레드 수입니다.
API 호출
물론 NIO를 사용할 때의 API 호출은 IO를 사용할 때와 다르게 보이지만, 이는 단순히 InputStream에서 바이트 단위로 읽는 대신 데이터를 먼저 버퍼로 읽어온 다음 처리해야 하기 때문에 예상치 못한 일이 아닙니다.
데이터 처리
IO 설계와 비교하여 순수 NIO 설계를 사용하면 데이터 처리에도 영향을 미칩니다.
IO 설계에서는 InputStream 또는 Reader에서 바이트 단위로 데이터를 읽습니다. 예를 들어 라인 기반 텍스트 데이터 스트림을 처리한다고 가정해 보겠습니다.
다음과 같이 코드 코드를 복사합니다.
이름: 안나
나이: 25
이메일: [email protected]
전화: 1234567890
텍스트 줄의 스트림은 다음과 같이 처리될 수 있습니다.
다음과 같이 코드 코드를 복사합니다.
InputStream input = … ; // 클라이언트 소켓에서 InputStream을 가져옵니다.
BufferedReader 리더 = new BufferedReader(new InputStreamReader(input));
문자열 nameLine = reader.readLine();
문자열 ageLine = reader.readLine();
문자열 emailLine= reader.readLine();
문자열 PhoneLine= reader.readLine();
처리 상태는 프로그램이 실행된 기간에 따라 결정됩니다. 즉, reader.readLine() 메서드가 반환되면 텍스트 줄을 읽었다는 것을 확실히 알 수 있습니다. 이것이 readline()이 전체 줄을 읽을 때까지 차단되는 이유입니다. 또한 이 줄에 이름이 포함되어 있다는 것도 알고 있습니다. 마찬가지로 두 번째 readline() 호출이 반환되면 이 줄에 연령 등이 포함된다는 것도 알 수 있습니다. 보시다시피 이 핸들러는 새 데이터를 읽을 때만 실행되며 각 단계의 데이터가 무엇인지 알고 있습니다. 실행 중인 스레드가 읽은 데이터 중 일부를 처리한 후에는 데이터(대부분)를 롤백하지 않습니다. 다음 그림도 이 원칙을 보여줍니다.
차단된 스트림에서 데이터 읽기
NIO 구현은 다르지만 다음은 간단한 예입니다.
다음과 같이 코드 코드를 복사합니다.
ByteBuffer 버퍼 = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(버퍼);
채널에서 ByteBuffer로 바이트를 읽는 두 번째 줄을 참고하세요. 이 메서드 호출이 반환되면 필요한 모든 데이터가 버퍼에 있는지 알 수 없습니다. 여러분이 알고 있는 것은 버퍼에 일부 바이트가 포함되어 있어 처리가 약간 어렵다는 것뿐입니다.
첫 번째 읽기(버퍼) 호출 후 버퍼에 읽혀진 데이터가 "Name: An"과 같이 절반 라인에 불과하다고 가정합니다. 데이터를 처리할 수 있습니까? 당연히 그렇지 않습니다. 전체 데이터 행을 캐시로 읽어올 때까지 기다려야 합니다. 그 전에는 데이터 처리가 의미가 없습니다.
그렇다면 버퍼에 처리하기에 충분한 데이터가 포함되어 있는지 어떻게 알 수 있습니까? 글쎄, 당신은 모른다. 검색된 메서드는 버퍼의 데이터만 볼 수 있습니다. 결과적으로 모든 데이터가 버퍼에 있다는 것을 알기 전에 버퍼의 데이터를 여러 번 확인해야 합니다. 이는 비효율적일 뿐만 아니라 프로그래밍 솔루션을 복잡하게 만들 수도 있습니다. 예를 들어:
다음과 같이 코드 코드를 복사합니다.
ByteBuffer 버퍼 = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(버퍼);
while(!bufferFull(bytesRead) ) {
bytesRead = inChannel.read(버퍼);
}
bufferFull() 메서드는 버퍼로 읽혀진 데이터의 양을 추적하고 버퍼가 가득 찼는지 여부에 따라 true 또는 false를 반환해야 합니다. 즉, 버퍼가 처리될 준비가 되면 버퍼가 가득 찬 것입니다.
bufferFull() 메서드는 버퍼를 스캔하지만 bufferFull() 메서드가 호출되기 전과 동일한 상태를 유지해야 합니다. 그렇지 않으면 버퍼로 읽어온 다음 데이터가 올바른 위치로 읽혀지지 않을 수 있습니다. 이것은 불가능합니다. 그러나 알아야 할 또 다른 문제입니다.
버퍼가 가득 차면 처리가 가능합니다. 작동하지 않고 실제 사례에 적합한 경우 일부를 처리할 수 있습니다. 그러나 많은 경우에는 그렇지 않습니다. 다음 그림은 "버퍼 데이터 사이클 준비"를 보여줍니다.
모든 데이터가 버퍼로 읽어질 때까지 채널에서 데이터를 읽습니다.
요약
NIO를 사용하면 단일 스레드(또는 몇 개)를 사용하여 여러 채널(네트워크 연결 또는 파일)을 관리할 수 있지만, 데이터 구문 분석이 차단 스트림에서 읽는 것보다 더 복잡할 수 있다는 단점이 있습니다.
채팅 서버와 같이 매번 소량의 데이터만 전송하는 동시에 열리는 수천 개의 연결을 관리해야 하는 경우 NIO를 구현하는 서버가 유리할 수 있습니다. 마찬가지로, P2P 네트워크와 같이 다른 컴퓨터에 대한 열린 연결을 많이 유지해야 하는 경우 별도의 스레드를 사용하여 모든 아웃바운드 연결을 관리하는 것이 유리할 수 있습니다. 하나의 스레드에서 여러 연결의 설계 방식은 아래 그림에 나와 있습니다.
단일 스레드가 여러 연결을 관리합니다.
매우 높은 대역폭을 사용하여 소수의 연결이 있고 동시에 많은 양의 데이터를 전송하는 경우 일반적인 IO 서버 구현이 적합할 수 있습니다. 다음 그림은 일반적인 IO 서버 설계를 보여줍니다.
일반적인 IO 서버 설계:
연결은 스레드에 의해 처리됩니다.
채널
Java NIO 채널은 스트림과 유사하지만 약간 다릅니다.
채널에서 데이터를 읽을 수 있고 채널에 데이터를 쓸 수 있습니다. 그러나 읽기 및 쓰기 스트림은 일반적으로 단방향입니다.
채널은 비동기적으로 읽고 쓸 수 있습니다.
채널의 데이터는 먼저 버퍼에서 읽혀지거나 항상 버퍼에서 쓰여져야 합니다.
위에서 언급한 것처럼 데이터는 채널에서 버퍼로 읽히고 데이터는 버퍼에서 채널로 기록됩니다. 아래와 같이:
채널 구현
다음은 Java NIO에서 가장 중요한 채널의 구현입니다.
FileChannel: 파일에서 데이터를 읽고 씁니다.
DatagramChannel: UDP를 통해 네트워크의 데이터를 읽고 쓸 수 있습니다.
SocketChannel: TCP를 통해 네트워크에서 데이터를 읽고 쓸 수 있습니다.
ServerSocketChannel: 웹 서버와 같이 들어오는 TCP 연결을 모니터링할 수 있습니다. 각각의 새로운 수신 연결에 대해 SocketChannel이 생성됩니다.
기본 채널 예시
다음은 FileChannel을 사용하여 데이터를 버퍼로 읽는 예입니다.
다음과 같이 코드 코드를 복사합니다.
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {
System.out.println("읽기 " + bytesRead);
buf.flip();
동안(buf.hasRemaining()){
System.out.print((char) buf.get());
}
buf.clear();
bytesRead = inChannel.read(buf);
}
aFile.close();
buf.flip()에 대한 호출은 먼저 데이터를 Buffer로 읽은 다음 Buffer를 반전시킨 다음 Buffer에서 데이터를 읽습니다. 다음 섹션에서는 Buffer에 대해 더 자세히 설명하겠습니다.
완충기
Java NIO의 버퍼는 NIO 채널과 상호 작용하는 데 사용됩니다. 아시다시피 데이터는 채널에서 버퍼로 읽혀지고 버퍼에서 채널로 쓰여집니다.
버퍼는 본질적으로 데이터를 쓸 수 있고 데이터를 읽을 수 있는 메모리 블록입니다. 이 메모리는 NIO 버퍼 개체로 패키지되어 있으며 이 메모리에 편리하게 액세스할 수 있는 일련의 메서드를 제공합니다.
버퍼의 기본 사용법
버퍼를 사용하여 데이터를 읽고 쓰는 작업은 일반적으로 다음 네 단계를 따릅니다.
버퍼에 데이터 쓰기
Flip() 메서드 호출
버퍼에서 데이터 읽기
Clear() 메서드 또는 Compact() 메서드를 호출합니다.
데이터가 버퍼에 기록되면 버퍼는 기록된 데이터의 양을 기록합니다. 데이터를 읽으려면 Flip() 메서드를 통해 버퍼를 쓰기 모드에서 읽기 모드로 전환해야 합니다. 읽기 모드에서는 이전에 버퍼에 기록된 모든 데이터를 읽을 수 있습니다.
모든 데이터를 읽은 후에는 다시 쓸 수 있도록 버퍼를 지워야 합니다. 버퍼를 지우는 방법에는 두 가지가 있습니다. 즉,clear() 또는 Compact() 메서드를 호출하는 것입니다. Clear() 메서드는 전체 버퍼를 지웁니다. Compact() 메소드는 읽은 데이터만 지웁니다. 읽지 않은 데이터는 버퍼의 시작 부분으로 이동되고 새로 작성된 데이터는 버퍼에서 읽지 않은 데이터 뒤에 배치됩니다.
다음은 Buffer를 사용하는 예입니다.
다음과 같이 코드 코드를 복사합니다.
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
//48바이트 용량의 버퍼 생성
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf); //버퍼에 읽습니다.
while (bytesRead != -1) {
buf.flip();//버퍼를 읽을 수 있도록 준비합니다.
동안(buf.hasRemaining()){
System.out.print((char) buf.get()); // 한 번에 1바이트씩 읽습니다.
}
buf.clear(); //쓰기용 버퍼를 준비합니다.
bytesRead = inChannel.read(buf);
}
aFile.close();
버퍼의 용량, 위치 및 한도
버퍼는 본질적으로 데이터를 쓸 수 있고 데이터를 읽을 수 있는 메모리 블록입니다. 이 메모리는 NIO 버퍼 개체로 패키지되어 있으며 이 메모리에 편리하게 액세스할 수 있는 일련의 메서드를 제공합니다.
Buffer의 작동 방식을 이해하려면 다음 세 가지 속성을 잘 알아야 합니다.
용량
위치
한계
위치와 한계의 의미는 버퍼가 읽기 모드인지 쓰기 모드인지에 따라 달라집니다. 버퍼가 어떤 모드에 있든 용량의 의미는 항상 동일합니다.
다음은 읽기 및 쓰기 모드에서의 용량, 위치, 제한에 대한 설명이며, 그림과 함께 자세한 설명이 나와 있습니다.
용량
메모리 블록으로서 Buffer에는 "용량"이라고도 하는 고정된 크기 값이 있습니다. byte, long, char 및 기타 유형의 용량만 쓸 수 있습니다. 버퍼가 가득 차면 데이터 쓰기를 계속하기 전에 데이터를 읽거나 데이터를 지워 버퍼를 비워야 합니다.
위치
Buffer에 데이터를 쓸 때 position은 현재 위치를 나타냅니다. 초기 위치값은 0이다. Buffer에 Byte, Long 등의 데이터를 쓰면 데이터를 삽입할 수 있는 다음 Buffer 단위로 위치가 앞으로 이동한다. 최대 위치는 용량 1이 될 수 있습니다.
데이터를 읽을 때 특정 위치에서도 읽혀집니다. 버퍼를 쓰기 모드에서 읽기 모드로 전환하면 위치가 0으로 재설정됩니다. 버퍼 위치에서 데이터를 읽으면 해당 위치는 읽을 수 있는 다음 위치로 이동합니다.
한계
쓰기 모드에서 버퍼의 한계는 버퍼에 쓸 수 있는 최대 데이터 양을 나타냅니다. 쓰기 모드에서 제한은 버퍼의 용량과 동일합니다.
버퍼를 읽기 모드로 전환할 때 제한은 읽을 수 있는 최대 데이터 양을 나타냅니다. 따라서 버퍼를 읽기 모드로 전환하면 쓰기 모드의 위치 값으로 제한이 설정됩니다. 즉, 이전에 쓴 모든 데이터를 읽을 수 있습니다. (기록된 데이터 수에 제한이 설정되어 있으며 이 값은 쓰기 모드에서의 위치입니다.)
버퍼 유형
Java NIO에는 다음과 같은 버퍼 유형이 있습니다.
바이트버퍼
MappedByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
보시다시피 이러한 버퍼 유형은 다양한 데이터 유형을 나타냅니다. 즉, 버퍼의 바이트는 char, short, int, long, float 또는 double 유형을 통해 조작될 수 있습니다.
MappedByteBuffer는 약간 특별하며 해당 장에서 논의됩니다.
버퍼 할당
Buffer 객체를 얻으려면 먼저 할당해야 합니다. 모든 Buffer 클래스에는 할당 메소드가 있습니다. 다음은 48바이트 용량의 ByteBuffer를 할당한 예이다.
다음과 같이 코드 코드를 복사합니다.
ByteBuffer buf = ByteBuffer.allocate(48);
이는 1024자를 저장할 수 있는 CharBuffer를 할당합니다.
다음과 같이 코드 코드를 복사합니다.
CharBuffer buf = CharBuffer.allocate(1024);
버퍼에 데이터 쓰기
버퍼에 데이터를 쓰는 방법에는 두 가지가 있습니다.
채널에서 버퍼로 쓰기.
Buffer의 put() 메소드를 통해 Buffer에 씁니다.
채널에서 버퍼로 쓰기의 예
다음과 같이 코드 코드를 복사합니다.
int bytesRead = inChannel.read(buf); //버퍼에 읽습니다.
put 메소드를 통해 Buffer 작성 예:
다음과 같이 코드 코드를 복사합니다.
buf.put(127);
다양한 버전의 put 메소드가 있어 다양한 방식으로 Buffer에 데이터를 쓸 수 있습니다. 예를 들어, 지정된 위치에 쓰거나 버퍼에 바이트 배열을 씁니다. Buffer 구현에 대한 자세한 내용은 JavaDoc을 참조하세요.
플립() 메서드
Flip 메서드는 버퍼를 쓰기 모드에서 읽기 모드로 전환합니다. Flip() 메서드를 호출하면 위치가 다시 0으로 설정되고 제한이 이전 위치의 값으로 설정됩니다.
즉, position은 이제 읽기 위치를 표시하는 데 사용되며,limit는 이전에 기록된 바이트, 문자 등의 수, 즉 현재 읽을 수 있는 바이트, 문자 등의 수를 나타냅니다.
버퍼에서 데이터 읽기
버퍼에서 데이터를 읽는 방법에는 두 가지가 있습니다.
버퍼에서 채널로 데이터를 읽습니다.
버퍼에서 데이터를 읽으려면 get() 메소드를 사용하십시오.
버퍼에서 채널로 데이터를 읽는 예:
다음과 같이 코드 코드를 복사합니다.
//버퍼에서 채널로 읽어옵니다.
int bytesWritten = inChannel.write(buf);
get() 메소드를 사용하여 Buffer에서 데이터를 읽는 예
다음과 같이 코드 코드를 복사합니다.
바이트 aByte = buf.get();
get 메소드에는 다양한 버전이 있으며 이를 통해 다양한 방법으로 Buffer에서 데이터를 읽을 수 있습니다. 예를 들어 지정된 위치에서 읽거나 버퍼의 데이터를 바이트 배열로 읽습니다. Buffer 구현에 대한 자세한 내용은 JavaDoc을 참조하세요.
rewind() 메서드
Buffer.rewind()는 위치를 다시 0으로 설정하므로 버퍼의 모든 데이터를 다시 읽을 수 있습니다. 제한은 변경되지 않고 여전히 버퍼에서 읽을 수 있는 요소(바이트, 문자 등) 수를 나타냅니다.
Clear() 및 Compact() 메서드
버퍼의 데이터를 읽은 후에는 버퍼에 다시 쓸 준비가 되어야 합니다. 이는clear() 또는 Compact() 메소드를 통해 수행될 수 있습니다.
Clear() 메서드가 호출되면 위치는 다시 0으로 설정되고 제한은 용량 값으로 설정됩니다. 즉, 버퍼가 지워집니다. 버퍼의 데이터는 지워지지 않지만 이러한 표시는 버퍼에 데이터 쓰기를 시작할 위치를 알려줍니다.
버퍼에 읽지 않은 데이터가 있는 경우 Clear() 메서드를 호출하면 데이터가 "잊혀집니다". 즉, 읽은 데이터와 읽지 않은 데이터를 알려주는 마커가 더 이상 존재하지 않는다는 의미입니다.
버퍼에 아직 읽지 않은 데이터가 있고 해당 데이터가 나중에 필요하지만 일부 데이터를 먼저 쓰고 싶다면 Compact() 메서드를 사용하십시오.
Compact() 메서드는 읽지 않은 모든 데이터를 버퍼의 시작 부분에 복사합니다. 그런 다음 읽지 않은 마지막 요소 바로 뒤에 위치를 설정합니다. 한계 속성은 여전히 clear() 메소드와 같이 용량으로 설정됩니다. 이제 버퍼에 데이터를 쓸 준비가 되었지만 읽지 않은 데이터는 덮어쓰지 않습니다.
mark() 및 Reset() 메서드
Buffer.mark() 메서드를 호출하면 버퍼의 특정 위치를 표시할 수 있습니다. 나중에 Buffer.reset() 메서드를 호출하여 이 위치로 복원할 수 있습니다. 예를 들어:
다음과 같이 코드 코드를 복사합니다.
buffer.mark();
//예를 들어 구문 분석 중에 buffer.get()을 몇 번 호출합니다.
buffer.reset();//위치를 다시 마크로 설정합니다.
equals() 및 CompareTo() 메서드
두 개의 버퍼에 대해 equals() 및 CompareTo() 메서드를 사용할 수 있습니다.
같음()
다음 조건이 충족되면 두 버퍼가 동일하다는 의미입니다.
동일한 유형(byte, char, int 등)을 갖습니다.
Buffer에 남은 바이트, 문자 등의 수는 동일합니다.
버퍼에 남아 있는 모든 바이트, 문자 등은 동일합니다.
보시다시피 같음은 버퍼의 모든 요소가 아닌 일부만 비교합니다. 실제로는 버퍼의 나머지 요소만 비교합니다.
CompareTo() 메서드
CompareTo() 메서드는 두 버퍼의 나머지 요소(바이트, 문자 등)를 비교합니다. 다음 조건이 충족되면 한 버퍼는 다른 버퍼보다 "작은" 것으로 간주됩니다.
첫 번째 같지 않은 요소는 다른 버퍼의 해당 요소보다 작습니다.
모든 요소는 동일하지만 첫 번째 버퍼는 다른 버퍼보다 먼저 소진됩니다(첫 번째 버퍼는 다른 버퍼보다 적은 수의 요소를 가짐).
(주석: 나머지 요소는 위치부터 한계까지의 요소입니다)
분산/수집
(이 부분의 원본 주소, 저자: Jakob Jenkov, 번역자: Guo Lei)
Java NIO는 분산/수집을 지원하기 시작합니다. 분산/수집은 채널에서 읽거나 쓰는 작업을 설명하는 데 사용됩니다(역자 주: 채널은 중국어로 채널로 번역되는 경우가 많습니다).
채널에서 분산 읽기는 읽기 작업 중에 읽기 데이터를 여러 버퍼에 쓰는 것을 의미합니다. 따라서 채널은 채널에서 읽은 데이터를 여러 버퍼로 "분산"합니다.
채널을 수집하고 쓴다는 것은 쓰기 작업 중에 여러 버퍼의 데이터를 동일한 채널에 쓰는 것을 의미합니다. 따라서 채널은 여러 버퍼의 데이터를 "수집"하여 채널로 보냅니다.
분산/수집은 전송된 데이터를 별도로 처리해야 하는 상황에서 자주 사용됩니다. 예를 들어 메시지 헤더와 메시지 본문으로 구성된 메시지를 전송할 때 메시지 본문과 메시지 헤더를 서로 다른 버퍼에 분산시킬 수 있습니다. 메시지 헤더와 메시지 본문을 편리하게 처리할 수 있습니다.
산란 읽기
분산 읽기는 한 채널의 데이터를 여러 버퍼로 읽는 것을 의미합니다. 아래 그림에 설명된 대로:
코드 예시는 다음과 같습니다.
다음과 같이 코드 코드를 복사합니다.
ByteBuffer 헤더 = ByteBuffer.allocate(128);
ByteBuffer 본문 = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { 헤더, 본문 };
채널.읽기(bufferArray);
버퍼가 먼저 배열에 삽입된 다음 배열이 채널.read()에 대한 입력 매개변수로 사용된다는 점에 유의하세요. read() 메소드는 채널에서 읽은 데이터를 배열의 버퍼 순서대로 버퍼에 씁니다. 한 버퍼가 채워지면 채널이 다른 버퍼에 씁니다.
분산 읽기는 다음 버퍼로 이동하기 전에 현재 버퍼를 채워야 하며 이는 동적 메시지에 적합하지 않음을 의미합니다(번역가 참고 사항: 메시지 크기는 고정되지 않음). 즉, 메시지 헤더와 메시지 본문이 있는 경우 분산 읽기가 제대로 작동하려면 메시지 헤더를 완전히 채워야 합니다(예: 128바이트).
쓰기 수집
쓰기 수집은 데이터가 여러 버퍼에서 동일한 채널로 기록되는 것을 의미합니다. 아래 그림에 설명된 대로:
코드 예시는 다음과 같습니다.
다음과 같이 코드 코드를 복사합니다.
ByteBuffer 헤더 = ByteBuffer.allocate(128);
ByteBuffer 본문 = ByteBuffer.allocate(1024);
//버퍼에 데이터 쓰기
ByteBuffer[] bufferArray = { 헤더, 본문 };
채널.쓰기(bufferArray);
버퍼 배열은 write() 메소드의 입력 매개변수입니다. write() 메소드는 배열의 버퍼 순서대로 채널에 데이터를 씁니다. 위치와 한계 사이의 데이터만 기록됩니다. 따라서 버퍼의 용량이 128바이트이지만 58바이트의 데이터만 포함하는 경우 58바이트의 데이터가 채널에 기록됩니다. 따라서 Scattering Reads와 달리 Gathering Writes는 동적 메시지를 더 잘 처리할 수 있습니다.
채널 간 데이터 전송
(이 부분의 원본 주소, 저자: Jakob Jenkov, 번역자: Guo Lei, 교정자: Zhou Tai)
Java NIO에서 두 채널 중 하나가 FileChannel인 경우 한 채널(역자 주: 채널은 종종 중국어로 채널로 번역됨)에서 다른 채널로 데이터를 직접 전송할 수 있습니다.
전송에서()
FileChannel의 transferFrom() 메소드는 소스 채널에서 FileChannel로 데이터를 전송할 수 있습니다(번역자 참고 사항: 이 메소드는 주어진 읽기 가능한 바이트 채널에서 이 채널의 파일로 바이트를 전송하는 것으로 JDK 문서에 설명되어 있습니다.). 간단한 예는 다음과 같습니다.
다음과 같이 코드 코드를 복사합니다.
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
롱 포지션 = 0;
긴 개수 = fromChannel.size();
toChannel.transferFrom(위치, 개수, fromChannel);
메소드의 입력 매개변수 position은 대상 파일에 데이터를 쓰는 위치부터 시작함을 나타내고, count는 전송된 최대 바이트 수를 나타냅니다. 소스 채널의 남은 공간이 count 바이트 미만인 경우 전송된 바이트 수는 요청된 바이트 수보다 적습니다.
또한 SoketChannel 구현에서 SocketChannel은 현재 준비된 데이터(count 바이트보다 작을 수 있음)만 전송한다는 점에 유의해야 합니다. 따라서 SocketChannel은 요청된 모든 데이터(바이트 수)를 FileChannel로 전송하지 못할 수 있습니다.
전송 대상()
transferTo() 메서드는 FileChannel에서 다른 채널로 데이터를 전송합니다. 간단한 예는 다음과 같습니다.
다음과 같이 코드 코드를 복사합니다.
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
롱 포지션 = 0;
긴 개수 = fromChannel.size();
fromChannel.transferTo(위치, 개수, toChannel);
이 예가 이전 예와 특히 유사하다는 것을 알았습니까? 메서드를 호출하는 FileChannel 개체가 다르다는 점을 제외하면 다른 모든 것은 동일합니다.
위에서 언급한 SocketChannel 문제는 transferTo() 메서드에도 존재합니다. SocketChannel은 대상 버퍼가 채워질 때까지 계속해서 데이터를 전송합니다.
선택자
(이 섹션의 원문 링크, 저자: Jakob Jenkov, 번역자: Langjiv, 교정자: Ding Yi)
선택기는 하나 이상의 NIO 채널을 감지하고 채널이 읽기 및 쓰기와 같은 이벤트에 대해 준비가 되었는지 여부를 알 수 있는 Java NIO의 구성 요소입니다. 이러한 방식으로 단일 스레드는 여러 채널을 관리하여 여러 네트워크 연결을 관리할 수 있습니다.
(1) 선택기를 사용하는 이유는 무엇입니까?
여러 채널을 처리하기 위해 단일 스레드만 사용하는 이점은 채널을 처리하는 데 필요한 스레드 수가 적다는 것입니다. 실제로 하나의 스레드만 사용하여 모든 채널을 처리하는 것이 가능합니다. 운영 체제의 경우 스레드 간 컨텍스트 전환은 매우 비용이 많이 들고 각 스레드는 일부 시스템 리소스(예: 메모리)를 차지합니다. 따라서 사용되는 스레드 수가 적을수록 좋습니다.
그러나 최신 운영 체제와 CPU는 멀티태스킹 성능이 점점 더 좋아지고 있으므로 멀티스레딩의 오버헤드는 시간이 지남에 따라 점점 작아진다는 점을 명심하세요. 실제로 CPU에 다중 코어가 있는 경우 멀티태스킹을 사용하지 않는 것은 CPU 성능을 낭비하는 것일 수 있습니다. 어쨌든, 그 디자인에 대한 논의는 다른 글에서 다루어야 할 것 같습니다. 여기서는 Selector를 사용하여 여러 채널을 처리할 수 있다는 점만 알아도 충분합니다.
다음은 선택기를 사용하여 세 개의 채널을 처리하는 단일 스레드의 예제 다이어그램입니다.
(2)선택기 생성
다음과 같이 Selector.open() 메서드를 호출하여 선택기를 만듭니다.
다음과 같이 코드 코드를 복사합니다.
선택기 선택기 = Selector.open();
(3) Selector에 채널 등록
채널과 셀렉터를 함께 사용하기 위해서는 해당 채널이 셀렉터에 등록되어 있어야 합니다. 이는 다음과 같이 SelectableChannel.register() 메서드를 통해 수행됩니다.
다음과 같이 코드 코드를 복사합니다.
채널.configureBlocking(false);
SelectionKey 키 = 채널.레지스터(선택기,
선택키.OP_READ);
선택기와 함께 사용할 경우 채널은 비차단 모드에 있어야 합니다. 이는 FileChannel을 비차단 모드로 전환할 수 없기 때문에 선택기와 함께 FileChannel을 사용할 수 없음을 의미합니다. 소켓 채널은 괜찮습니다.
Register() 메소드의 두 번째 매개변수에 주목하세요. 이는 "관심 컬렉션"으로, 선택기를 통해 채널을 청취할 때 어떤 이벤트에 관심이 있는지를 의미합니다. 들을 수 있는 이벤트에는 네 가지 유형이 있습니다.
연결하다
수용하다
읽다
쓰다
이벤트를 트리거하는 채널은 이벤트가 준비되었음을 의미합니다. 따라서 다른 서버에 성공적으로 연결되는 채널을 "연결 준비"라고 합니다. 서버 소켓 채널은 들어오는 연결을 수신할 준비가 되었을 때 "수신 준비" 상태라고 합니다. 읽을 데이터가 있는 채널을 "읽기 준비" 상태라고 합니다. 데이터 쓰기를 대기 중인 채널은 "쓰기 준비" 상태라고 할 수 있습니다.
이 네 가지 이벤트는 SelectionKey의 네 가지 상수로 표시됩니다.
SelectionKey.OP_CONNECT
SelectionKey.OP_ACCEPT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
둘 이상의 이벤트에 관심이 있는 경우 비트별 OR 연산자를 사용하여 다음과 같이 상수를 연결할 수 있습니다.
다음과 같이 코드 코드를 복사합니다.
intinterestSet = SelectionKey.OP_READ |
관심 수집에 대해서는 아래에서 언급하겠습니다.
(4)선택키
이전 섹션에서는 Selector에 채널을 등록할 때 Register() 메서드가 SelectionKey 객체를 반환했습니다. 이 객체에는 관심을 가질 만한 몇 가지 속성이 포함되어 있습니다.
이자수금
준비된 컬렉션
채널
선택자
추가 개체(선택 사항)
아래에서는 이러한 속성을 설명합니다.
이자수금
선택기로 채널 등록 섹션에 설명된 대로 관심 컬렉션은 사용자가 선택하는 흥미로운 이벤트의 컬렉션입니다. 다음과 같이 SelectionKey를 통해 관심 분야 컬렉션을 읽고 쓸 수 있습니다.
다음과 같이 코드 코드를 복사합니다.
intinterestSet = SelectionKey.interess();
부울 isInterestedInAccept= (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect =interestSet & SelectionKey.OP_CONNECT;
부울 isInterestedInRead =interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite =interestSet & SelectionKey.OP_WRITE;
"비트 AND"를 이용하여 관심 수집과 주어진 SelectionKey 상수를 연산함으로써 특정 이벤트가 관심 수집에 포함되어 있는지 확인할 수 있음을 알 수 있다.
준비된 컬렉션
준비 세트는 채널이 준비된 작업 세트입니다. 선택(Selection) 후에는 먼저 준비된 세트에 접근하게 됩니다. 선택에 대해서는 다음 섹션에서 설명하겠습니다. 준비된 컬렉션은 다음과 같이 접근할 수 있습니다:
int ReadySet = SelectionKey.readyOps();
관심 수집 감지와 동일한 방법을 사용하여 채널에 어떤 이벤트나 작업이 준비되어 있는지 감지할 수 있습니다. 그러나 다음 네 가지 메서드도 사용할 수 있으며 모두 부울 유형을 반환합니다.
다음과 같이 코드 코드를 복사합니다.
SelectionKey.isAcceptable();
SelectionKey.isConnectable();
SelectionKey.isReadable();
SelectionKey.isWritable();
채널+선택기
SelectionKey에서 채널 및 선택기에 액세스하는 것은 간단합니다. 다음과 같이:
다음과 같이 코드 코드를 복사합니다.
채널채널= SelectionKey.channel();
선택기 선택기 = SelectionKey.selector();
추가 개체
특정 채널을 쉽게 식별하기 위해 객체 또는 추가 정보를 SelectionKey에 첨부할 수 있습니다. 예를 들어, 채널에 사용하기 위해 버퍼를 연결하거나 집계된 데이터가 포함된 개체를 연결할 수 있습니다. 그것을 사용하는 방법:
다음과 같이 코드 코드를 복사합니다.
SelectionKey.attach(theObject);
객체attachedObj = SelectionKey.attachment();
Register() 메서드를 사용하여 선택기로 채널을 등록할 때 객체를 연결할 수도 있습니다. 좋다:
다음과 같이 코드 코드를 복사합니다.
SelectionKey 키 = Channel.register(selector, SelectionKey.OP_READ, theObject);
(5) Selector를 통해 채널을 선택합니다.
하나 이상의 채널이 선택기에 등록되면 오버로드된 여러 select() 메서드를 호출할 수 있습니다. 이러한 메서드는 관심 있는 이벤트(예: 연결, 수락, 읽기 또는 쓰기)에 대해 준비된 채널을 반환합니다. 즉, "읽기 준비된" 채널에 관심이 있는 경우 select() 메서드는 읽기 이벤트가 준비된 채널을 반환합니다.
다음은 select() 메소드입니다:
정수 선택()
int select(긴 시간 초과)
int selectNow()
select()는 등록한 이벤트에 대해 하나 이상의 채널이 준비될 때까지 차단됩니다.
select(long timeout)는 최대 밀리초(매개변수) 동안 차단된다는 점을 제외하면 select()와 동일합니다.
selectNow()는 어떤 채널이 준비되어 있든 차단하지 않고 즉시 반환합니다(번역자 참고 사항: 이 메서드는 비차단 선택 작업을 수행합니다. 이전 선택 작업 이후 선택할 수 있는 채널이 없으면 이 메서드는 직접 0을 반환합니다.).
select() 메서드에서 반환된 int 값은 준비된 채널 수를 나타냅니다. 즉, select() 메서드에 대한 마지막 호출 이후 준비된 채널 수입니다. select() 메서드가 호출되면 한 채널이 준비되었으므로 1이 반환되고, select() 메서드가 다시 호출되면 다른 채널이 준비되면 다시 1이 반환됩니다. 첫 번째 준비 채널에서 작업이 수행되지 않으면 이제 두 개의 준비 채널이 있지만 각 select() 메서드 호출 사이에는 하나의 채널만 준비됩니다.
선택된키()
select() 메서드가 호출되고 반환 값이 하나 이상의 채널이 준비되었음을 나타내면 선택기의 selectedKeys() 메서드를 호출하여 "선택한 키 세트"의 준비된 채널에 액세스할 수 있습니다. 아래와 같이:
다음과 같이 코드 코드를 복사합니다.
selectedKeys 설정 = selector.selectedKeys();
Selector와 같은 채널을 등록할 때 Channel.register() 메서드는 SelectionKey 객체를 반환합니다. 이 객체는 Selector에 등록된 채널을 나타냅니다. 이러한 객체는 SelectionKey의 selectedKeySet() 메서드를 통해 액세스할 수 있습니다.
준비된 채널은 선택한 키 세트를 탐색하여 액세스할 수 있습니다. 다음과 같이:
다음과 같이 코드 코드를 복사합니다.
selectedKeys 설정 = selector.selectedKeys();
반복자 keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey 키 = keyIterator.next();
if(key.isAcceptable()) {
// ServerSocketChannel에 의해 연결이 승인되었습니다.
} else if (key.isConnectable()) {
// 원격 서버와 연결이 설정되었습니다.
} else if (key.isReadable()) {
// 채널을 읽을 준비가 되었습니다.
} else if (key.isWritable()) {
// 채널에 쓸 준비가 되었습니다.
}
keyIterator.<tuihighlight><a href="javascript:;" style="display:inline;float:none;position:inherit;cursor:pointer;color:#7962D5;text-꾸밈:underline;">제거</a ></tuihighlight>();
}
이 루프는 선택된 키 세트의 각 키를 반복하고 각 키에 해당하는 채널에 대한 준비 이벤트를 감지합니다.
각 반복이 끝날 때마다 keyIterator.remove() 호출을 확인하세요. 선택기는 선택한 키 세트 자체에서 SelectionKey 인스턴스를 제거하지 않습니다. 채널이 처리되면 직접 제거해야 합니다. 다음에 채널이 준비되면 선택기는 채널을 선택한 키 세트에 다시 넣습니다.
SelectionKey.channel() 메서드에서 반환된 채널은 ServerSocketChannel 또는 SocketChannel 등 처리하려는 유형으로 변환되어야 합니다.
(6)웨이크업()
select() 메서드 호출 후 스레드가 차단됩니다. 준비된 채널이 없더라도 select() 메서드에서 반환하는 방법이 있습니다. 첫 번째 스레드가 select() 메서드를 호출한 개체에서 다른 스레드가 Selector.wakeup() 메서드를 호출하도록 하면 됩니다. select() 메서드에서 차단된 스레드는 즉시 반환됩니다.
다른 스레드가 wakeup() 메서드를 호출하지만 현재 select() 메서드에서 차단된 스레드가 없으면 select() 메서드를 호출하는 다음 스레드가 즉시 "깨어납니다".
(7)닫기()
Selector를 사용한 후 close() 메서드를 호출하면 Selector가 닫히고 Selector에 등록된 모든 SelectionKey 인스턴스가 무효화됩니다. 채널 자체는 닫히지 않습니다.
(8) 완전한 예
다음은 Selector를 열고 Selector에 채널을 등록한 후(채널 초기화 과정은 생략) Selector의 4가지 이벤트(accept, connect, read, write)가 준비되었는지 지속적으로 모니터링하는 완전한 예입니다.
다음과 같이 코드 코드를 복사합니다.
선택기 선택기 = Selector.open();
채널.configureBlocking(false);
SelectionKey 키 = Channel.register(selector, SelectionKey.OP_READ);
동안(참) {
int ReadyChannels = selector.select();
if(readyChannels == 0) 계속;
selectedKeys 설정 = selector.selectedKeys();
반복자 keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey 키 = keyIterator.next();
if(key.isAcceptable()) {
// ServerSocketChannel에 의해 연결이 승인되었습니다.
} else if (key.isConnectable()) {
// 원격 서버와 연결이 설정되었습니다.
} else if (key.isReadable()) {
// 채널을 읽을 준비가 되었습니다.
} else if (key.isWritable()) {
// 채널에 쓸 준비가 되었습니다.
}
keyIterator.<tuihighlight><a href="javascript:;" style="display:inline;float:none;position:inherit;cursor:pointer;color:#7962D5;text-꾸밈:underline;">제거</a ></tuihighlight>();
}
}
파일 채널
(이 섹션의 원문 링크, 저자: Jakob Jenkov, 번역자: Zhou Tai, 교정자: Ding Yi)
Java NIO의 FileChannel은 파일에 연결된 채널입니다. 파일 채널을 통해 파일을 읽고 쓸 수 있습니다.
FileChannel은 비차단 모드로 설정할 수 없으며 항상 차단 모드에서 실행됩니다.
OpenFileChannel
FileChannel을 사용하기 전에 먼저 열어야 합니다. 그러나 FileChannel을 직접 열 수는 없습니다. InputStream, OutputStream 또는 RandomAccessFile을 사용하여 FileChannel 인스턴스를 얻어야 합니다. 다음은 RandomAccessFile을 통해 FileChannel을 여는 예입니다:
다음과 같이 코드 코드를 복사합니다.
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
FileChannel에서 데이터 읽기
FileChannel에서 데이터를 읽으려면 여러 read() 메서드 중 하나를 호출합니다. 좋다:
다음과 같이 코드 코드를 복사합니다.
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
먼저 버퍼를 할당합니다. FileChannel에서 읽은 데이터는 Buffer로 읽혀집니다.
그런 다음 FileChannel.read() 메서드를 호출합니다. 이 메서드는 FileChannel의 데이터를 Buffer로 읽습니다. read() 메서드에서 반환된 int 값은 버퍼로 읽힌 바이트 수를 나타냅니다. -1을 반환하면 파일의 끝에 도달했음을 의미합니다.
FileChannel에 데이터 쓰기
FileChannel.write() 메서드를 사용하여 FileChannel에 데이터를 씁니다. 이 메서드의 매개 변수는 Buffer입니다. 좋다:
다음과 같이 코드 코드를 복사합니다.
String newData = "파일에 쓸 새 문자열..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
채널.쓰기(buf);
}
FileChannel.write()는 while 루프에서 호출됩니다. write() 메서드가 FileChannel에 한 번에 쓸 수 있는 바이트 수를 보장할 수 없기 때문에 채널에 기록되지 않은 바이트가 Buffer에 없을 때까지 write() 메서드를 반복적으로 호출해야 합니다.
닫기파일채널
FileChannel은 작업이 끝나면 닫혀야 합니다. 좋다:
다음과 같이 코드 코드를 복사합니다.
채널.닫기();
FileChannel 위치 방법
때로는 FileChannel의 특정 위치에서 데이터를 읽고 써야 할 수도 있습니다. position() 메서드를 호출하여 FileChannel의 현재 위치를 가져올 수 있습니다.
position(long pos) 메소드를 호출하여 FileChannel의 현재 위치를 설정할 수도 있습니다.
다음은 두 가지 예입니다.
다음과 같이 코드 코드를 복사합니다.
긴 위치 = 채널.위치();
채널.위치(pos +123);
파일 끝 이후 위치를 설정한 다음 파일 채널에서 데이터를 읽으려고 하면 읽기 메서드는 파일 끝 플래그인 -1을 반환합니다.
파일 끝 이후 위치를 설정한 후 해당 채널에 데이터를 쓰면 현재 위치까지 파일이 확장되어 데이터가 기록됩니다. 이로 인해 디스크의 실제 파일에 기록된 데이터 사이에 공백인 "파일 구멍"이 발생할 수 있습니다.
FileChannel 크기 방법
FileChannel 인스턴스의 size() 메서드는 인스턴스와 연결된 파일의 크기를 반환합니다. 좋다:
다음과 같이 코드 코드를 복사합니다.
긴 파일 크기 = 채널.크기();
FileChannel의 자르기 메소드
FileChannel.truncate() 메서드를 사용하여 파일을 가로챌 수 있습니다. 파일을 가로채면 지정된 파일 길이 이후의 부분이 삭제됩니다. 좋다:
다음과 같이 코드 코드를 복사합니다.
채널.잘림(1024);
이 예에서는 파일의 처음 1024바이트를 가로챕니다.
FileChannel의 Force 메서드
FileChannel.force() 메서드는 아직 디스크에 기록되지 않은 채널의 데이터를 디스크에 강제로 적용합니다. 성능상의 이유로 운영 체제는 데이터를 메모리에 캐시하므로 FileChannel에 기록된 데이터가 디스크에 즉시 기록된다는 보장은 없습니다. 이를 보장하려면 force() 메소드를 호출해야 합니다.
force() 메소드에는 파일 메타데이터(권한 정보 등)를 동시에 디스크에 쓸지 여부를 나타내는 부울 매개변수가 있습니다.
다음 예에서는 파일 데이터와 메타데이터를 모두 디스크에 강제 적용합니다.
다음과 같이 코드 코드를 복사합니다.
채널.힘(true);
소켓 채널
(이 섹션의 원문 링크, 저자: Jakob Jenkov, 번역자: Zheng Yuting, 교정자: Ding Yi)
Java NIO의 SocketChannel은 TCP 네트워크 소켓에 연결된 채널입니다. SocketChannel은 다음 두 가지 방법으로 생성할 수 있습니다.
SocketChannel을 열고 인터넷의 서버에 연결합니다.
ServerSocketChannel에 새 연결이 도착하면 SocketChannel이 생성됩니다.
소켓채널 열기
다음은 SocketChannel을 여는 방법입니다.
다음과 같이 코드 코드를 복사합니다.
SocketChannel 소켓Channel = SocketChannel.open();
소켓Channel.connect(new InetSocketAddress("http://jenkov.com", 80));
소켓채널 닫기
SocketChannel 작업이 완료되면 SocketChannel.close()를 호출하여 SocketChannel을 닫습니다.
다음과 같이 코드 코드를 복사합니다.
소켓채널.닫기();
SocketChannel에서 데이터 읽기
SocketChannel에서 데이터를 읽으려면 read() 메서드 중 하나를 호출합니다. 예는 다음과 같습니다.
다음과 같이 코드 코드를 복사합니다.
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = 소켓Channel.read(buf);
먼저 버퍼를 할당합니다. SocketChannel에서 읽은 데이터는 이 버퍼에 배치됩니다.
그런 다음 SocketChannel.read()를 호출합니다. 이 메서드는 SocketChannel의 데이터를 Buffer로 읽습니다. read() 메서드에서 반환된 int 값은 버퍼로 읽힌 바이트 수를 나타냅니다. -1이 반환되면 스트림의 끝을 읽었음을 의미합니다(연결이 닫혔습니다).
SocketChannel에 쓰기
SocketChannel에 데이터를 쓰려면 Buffer를 매개변수로 사용하는 SocketChannel.write() 메서드를 사용합니다. 예는 다음과 같습니다:
다음과 같이 코드 코드를 복사합니다.
String newData = "파일에 쓸 새 문자열..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
채널.쓰기(buf);
}
SocketChannel.write() 메서드는 while 루프에서 호출됩니다. Write() 메서드는 SocketChannel에 쓸 수 있는 바이트 수를 보장할 수 없습니다. 따라서 버퍼에 쓸 바이트가 더 이상 남지 않을 때까지 write()를 반복적으로 호출합니다.
비차단 모드
SocketChannel을 비차단 모드로 설정할 수 있습니다. 설정 후 비동기 모드에서 connect(), read() 및 write()를 호출할 수 있습니다.
연결하다()
SocketChannel이 비차단 모드이고 이때 connect()가 호출되면 연결이 설정되기 전에 메서드가 반환될 수 있습니다. 연결이 설정되었는지 확인하려면 FinishConnect() 메서드를 호출하면 됩니다. 이와 같이:
다음과 같이 코드 코드를 복사합니다.
소켓Channel.configureBlocking(false);
소켓Channel.connect(new InetSocketAddress("http://jenkov.com", 80));
while(!socketChannel.finishConnect() ){
//기다리거나 다른 일을 하세요...
}
쓰다()
비차단 모드에서는 write() 메서드가 아무것도 쓰기 전에 반환될 수 있습니다. 따라서 루프에서 write()를 호출해야 합니다. 이전에도 예시가 있었으므로 여기서는 자세히 다루지 않겠습니다.
읽다()
비차단 모드에서는 데이터를 읽기 전에 read() 메서드가 반환될 수 있습니다. 따라서 읽은 바이트 수를 알려주는 int 반환 값에 주의를 기울여야 합니다.
비차단 모드 및 선택기
비차단 모드는 선택기와 함께 더 잘 작동합니다. 선택기에 하나 이상의 SocketChannel을 등록하면 읽기, 쓰기 등의 준비가 된 채널을 선택기에 요청할 수 있습니다. Selector와 SocketChannel의 조합에 대해서는 나중에 자세히 설명하겠습니다.
ServerSocket 채널
(이 섹션의 원문 링크, 저자: Jakob Jenkov, 번역자: Zheng Yuting, 교정자: Ding Yi)
Java NIO의 ServerSocketChannel은 표준 IO의 ServerSocket과 마찬가지로 새로 들어오는 TCP 연결을 수신할 수 있는 채널입니다. ServerSocketChannel 클래스는 java.nio.channels 패키지에 있습니다.
예는 다음과 같습니다.
다음과 같이 코드 코드를 복사합니다.
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
동안(참){
소켓채널 소켓채널 =
serverSocketChannel.accept();
//socketChannel로 뭔가를 하세요...
}
ServerSocketChannel 열기
ServerSocketChannel.open() 메서드를 호출하여 ServerSocketChannel을 엽니다. 예를 들면 다음과 같습니다.
다음과 같이 코드 코드를 복사합니다.
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
ServerSocketChannel 닫기
ServerSocketChannel.close() 메서드를 호출하여 ServerSocketChannel을 닫습니다. 예를 들면 다음과 같습니다.
다음과 같이 코드 코드를 복사합니다.
serverSocketChannel.close();
새로 들어오는 연결 듣기
ServerSocketChannel.accept() 메서드를 통해 새로 들어오는 연결을 수신합니다. accept() 메서드가 반환되면 새로 들어오는 연결이 포함된 SocketChannel을 반환합니다. 따라서 accept() 메서드는 새 연결이 도착할 때까지 차단됩니다.
일반적으로 하나의 연결만 수신하는 대신 다음 예제와 같이 while 루프에서 accept() 메서드가 호출됩니다.
다음과 같이 코드 코드를 복사합니다.
동안(참){
소켓채널 소켓채널 =
serverSocketChannel.accept();
//socketChannel로 뭔가를 하세요...
}
물론 while 루프에서 true 외에 다른 종료 기준을 사용할 수도 있습니다.
비차단 모드
ServerSocketChannel은 비차단 모드로 설정할 수 있습니다. 비차단 모드에서는 accept() 메서드가 즉시 반환됩니다. 새로 들어오는 연결이 없으면 반환 값은 null이 됩니다. 따라서 반환된 SocketChannel이 null인지 확인해야 합니다. 좋다:
다음과 같이 코드 코드를 복사합니다.
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
serverSocketChannel.configureBlocking(false);
동안(참){
소켓채널 소켓채널 =
serverSocketChannel.accept();
if(소켓채널 != null){
//socketChannel로 뭔가를 하세요...
}
}
데이터그램 채널
(이 섹션의 원문 링크, 저자: Jakob Jenkov, 번역자: Zheng Yuting, 교정자: Ding Yi)
Java NIO의 DatagramChannel은 UDP 패킷을 보내고 받을 수 있는 채널입니다. UDP는 연결이 없는 네트워크 프로토콜이므로 다른 채널처럼 읽고 쓸 수 없습니다. 데이터 패킷을 보내고 받습니다.
OpenDatagram채널
DatagramChannel이 열리는 방법은 다음과 같습니다.
다음과 같이 코드 코드를 복사합니다.
DatagramChannel 채널 = DatagramChannel.open();
Channel.socket().bind(new InetSocketAddress(9999));
이 예제에서 열린 DatagramChannel은 UDP 포트 9999에서 패킷을 수신할 수 있습니다.
데이터를 수신하다
receive() 메서드를 통해 DatagramChannel에서 다음과 같은 데이터를 수신합니다.
다음과 같이 코드 코드를 복사합니다.
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
채널.수신(buf);
receive() 메소드는 수신된 데이터 패킷 내용을 지정된 버퍼에 복사합니다. 버퍼가 수신된 데이터를 수용할 수 없으면 초과 데이터가 삭제됩니다.
데이터 보내기
다음과 같이 send() 메서드를 통해 DatagramChannel에서 데이터를 보냅니다.
다음과 같이 코드 코드를 복사합니다.
String newData = "파일에 쓸 새 문자열..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
int bytesSent = Channel.send(buf, new InetSocketAddress("jenkov.com", 80));
이 예에서는 "jenkov.com" 서버의 UDP 포트 80으로 문자열을 보냅니다. 서버가 이 포트를 모니터링하지 않기 때문에 아무 일도 일어나지 않습니다. 또한 UDP는 데이터 전달 측면에서 어떤 보장도 없기 때문에 나가는 패킷이 수신되었는지 여부도 알려주지 않습니다.
특정 주소에 연결
DatagramChannel은 네트워크의 특정 주소에 "연결"될 수 있습니다. UDP는 연결이 없기 때문에 특정 주소에 연결해도 TCP 채널과 같은 실제 연결이 생성되지 않습니다. 대신, DatagramChannel은 특정 주소에서만 데이터를 보내고 받을 수 있도록 잠겨 있습니다.
예는 다음과 같습니다.
다음과 같이 코드 코드를 복사합니다.
Channel.connect(new InetSocketAddress("jenkov.com", 80));
연결되면 기존 채널에서와 마찬가지로 read() 및 write() 메서드를 사용할 수도 있습니다. 데이터 전송에 대한 보장은 없습니다. 다음은 몇 가지 예입니다.
다음과 같이 코드 코드를 복사합니다.
int bytesRead = 채널.read(buf);
int bytesWritten = 채널.write(그러나);
파이프
(이 섹션의 원문 링크, 저자: Jakob Jenkov, 번역자: Huang Zhong, 교정자: Ding Yi)
Java NIO 파이프는 2개의 스레드 간의 단방향 데이터 연결입니다. 파이프에는 소스 채널과 싱크 채널이 있습니다. 데이터는 싱크 채널에 기록되고 소스 채널에서 읽혀집니다.
다음은 파이프 원리의 그림입니다.
파이프라인 생성
Pipe.open() 메서드를 통해 파이프를 엽니다. 예를 들어:
다음과 같이 코드 코드를 복사합니다.
파이프 파이프 = Pipe.open();
파이프에 데이터 쓰기
파이프에 데이터를 쓰려면 싱크 채널에 액세스해야 합니다. 이와 같이:
다음과 같이 코드 코드를 복사합니다.
Pipe.SinkChannelinkChannel = 파이프.싱크();
다음과 같이 SinkChannel의 write() 메서드를 호출하여 SinkChannel에 데이터를 씁니다.
다음과 같이 코드 코드를 복사합니다.
String newData = "파일에 쓸 새 문자열..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
<b>sinkChannel.write(buf);</b>
}
[암호]
파이프에서 데이터 읽기
파이프에서 데이터를 읽으려면 다음과 같이 소스 채널에 액세스해야 합니다.
[암호]
Pipe.SourceChannel sourceChannel = 파이프.소스();
다음과 같이 소스 채널의 read() 메서드를 호출하여 데이터를 읽습니다.
다음과 같이 코드 코드를 복사합니다.
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
read() 메소드에 의해 반환된 int 값은 버퍼로 읽혀진 바이트 수를 알려줍니다.