채널이 데이터를 읽고 쓰는 버퍼는 모두
ByteBuffer
이다.
그렇기 때문에 채널을 통해 읽은 데이터를 복원하려면 ByteBuffer
를 문자열 또는 다른 타입 버퍼(CharBuffer
, ShortBuffer
, IntBuffer
, LongBuffer
,FloatBuffer
, DoubleBuffer
)로 변환해야 한다. 반대로 문자열 또는 다른 타입 버퍼의 내용을 채널을 통해 쓰고 싶다면 ByteBuffer
로 변환해야 한다.
프로그램에서 가장 많이 처리되는 데이터는 String 타입, 즉 문자열이다. 채널을 통해 문자열을 파일이나 네트워크로 전송하려면 특정 문자셋(UTF-8, EUC-KR)
으로 인코딩해서 ByteBuffer
로 변환해야 한다.
java.nio.charset.Charset
) 객체가 필요하다.Charset charset = Charset.forName("UTF-8"); //매개값으로 주어진 문자셋
Charset charset = Charset.defaulCharset(); //운영체제가 사용하는 디폴트 문자셋
Charset
을 이용해서 문자열을 ByteBuffer
로 변환하려면 다음과 같이 encode()
메소드를 호출하면 된다.String data = ...;
ByteBuffer byteBuffer = charset.encode(data);
반대로 파일이나 네트워크로부터 읽은 ByteBuffer
가 특정 문자셋(UTF-8, EUC-KR)으로 인코딩되어 있을 경우, 해당 문자셋으로 디코딩해야만 문자열로 복원할 수 있다. Charset
은 ByteBuffer
를 디코딩해서 CharBuffer
로 변환시키는 decode()
메소드를 제공하고 있기 때문에 다음과 같이 문자열로 쉽게 복원할 수 있다.
ByteBuffer byteBuffer = ...;
String data = charset.decode(byteBuffer).toString();
int[]
배열을 생성하고 이것을 파일이나 네트워크로 출력하기 위해서는 int[]
배열 또는 IntBuffer
로부터 ByteBuffer
를 생성해야 한다. int
타입은 4byte 크기를 가지므로 int[]
배열 크기 또는 IntBuffer
의 capacity
보다 4배 큰 capacity
를 가진 ByteBuffer
를 생성하고, ByteBuffer
의 putInt()
메소드로 정수값을 하나씩 저장하면 된다.
다음은 int[]
배열을 IntBuffer
로 래핑하고, 4배 큰 ByteBuffer
를 생성한 후 정수값을 저장한다. putInt()
메소드는 position
을 이동시키기 때문에 모두 저장한 후에는 position
을 0
으로 되돌려 놓는 flip()
메소드를 호출해야 한다.
int[] data = new int[] { 10, 20 };
IntBuffer intBuffer = IntBuffer.wrap(data);
ByteBuffer byteBuffer = ByteBuffer.allocate(intBuffer.capacity() * 4);
for (int i = 0; i < intBuffer.capacity(); i++) {
byteBuffer.putInt(intBuffer.get(i));
}
byteBuffer.flip();
반대로 파일이나 네트워크로부터 입력된 ByteBuffer
에 4바이트씩 연속된 int
데이터가 저장되어 있을 경우, int[]
배열로 복원이 가능하다. ByteBuffer
의 asIntBuffer()
메소드로 IntBuffer
를 얻고, IntBuffer
의 capacity
와 동일한 크기의 int[]
배열을 생성한다. 그리고 IntBuffer
의 get()
메소드로 int 값들을 배열에 저장하면 된다.
ByteBuffer byteBuffer = ...;
IntBuffer intBuffer = byteBuffer.asIntBuffer();
int[] data = new int[intBuffer.capacity()];
intBuffer.get(data);
🚫 주의
array()
메소드는 래핑한 배열만 리턴하기 때문에int[]
배열을wrap()
메소드로 래핑한IntBuffer
에서만 사용할 수 있다! 그러므로ByteBuffer
에서asIntBuffer()
로 얻은IntBuffer
에서는array()
메소드를 사용해서int[]
배열을 얻을 수 없다.
ByteBuffer
와 IntBuffer
간의 변환처럼 ByteBuffer
와 ShortBuffer
, LongBuffer
, FloatBuffer
, DoubleBuffer
간의 변환도 비슷한 방법으로 하면 된다.
리턴 타입 | 변환 메소드 | 설명 |
---|---|---|
ShortBuffer | asShortBuffer() | 2바이트씩 연속된 short 데이터를 가진 ByteBuffer일 경우 |
IntBuffer | asIntBuffer() | 4바이트씩 연속된 int 데이터를 가진 ByteBuffer일 경우 |
LongBuffer | asLongBuffer() | 8바이트씩 연속된 long 데이터를 가진 ByteBuffer일 경우 |
FloatBuffer | asFloatBuffer() | 4바이트씩 연속된 float 데이터를 가진 ByteBuffer일 경우 |
DoubleBuffer | asDoubleBuffer() | 8바이트씩 연속된 double 데이터를 가진 ByteBuffer일 경우 |
java.nio.channels.FileChannel
을 이용하면 파일 읽기/쓰기를 할 수 있다.
FileChannel
은 동기화 처리가 되어있기 때문에 멀티 스레드 환경에서 사용해도 안전하다.
FileChannel
은 정적 메소드인 open()
을 호출해서 얻을 수도 있지만, IO의 FileInputStream
, FileOutputStream
의 getChannel()
메소드를 호출해서 얻을 수도 있다.
다음은 open()
메소드로 FileChannel
을 얻는 방법을 보여준다.
FileChannel fileChannel = FileChannel.open(Path path, OpenOption... options);
path
매개값은 열거나, 생성하고자 하는 파일의 경로를 Path
객체로 생성해서 지정하면 된다.options
매개값은 열기 옵션 값으로, StandardOpenOption
의 다음 열거 상수를 나열해주면 된다.열거 상수 | 설명 |
---|---|
READ | 읽기용으로 파일을 연다. |
WRITE | 쓰기용으로 파일을 연다. |
CREATE | 파일이 없다면 새 파일을 생성한다. |
CREATE_NEW | 새 파일을 만든다. 이미 파일이 있으면 예외와 함께 실패한다. |
APPEND | 파일 끝에 데이터를 추가한다.(WRITE나 CREATE와 함께 사용됨) |
DELETE_ON_CLOSE | 채널을 닫을 때 파일을 삭제한다.(임시 파일을 삭제할 때 사용) |
TRUNCATE_EXISTING | 파일을 0바이트로 잘라낸다.(WRITE 옵션과 함께 사용됨) |
예를 들어 "C:\Temp\file.txt"
파일을 생성하고, 어떤 내용을 쓰고 싶다면 다음과 같이 매개값을 지정하면 된다.
FileChannel fileChannel = FileChannel.open(
Paths.get("C:/Temp/file.txt"),
StandardOpenOption.CREATE_NEW,
StandardOpenOption.WRITE
);
다음은 "C:\Temp\file.txt"
파일을 읽고, 쓸 수 있도록 FileChannel
을 생성한다.
FileChannel fileChannel = FileChannel.open(
Paths.get("C:/Temp/file.txt"),
StandardOpenOption.READ,
StandardOpenOption.WRITE
);
FileChannel
을 더 이상 이용하지 않을 경우에는 다음과 같이 close()
메소드를 호출해서 닫아주어야 한다.
fileChannel.close();
파일에 바이트를 쓰려면
FileChannel
의write()
메소드를 호출하면 된다.
매개값으로 ByteBuffer
객체를 주면 되는데, 파일에 쓰여지는 바이트는 ByteBuffer
의 position
부터 limit
까지다. position
이 0
이고 limit
이 capacity
와 동일하다면 ByteBuffer
의 모든 바이트가 파일에 쓰여진다. write()
메소드의 리턴값은 ByteBuffer
에서 파일로 쓰여진 바이트 수이다.
int bytesCount = fileChannel.write(ByteBuffer src);
파일로부터 바이트를 읽기 위해서는 FileChannel
의 read()
메소드를 호출하면 된다. 매개값으로 ByteBuffer
객체를 주면 되는데, 파일에 읽혀지는 바이트는 ByteBuffer
의 position
부터 저장된다. position
이 0
이면 ByteBuffer
의 첫 바이트로부터 저장된다. read()
메소드의 리턴값은 파일에서 ByteBuffer
로 읽혀진 바이트 수이다. 한 번 읽을 수 있는 최대 바이트수는 ByteBuffer
의 capacity
까지이므로 리턴되는 최댓값은 capacity
가 된다. 더 이상 읽을 바이트가 없다면 read()
메소드는 -1
을 리턴한다.
int bytesCount = fileChannel.read(ByteBuffer dst);
버퍼에 한 바이트를 저장할 때마다 position
이 1
씩 증가하게 되는데, read()
메소드가 -1
을 리턴할 때까지 버퍼에 저장한 마지막 바이트의 위치는 position - 1
인덱스이다.
이것이 자바다 책