본문으로 건너뛰기
홈
기술
기술 전체
프로그래밍68
컴퓨터 과학63
AI48
웹 개발36
인프라33
데이터31
소프트웨어 공학18
소개
← 목록으로프로그래밍 › Java › 입출력과 네트워크

[NIO] 파일 채널(FileChannel)

목차

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(...)와 파일 시스템 요구를 검토한다.

참고: Oracle FileChannel API, Files.copy API

같은 카테고리의 글