1. 소개
회계 담당자로부터 "Windows-1251"로 저장된 보고서 파일을 받았다고 가정해 봅시다(키릴 문자용의 오래된 인코딩). 여러분의 Java 애플리케이션은 모든 입력 데이터를 "UTF-8"로 기대하기 때문에 그렇지 않으면 오류가 나거나 글자가 깨져 보일 수 있습니다. 혹은 "ISO-8859-1"만 받는 시스템과 연동해야 할 수도 있죠. 어떻게 할까요? 물론, 파일을 재인코딩해야 합니다!
재인코딩은 한 인코딩으로 저장된 파일에서 텍스트를 읽어 다른 인코딩으로 저장하는 과정입니다. 마치 받은 사람이 정확히 이해할 수 있도록 편지를 다른 언어로 옮겨 적는 것과 같습니다.
Java에서 재인코딩이 동작하는 방식: 일반적인 접근
Java에서 문자열(String)은 프로그램 내부에서 항상 Unicode(UTF‑16)로 저장됩니다. 즉, 파일에서 텍스트를 읽어 들이면 이미 “범용” 형태가 되므로 지원되는 어떤 인코딩으로든 다시 기록할 수 있습니다. 따라서 재인코딩은 간단합니다:
- 올바른 원본 인코딩을 지정해 파일을 문자열로 읽습니다.
- 그 문자열을 새 파일로 쓸 때 원하는 대상 인코딩을 명시합니다.
개념도:
[인코딩 A의 파일] --(Charset A로 읽기)--> [메모리의 String] --(Charset B로 쓰기)--> [인코딩 B의 파일]
2. 재인코딩 단계별 알고리즘
1단계. 원본 인코딩과 대상 인코딩 결정
원본 인코딩은 원본 파일이 저장된 인코딩입니다(예: "Windows-1251"). 대상 인코딩은 결과를 얻고자 하는 인코딩입니다(예: "UTF-8").
2단계. 해당 인코딩으로 읽기 스트림 열기
Files.newBufferedReader(Path, Charset) 또는 고전적인 InputStreamReader를 사용합니다.
3단계. 해당 인코딩으로 쓰기 스트림 열기
Files.newBufferedWriter(Path, Charset) 또는 고전적인 OutputStreamWriter를 사용합니다.
4단계. 줄을 읽어 새 파일에 쓰기
줄 단위로 읽고(파일이 작다면 전체를 읽어도 됨), 각 줄을 새 파일에 기록합니다.
5단계. 스트림 닫기(try-with-resources 사용 권장)
이렇게 하면 리소스를 올바르게 해제할 수 있습니다. try-with-resources 구문을 사용하세요.
3. 코드 예시: 재인코딩 Windows-1251 → UTF-8
Windows‑1251에서 UTF‑8로 파일을 재인코딩하는 간단한 프로그램을 구현해 봅시다. 특히 러시아어 데이터와 작업할 때 현실에서 자주 마주치는 사례입니다.
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
public class FileReencoder {
public static void main(String[] args) throws IOException {
// 파일 경로
Path inputPath = Paths.get("input-1251.txt");
Path outputPath = Paths.get("output-utf8.txt");
// 원본 인코딩 Windows-1251로 reader 열기
try (
BufferedReader reader = Files.newBufferedReader(inputPath, Charset.forName("Windows-1251"));
BufferedWriter writer = Files.newBufferedWriter(outputPath, StandardCharsets.UTF_8)
) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine(); // 줄바꿈을 잊지 마세요!
}
}
System.out.println("파일이 Windows-1251에서 UTF-8로 성공적으로 재인코딩되었습니다!");
}
}
여기서는 읽기용 인코딩(Charset.forName("Windows-1251"))과 쓰기용 인코딩(StandardCharsets.UTF_8)을 명시적으로 지정합니다. 그런 다음 try-with-resources를 사용해 오류가 발생해도 스트림이 자동으로 닫히도록 합니다. writer.newLine()은 줄바꿈을 추가해 파일의 구조를 보존합니다.
4. 중요한 포인트와 팁
파일의 원본 인코딩은 어떻게 알 수 있나요?
- 파일 자체에는 인코딩을 나타내는 “라벨”이 없습니다(예외는 BOM이지만 항상 있는 것은 아님).
- 파일을 여러분이 만들었다면 저장할 때 사용했던 같은 인코딩을 사용하세요.
- 어디선가 받은 파일이라면 인코딩을 보여주는 에디터(예: Notepad++, Visual Studio Code)로 열어 보세요.
- Linux에서는 file -i file_name 명령을 사용할 수 있지만, 항상 정확하게 인코딩을 판별하지는 않습니다.
잘못된 원본 인코딩을 지정하면 어떻게 되나요?
글자가 깨지거나 일부 문자가 손실됩니다. 예를 들어 Windows‑1251로 저장된 파일을 UTF‑8로 읽으면 러시아어 문자가 "Привет"처럼 보이는 깨진 문자열로 변할 수 있습니다.
예외 처리
파일 작업에서는 항상 오류가 발생할 수 있습니다: 파일을 찾을 수 없음, 권한 부족, 잘못된 인코딩 등. 예외를 처리(try-catch)하거나 상위로 던지기(throws)를 사용해 애플리케이션이 “이유도 모른 채” 중단되지 않도록 하세요.
대용량 파일 작업
파일이 매우 큰 경우(기가바이트 단위)에는 예시처럼 줄 단위로 읽고 쓰세요. 파일 전체를 한 번에 메모리로 읽지 마세요 — OutOfMemoryError가 발생할 수 있습니다.
실시간(스트리밍) 재인코딩
파일이 아주 크다면 중간 파일을 만들지 않고도 “실시간”으로 처리할 수 있습니다 — 예를 들어 네트워크에서 들어오는 데이터를 스트리밍 처리할 때 그렇게 할 수 있습니다.
5. 파일 재인코딩에서 흔한 실수
오류 №1: 잘못된 원본 인코딩. 원본 인코딩을 잘못 지정하면 결과가 좋지 않습니다: 러시아어 “안녕”에 해당하는 단어가 "Привет"처럼 보일 수 있습니다. 파일이 어떤 인코딩으로 생성되었는지 항상 확인하세요.
오류 №2: 시스템 기본 인코딩의 암묵적 사용. 인코딩을 명시하지 않으면 Java는 시스템 기본값(System.getProperty("file.encoding"))을 사용합니다. 이로 인해 컴퓨터마다 결과가 달라질 수 있습니다(예: Windows — cp1251, Linux — UTF‑8).
오류 №3: 파일 전체를 메모리로 읽고 쓰기. 대용량 파일에 이런 접근을 하면 메모리가 고갈됩니다. 줄 단위로 읽고 쓰세요.
오류 №4: 처리되지 않은 예외. 파일이 존재하지 않거나 다른 프로세스가 사용 중이거나 손상된 문자가 포함될 수 있습니다. 항상 예외를 처리하거나 try-with-resources를 사용하세요.
오류 №5: 바이너리 파일 재인코딩. 이미지, PDF, 아카이브 등 바이너리 파일을 재인코딩하려고 하지 마세요! 파일이 손상됩니다. 재인코딩은 텍스트 파일에만 의미가 있습니다.
GO TO FULL VERSION