[Java] 파일 입출력 - 입출력 스트림과 직렬화(Serialization)
by 우와한개발자
[입출력 객체와 스트림]
1. 입출력(Input/Output)이란?
- 프로그램 외부의 자원(파일, 네트워크, 메모리 등)과 데이터를 주고받는 과정
- Java 7 이전 File 클래스 중심
| 구분 | 패키지 | 특징 |
| 전통 I/O | java.io | 스트림 기반, 순차 처리 |
| NIO / NIO.2 | java.nio, java.nio.file | 버퍼 기반, 고성능, Path 중심 |
2. NIO.2 기반 파일 처리
- Java 7 이후 도입
- Path 클래스중심
1) Path 객체 생성
- 파일 시스템의 경로를 표현하는 인터페이스
- 문자열 대신 경로 객체를 사용해 운영체제에 독립적
Path path = Path.of("data.txt");
System.out.println("파일 이름: " + path.getFileName());
System.out.println("절대 경로: " + path.toAbsolutePath());
2) Files 클래스의 메서드
Path path = Path.of("data.txt");
// 1. 파일 존재 여부
Files.exists(path)? " 파일존재함":"파일 없음";
// 2. 파일 생성
Files.createFile(path);
// 3. 파일 삭제
Files.delete(path);
// 4. 파일 복사
Path origin = Path.of("origin.txt");
Path copy = Path.of("copy.txt");
Files.copy(origin, copy, StandardCopyOption.REPLACE_EXISTING); // 이미 존재하면 덮어씀.
// 5. 디렉토리 탐색
// Stream은 자원을 사용하므로 꼭 닫기 위해 try-with-resources문 사용.
try(Stream<Path> paths = Files.list(Path.of("."))){
}cathch(IOException e){}
3. 바이트 스트림과 문자 스트림
1) 입출력 스트림(I/O Stream)
- 입출력 스트림은 데이터가 소스(Source)에서 싱크(Sink)로 흐르는 방향성을 가진 데이터 흐름
- 소스(Source): 데이터 출발점
- 싱크(Sink): 데이터 도착점
- 입력 스트림(Input Stream) : 소스(Source)로부터 데이터를 읽는 흐름
- 출력 스트림(Output Stream) : 싱크(Sink)로 데이터를 보내는 흐름
- 노드(Node) : 소스+싱크, 디스크파일, 메모리, 스레드, 파이프 등
⚠️ I/O를 수도관이라고 생각하면
노드 = 물탱크 / 수도 / 싱크대
스트림 = 물이 흐르는 파이프
파일(노드) ----(스트림)---- 프로그램
| 용어 | 설명 |
| Source Stream | 데이터를 읽어오는 스트림 |
| Sink Stream | 데이터를 내보내는 스트림 |
| Byte Stream | 바이트 단위(1byte)로 처리 → 이미지, 영상, 바이너리 파일 |
| Character Stream | 문자 단위(2byte, char)로 처리 → 텍스트 파일 |
| 구분 | Byte Streams | Character Streams |
| Source Streams | InputStream | Reader |
| Sink Streams | OutputStream | Writer |
2) 바이트 스트림
- 바이트 단위(1byte, 8bit)로 데이터를 처리
- 이미지, 영상, 실행파일 등의 이진 데이터 처리
- 바이트 배열의 크기를 지정했을 때, 크기의 경계에서 문자(2byte)를 반만 읽게 되어 문자가 깨지게 되므로 문자열은 문자 스트림으로 읽도록 함.
3) 문자 스트림
- 문자 단위(2byte, char)로 데이터를 처리
- 텍스트 파일을 처리할 때는 반드시 문자 인코딩 명시(보통, UTF-8)
4. 버퍼링과 필터 스트림
1) 노드 스트림 Node Stream
- 자바 입출력(I/O)에서 노드Node : 데이터가 실제로 들어오고 나가는 끝단(출발지/도착지). ****즉,데이터가 존재하거나 이동하는 대상(장치)
- 자바에서는 파일, 메모리, 파이프 3가지의 노드
- 노드 스트림 클래스
| 구분 | Byte Streams | Character Streams |
| File | FileInputStreamFileOutputStream | FileReaderFileWriter |
| Memory : Array | ByteArrayInputStreamByteArrayOutputStream | CharArrayReaderCharArrayWriter |
| Memory : String | – | StringReaderStringWriter |
| Pipe | PipedInputStreamPipedOutputStream | PipedReaderPipedWriter |
2) 필터 스트림 Filter Stream
- 처리 스트림(Processiong Stream)이라고도 함.
- 노드 스트림을 감싸서 기능 추가하는 스트림
- 버퍼, 형변환 등의 기능
파일
↓
FileInputStream ← 노드 스트림
↓
InputStreamReader ← 필터 (바이트 → 문자 변환)
↓
BufferedReader ← 필터 (버퍼 기능)
↓
프로그램
- 필터 스트림 클래스
| Type | Byte Streams | Character Streams | 설명 |
| Buffering | BufferedInputStreamBufferedOutputStream | BufferedReaderBufferedWriter | 버퍼를 사용해 입출력 성능 향상 |
| Data conversion | DataInputStreamDataOutputStream | - | 기본 자료형(int, double 등) 단위로 입출력 |
| Filtering | FilterInputStreamFilterOutputStream | FilterReaderFilterWriter | 다른 스트림을 감싸 기능 확장 |
| Converting bytes → character | - | InputStreamReaderOutputStreamWriter | 바이트 스트림 ↔ 문자 스트림 변환 |
| Object Serialization | ObjectInputStreamObjectOutputStream | - | 객체 직렬화 / 역직렬화 |
| Counting | LineNumberInputStream | LineNumberReader | 읽은 줄 번호 추적 |
| Printing | PrintStream | PrintWriter | println 등 편리한 출력 |
⚠️ FilterXxx 클래스는 추상(abstract) 클래스
[ CSV 처리와 Stream API ]
1. 데이터를 파일에 저장하는 방법
1) 단순 텍스트 형식으로 저장(권장)
- 언어에 종속되지 않음.
- 입출력 속도가 느림
2) 형식화된 바이너리 데이터로 저장(잘 안씀)
- 숫자-문자-숫자라면 writeInt-writeString-writeInt 순으로 읽어야하기 때문에 매우 불편
- DataOutputStream 이용
3) 객체 자체를 파일에 저장(객체 직렬화)
- 클래스 객체 전체를 직렬화(Serialization)하여 저장
- 이후 역직렬화(Deserialization)을 통해 객체로 복원 가능
- 클래스는 반드시 Serializable 인터페이스 구현
2. CSV 파일
- CSV는 쉼표(,)로 구분된 텍스트 형식
- CSV 파일을 읽을 때, Stream API 사용
try(Stream<String> lines =
Files.lines(Path.of("data.csv"),StandardCharsets.UTF-8)){
}cathch(IOException e){}
[ 객체 직렬화 Serialization ]
- 직렬화(Serialization) : 객체를 바이트 스트림 형태로 변환하는 과정
- 역직렬화 : 바이트 스트림을 객체로 복원하는 과정
1. Serializable 인터페이스
- 구현할 메서드가 없고, 이 클래스가 직렬화할 수 있음을 나타내는 표시일뿐(Marker 인터페이스)
public class ObjectExample implements Serializable{
...
}
1) serialVersionUID
- 직렬화 클래스 구조가 변경될 경우 불러오지 못하는 예외를 방지하기 위해 선언
- private static final long serialVersionUID
- 보통 DTO나 VO 선언시 많이 사용
- Serializable을 구현한 경우 거의 노란색 밑줄로 추가하라고 경고뜨니 추가하면됨
2) 객체 저장
- ObjectOutputStream의 writeObject()
- IOException 예외 처리
3) 객체 읽기
- ObjectInputStream의 readObj()
- IOException과 ClassNotFoundException 예외 처리
- 읽어들인건 형변환 필요
4) transient
private transient String password; // 직렬화 제외
- 객체 직렬화에서 제외할 변수에 선언
- 나이와 같이 변할 수 있는 변수에 사용
- record 클래스에서는 사용 불가 → 상태를 두지 않고 메서드로
- public record Person( String name, LocalDate birthDate ) implements Serializable { public int age() { return LocalDate.now().getYear() - birthDate.getYear(); } }
- 쓰레드는 직렬화 불가하여 transient가 붙어있음.
[ 기타 파일 관련 클래스]
1. RandomAccressFile
- 파일의 특정 위치(오프셋)에 접근할 수 있는 클래스
- 파일 포인터 : 현재 읽기/쓰기 위치를 가리키는 커서
- 원하는 위치로 바로 이동할 수 있어서 DB 인덱스 파일이나 바이너리 로그 같은 구조화된 파일 처리에 적합
| 메서드 | 설명 |
| seek(long pos) | 파일 포인터를 pos 위치로 이동 |
| getFilePointer() | 현재 포인터 위치 반환 (bytes) |
| readInt() / writeInt() | 4바이트 정수 읽기 / 쓰기 |
| readUTF() / writeUTF() | UTF-8 문자열 읽기 / 쓰기 |
| length() | 파일 전체 크기 반환 |
RandomAccessFile raf = new RandomAccessFile("data.bin", "rw");
raf.seek(100); // 101번째 바이트로 이동
raf.writeInt(42); // 4바이트 정수 쓰기
raf.seek(100);
int value = raf.readInt(); // → 42
System.out.println(raf.getFilePointer()); // 104
raf.close();
2. FileChannel
- NIO(Non-blocking I/O) 패키지의 채널 기반 파일 I/O 클래스
- 대용량 파일 처리, 메모리 매핑(MappedByteBuffer), 파일 잠금(FileLock) 이 필요할 때 사용
| 구분 | FileInputStream / OutputStream | FileChannel |
| 방식 | 단방향 (읽기 or 쓰기) | 양방향 (읽기 + 쓰기) |
| 버퍼 | 직접 관리 필요 | ByteBuffer 명시적 사용 |
| 속도 | 일반적 | 대용량에서 우수 |
| 메모리 매핑 | ❌ 불가 | ⭕ MappedByteBuffer |
| 파일 잠금 | ❌ 불가 | ⭕ FileLock 지원 |
try (FileChannel channel = FileChannel.open(
Path.of("bigfile.dat"),
StandardOpenOption.READ, StandardOpenOption.WRITE)) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer);
buffer.flip(); // 읽기 모드로 전환
// 메모리 매핑
MappedByteBuffer mbb = channel.map(
FileChannel.MapMode.READ_WRITE, 0, channel.size());
}
3. 파일 변경 감지
1) WatchServie
- 디렉토리(파일X)의 변경 사항을 감지하는 기능을 제공하는 클래스
- 즉, 폴더 안에서 파일이 생성/삭제/수정되는 이벤트를 감지
- 파일이 아닌 디렉토리 단위로만 등록
2) 감지하는 이벤트 종류
- ENTRY_CREATE → 파일 생성
- ENTRY_DELETE → 파일 삭제
- ENTRY_MODIFY → 파일 수정
⚠️ 주의사항
- key.reset()을 빠뜨리면 이후 이벤트를 받을 수 없음
- watcher.take()는 블로킹 호출이므로 별도 스레드에서 실행 권장
- OVERFLOW 이벤트 발생 시 이벤트가 유실된 것이므로 별도 처리 필요
'언어 > Java' 카테고리의 다른 글
| [Java] 정규 표현식(regular expression)과 Pattern/Matcher 클래스 (0) | 2026.03.04 |
|---|---|
| [Java] 문자열 클래스(String, StringBuffer, StringBuilder) (0) | 2026.03.04 |
| [Java] record 클래스와 Lombok 라이브러리 (1) | 2026.03.04 |
| [Java] 라이브러리(Library) & 모듈(module) & 리플렉션(reflection) (0) | 2026.03.04 |
| [Java] 주석(/, /*, /**)과 공백, 식별자와 예약어 (0) | 2026.03.03 |
블로그의 정보
우와한개발자 님의 블로그
우와한개발자