[JAVA] Java Files API를 활용한 텍스트 및 객체 입출력
개요
Java에서는 NIO를 사용해 다양한 방식으로 파일 입출력을 할 수 있다.
그 중 Java의 java.nio.file.Files
클래스는 텍스트, 바이너리, 객체 데이터를 간단히 읽고 쓸 수 있는 다양한 메서드를 제공한다.
본 글에서는 텍스트 파일 입출력, 바이트 단위 입출력, 객체 직렬화/역직렬화 입출력을 주요 키워드로 다룰 예정이다.
이 중 파일에 데이터를 Read, Write 할때 처리 단위에 따라 방법이 나뉘게 된다.
처리 단위는 크게 3가지로 구분된다.
- 문자열(
String
): 사람이 읽을 수 있는 형태 - 바이트(
byte
): 기계가 이해할 수 있는 원시 데이터 - 객체(
Object
): 자바 프로그래밍에서 구조화된 상태 그대로 데이터를 저장하거나 복원하는 방법
위 문서에서 입출력 메소드를 자세하게 확인이 가능하다.
문자열 단위 처리
readAllLines()
readAllLines()
메서드는 텍스트 파일을 한 줄씩 읽어, 각 줄을 String
으로 만들고, 모든 줄을 List<String>
형태로 반환한다.
즉, 파일에 줄이 10줄 있다면, List
안에 10개의 문자열이 생성된다.
주의할점은 메모리 기준으로 동작하기 때문에, 파일이 크다면 전체 내용을 한번에 메모리에 올라가는데 시간이 오래걸린다.
Path path = Paths.get("example.txt");
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
for (String line : lines) {
System.out.println(line);
}
Files.write()
Files.write()
메서드는 문자열의 컬렉션을 받아서 각각 파일의 한 줄로 작성한다.
즉, List<String>
안에 3개의 문자열이 있다면, 파일에는 3줄이 쓰이게 된다.
List<String> data = List.of("Hello", "Java", "File IO");
Files.write(Paths.get("output.txt"), data, StandardCharsets.UTF_8);
byte 단위 처리
위 문자열 단위로 처리할때는 텍스트 파일의 경우 문제가 없으나, 이미지, 음악, 바이너리 파일과 같은 기계가 해석하는 원시 데이터의 경우 문제가 발생한다.
newInputStream()
newInputStream(Path)
메서드는 파일에서 바이트 단위로 데이터를 읽을 수 있도록 InputStream
을 반환한다.
inputStream
은 read()
메서드를 통해 파일 내용을 byte 단위로 읽는다.
텍스트가 아닌 이미지, 영상, 음악, 바이너리 파일 등을 처리할 때 사용되며, 문자열처럼 인코딩을 하지 않기 때문에 데이터 손상 없이 원시 데이터 그대로 읽을 수 있다.
읽은 바이트는 배열에 담아 필요에 따라 가공하거나 저장할 수 있다.
Path path = Paths.get("example.bin");
try (InputStream in = Files.newInputStream(path)) {
byte[] buffer = in.readAllBytes(); // 파일 전체를 바이트 배열로 읽음
for (byte b : buffer) {
System.out.printf("0x%02X ", b); // 바이트 16진수 출력
}
}
주의할 점으로는 읽어온 바이트는 문자가 아니기 때문에 이를 문자로 바꾸고 싶다면 명시적으로 인코딩 방식을 지정해야만 한다.
newOutputStream()
newOutputStream()
메서드는 파일에 바이트 단위로 데이터를 출력할 수 있는 OutputStream
을 생성한다.
OutputStream
은 write()
메서드를 통해 파일 내용을 byte 단위로 읽는다.
문자열이 아닌 바이트 배열을 그대로 저장하므로, 파일 형식 그대로 유지하면서 저장할 수 있다.
이미지 파일을 복사하거나, 객체 직렬화 데이터를 저장할 때 사용된다.
byte[] data = { 0x48, 0x65, 0x6C, 0x6C, 0x6F }; // Hello의 ASCII 바이트
Path outputPath = Paths.get("output.bin");
try (OutputStream out = Files.newOutputStream(outputPath)) {
out.write(data); // 바이트 배열 그대로 저장
}
주의할 점으로는 기본적으로 덮어쓰기로 동작하기 때문에 동일한 이름의 파일이 있다면 주의해야한다!!
그리고, 한글, 특수문자는 바이트 단위이기 때문에 인코딩해서 사용해야 한다.
객체 단위 처리
자바에서는 객체를 파일에 저장하거나 다시 불러오기 위해, 객체를 바이트 형태로 변환하는 과정이 필요하다.
이것을 직렬화(Serialization)라고 하며, 반대로 저장된 데이터를 객체로 복원하는 것은 역직렬화(Deserialization)라고 한다.
ObjectOutputStream
ObjectOutputStream
메서드는 객체를 바이트 단위로 변환해 출력할 수 있도록 도와주는 스트림이다.
이 스트림은 OutputStream
과 연결되어 있으며, writeObject()
메서드를 통해 자바 객체를 파일에 저장할 수 있다.
import java.io.*;
import java.nio.file.*;
class Person implements Serializable {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
Path path = Paths.get("person.dat");
Person p = new Person("Alice", 30);
try (OutputStream out = Files.newOutputStream(path);
ObjectOutputStream oos = new ObjectOutputStream(out)) {
oos.writeObject(p); // 객체를 바이트 형태로 저장
}
주의할 점 객체를 저장하기 위해서는 직렬화 인터페이스를 구현해야한다.
ObjectInputStream
ObjectInputStream
메서드는 파일에 저장된 바이트 데이터를 다시 객체로 복원(역직렬화)할 때 사용하는 스트림이다.
역직렬화를 위해서는 ObjectInputStream
과 InputStream
을 연결하고, readObject()
메서드를 호출하면, 저장된 객체가 복원된다.
try (InputStream in = Files.newInputStream(path);
ObjectInputStream ois = new ObjectInputStream(in)) {
Person readPerson = (Person) ois.readObject();
System.out.println(readPerson.name + ", " + readPerson.age);
}