목차
Java NIO FileChannel(파일 채널)
FileChannel은 파일의 바이트를 ByteBuffer와 주고받는다. 파일을 읽거나 쓸 때 채널의 현재 위치(position)가 이동한다. 문자 인코딩은 채널이 알아서 정하지 않으므로 UTF-8 같은 규칙을 명시해야 한다. 단순한 작은 텍스트 파일이라면 Files.readString·Files.writeString이 더 짧고, 바이트 단위 위치 제어·큰 파일 처리·채널 간 전송이 필요할 때 FileChannel을 검토한다.
열기 옵션과 닫기
Path path = Path.of("sample.txt");
try (FileChannel channel = FileChannel.open(path,
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING,
StandardOpenOption.WRITE)) {
// 버퍼를 쓰는 작업
}
CREATE는 파일이 없으면 만든다. TRUNCATE_EXISTING은 이미 있는 파일 길이를 0으로 줄이므로 기존 내용을 보존해야 한다면 넣지 않는다. CREATE_NEW는 파일이 이미 있으면 실패한다. READ, WRITE, APPEND는 목적에 맞게 선택한다. try-with-resources는 성공과 예외 경로에서 채널을 닫는다. 원문의 의사 코드 FileChannel.open(Path path, OpenOption... options)는 메서드 서명 설명이지 그대로 실행하는 코드가 아니다.
버퍼를 끝까지 쓰고 읽기
다음 예제는 텍스트를 바이트로 인코딩한 뒤 기록하고, 다시 바이트를 모아 디코딩한다. 여러 번의 write나 read를 허용하는 구조다.
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
public class ChannelExample {
public static void main(String[] args) throws Exception {
Path path = Path.of("sample.txt");
ByteBuffer source = StandardCharsets.UTF_8.encode("안녕하세요, Java\n");
try (FileChannel out = FileChannel.open(path,
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING,
StandardOpenOption.WRITE)) {
while (source.hasRemaining()) out.write(source);
}
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
ByteBuffer buffer = ByteBuffer.allocate(8);
try (FileChannel in = FileChannel.open(path, StandardOpenOption.READ)) {
while (in.read(buffer) != -1) {
buffer.flip();
while (buffer.hasRemaining()) bytes.write(buffer.get());
buffer.clear();
}
}
System.out.print(bytes.toString(StandardCharsets.UTF_8));
// 안녕하세요, Java
}
}
source는 encode 직후 읽을 수 있는 상태다. write는 이번 호출에서 실제로 쓴 바이트 수만 반환하므로 hasRemaining()을 확인하며 끝까지 쓴다. 읽기는 버퍼를 채운 다음 flip()으로 읽기 범위를 정하고, 소비한 뒤 clear()로 다음 읽기를 준비한다. read()가 -1이면 파일 끝이다. UTF-8의 한 글자가 8바이트 버퍼의 경계에 걸릴 수 있으므로 매 조각을 각각 문자열로 디코딩하지 않고 전체 바이트를 모아 한 번 디코딩했다. 큰 파일에서는 전체를 메모리에 모으지 말고 상태를 유지하는 CharsetDecoder로 조각을 처리한다.
파일 복사는 목적을 구분하기
java.nio.file.Files.copy(
Path.of("source.txt"), Path.of("target.txt"),
java.nio.file.StandardCopyOption.REPLACE_EXISTING
);
이 코드는 FileChannel이 아니라 Files.copy를 사용하는 간단한 파일 복사다. 원문의 복사 절에 나온 API도 이 방법이다. 기존 대상 파일을 덮어쓰면 안 되면 REPLACE_EXISTING을 빼야 한다. 파일 속성 복사, 심벌릭 링크 처리, 원자적 교체의 요구는 별도 옵션과 파일 시스템의 지원 범위를 확인한다. 파일 채널 자체로 복사하려면 transferTo·transferFrom을 검토할 수 있지만 전송 바이트 수와 일부 전송 가능성을 확인해야 한다.
같은 파일을 여러 스레드가 공유 채널의 현재 위치로 읽고 쓰면 위치 경쟁이 생길 수 있다. 특정 위치의 I/O 오버로드를 쓰거나 채널 소유를 분리한다. 디스크에 내구성이 필요한 기록은 close()만으로 사고 대비가 끝났다고 가정하지 말고 force(...)와 파일 시스템 요구를 검토한다.