우와한 개발자

[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 이벤트 발생 시 이벤트가 유실된 것이므로 별도 처리 필요

 

블로그의 정보

우와한개발자 님의 블로그

우와한개발자

활동하기