2018년 12월 4일 화요일

[Java] N-I/O(Non-Blocking) 파일 읽기 쓰기 - GatheringByteChannel, ScatteringByteChannel, ByteBuffer 사용.

우리는 지금까지 다음과 같이 살펴보았다.

1.  InputStream / OutputStream : 입, 출력 스트림을 바이트로 처리하여 읽기, 쓰기.
2.  FileInputStream / FileOutputStream : 입, 출력 스트림을 바이트로 파일에 읽기, 쓰기.
3.  BufferedInputStream / BufferedOutputStream : 입, 출력을 버퍼를 사용해서 읽기, 쓰기.
4.  BufferdReader / bufferdWriter : 입, 출력을 텍스트형태로 한 라인씩 읽고 쓰기.
5.  DataInputStream / DataOutputStream : 자바의 데이터 형식으로 읽기, 쓰기 사용.

이제는 ByteBuffer를 통해서, 입, 출력 문자들을 저장한 뒤, Non-Blocking방식으로
저장한 데이터를 읽기, 쓰기 형태로 바꾸는 방법을 사용해보도록 하겠다.

다음시간에는 소켓을 통한 방법을 살펴 볼 예정이며, 이번시간에는 파일에 읽기 쓰기 형태로 알아보도록 하겠다. 

- 소켓에 적용 : Channel을 통해서 읽고 쓰기할땐, 하나의 버퍼를 사용하면됨. (selector라는 역할을 주의 깊게 봐야함.)

- 파일에 적용 : ByteBuffer를 2개 사용하여, 조금 더 버퍼사용 방법에 대해서 살펴보도록 하 겠다. 왜냐하면 버퍼를 사용할땐 지금까지 스트림을 통해 read, write한것과는 사뭇 다르기 때문이다.

예제를 살펴보기 전, 하나만 기억하자.
1. ByteBuffer는 위치를 가리키는 position이 존재한다. (default : 0번에 위치함.)
2. 이 position값은 데이터를 읽기, 쓰기할때 데이터의 위치를 가리킨다.
3. ByteBuffer로 데이터를 읽을 때, position은 1씩 증가함.
(position : 0번에 위치 -> 데이터를 읽음 -> 1번에 위치 즉, 다음 읽을 값을 가리킴.)
4. 마찬가지로 write할때도 3번 형태와 같아짐.
5. 소켓에서는 하나의 ByteBuffer를 사용하게 되는데, 읽고 쓰기할때는, position값을 항상 0번에 위치시키기 위해서 .flip()메소드를 사용함.



예제 - A.txt파일에 ByteBuffer를 사용해서 데이터를 읽어 B.txt파일에 저장하기.(저장할때도 ByteBuffer를 사용.)


▶ A.txt












ByteBuffer_Exam2.java
  1. package org.java.project.hanbat.chapter11;
  2.  
  3. import java.io.FileInputStream;
  4. import java.io.FileOutputStream;
  5. import java.io.IOException;
  6. import java.nio.ByteBuffer;
  7. import java.nio.channels.GatheringByteChannel;
  8. import java.nio.channels.ScatteringByteChannel;
  9.  
  10. public class ByteBuffer_Exam2{
  11.     public static void main(String[] args) {
  12.         String aFile = "A.txt";
  13.         String bFile = "B.txt";
  14.        
  15.         // ByteBuffer크기를 20으로 지정함. -> 최대 20Byte저장가능.
  16.         ByteBuffer buffer = ByteBuffer.allocate(20); // 읽기, 쓰기 용도의 버퍼
  17.        
  18.         try {
  19.             // 파일에 버퍼의 데이터를 read할때,
  20.             ScatteringByteChannel inChannel = (new FileInputStream(aFile)).getChannel();
  21.            
  22.             // 파일에 버퍼의 데이터를 write할때
  23.             GatheringByteChannel outChannel = (new FileOutputStream(bFile)).getChannel();
  24.            
  25.             // A.txt 파일의 내용을 읽어와서 Buffer에 저장 이때 position은 읽어들인 바이트 수 만큼 위치함.
  26.             int readCnt = inChannel.read(buffer);
  27.             System.out.println("read Count : " + readCnt);
  28.             inChannel.close();
  29.            
  30.             // 읽어온 A.txt의 내용을 콘솔로 출력해보기. byte배열을 사용하여 저장한뒤 출력함.
  31.             // position위치를 0으로 수정함.
  32.             buffer.flip();
  33.             byte[] rByte = new byte[20];
  34.             buffer.get(rByte); // 이때 position값은 rByte에 저장한 만큼 이동하게 된다.
  35.            
  36.             System.out.println("A.txt : " + new String(rByte)); // A.txt파일 내용을 콘솔에 출력
  37.            
  38.             // 다시 버퍼의 position값을 0으로 변경.(B.txt파일에 write해야하므로)
  39. //          buffer.flip();
  40.            
  41.             // B.txt에 읽어온 바이트 버퍼를 write함. 이때 버퍼의 position은 write한 만큼 이동함.
  42.             outChannel.write(buffer); //  
  43.             outChannel.close();
  44.         } catch(IOException e) {
  45.             e.printStackTrace();
  46.         }
  47.     }
  48. }




▶실행결과
B.txt가 빈 파일로 생성 되었으며, 다음과 같은 예외가 발생함.









: 읽어 들인 데이터의 수는 17이며 A.txt의 내용 17바이트만큼 잘 읽어왔다. 그러나 예외처리가 발생했다.
34번 라인을 보면, 읽어온 바이트 버퍼를 바이트 배열에 저장할때, BufferUnderflowException이 발생한다. 왜냐? 읽어온 바이트는 17바이트 인데, 20바이트의 공간을 갖는 배열에 저장하려고 하기 때문이다. 반대로 읽어온 바이트가 20바이트 이상이라면, 20바이트 크기를 갖는 배열에 저장할 때는 아무런 문제가 없다.

즉, 바이트 버퍼를 .get을 통해 바이트 배열에 저장할 때는 바이트 배열의 크기만큼 버퍼의 데이터를 저장하는데, 만약 버퍼의 크기가 더 작다면, 이는 바이트 배열의 공간이 낭비되므로 BufferUnderflowException이 발생한다. 
바이트 버퍼를 바이트 배열에 저장할 때 항상 바이트 배열의 공간보다 적게 데이터를 저장하지 않도록 주의하자.


- 텍스트의 크기를 다시 수정한 결과.


▶ A.txt






▶ 실행결과







▶ B.txt
















: B.txt에 20Byte만큼 저장된 결과를 확인할 수 있다. (띄어쓰기도 포함 되는 것을 주의!)

이로써 바이트 버퍼 사용하는 방법에 대해 살펴보았으며, 다음시간에는 
소켓을 바이트 버퍼를 통해서 서버와 클라이언트가 읽고 쓰는 예제에 대해서 살펴보도록 하겠다.














[Java] N-I/O(Non-Blocking) 파일 읽기 쓰기 - GatheringByteChannel, ScatteringByteChannel, ByteBuffer 사용.

우리는 지금까지 다음과 같이 살펴보았다. 1.  InputStream / OutputStream : 입, 출력 스트림을 바이트로 처리하여 읽기, 쓰기. 2.  FileInputStream / FileOutputStream : 입, 출력 스트림을 ...