1. 소개
가장 중요한 질문부터 시작하죠 — 왜 인코딩을 신경 써야 할까요? 겉으로 보면 파일 하나, 인코딩 하나로 끝날 것 같지만 현실은 훨씬 더 복잡해요. 파일은 어디서든 올 수 있고 예상 못 한 인코딩일 수 있습니다. 그런데 여러분의 애플리케이션은 완전히 다른 걸 기대하고 있겠죠.
가끔은 특정 API나 시스템과 통합해야 해서 그쪽에서 엄격하게 정해진 인코딩만 받아들이기도 해요. 또는 10년 된 옛 파일을 꺼내 새 에디터로 열었더니 문자가 깨져 있는 경우도 있고요. 혹은 CSV 리포트를 저장하는데, 동료들이 Windows, Mac, Excel 등에서 문제없이 열 수 있게 하고 싶을 때도 있습니다.
결론적으로 재인코딩은 드문 일이 아니라 아주 현실적이고 자주 마주치는 작업입니다: DB 덤프, 압축 파일, 회계 시스템 통합, 자동화 스크립트 등 어디서든 튀어나와요. 일찍 정리해두면 나중에 깜짝 놀랄 일이 줄어듭니다.
옛 인코딩에서 새 인코딩으로
파일을 재인코딩하려면 두 단계가 필요합니다:
- 읽기: 원본 인코딩으로 파일을 읽어 문자열(또는 문자)을 얻는다.
- 쓰기: 그 문자열을 명시적으로 원하는 대상 인코딩으로 새 파일에 쓴다.
비유하자면 프랑스어 책을 러시아어로 번역하는 것과 비슷합니다. 먼저 프랑스어를 읽을 수 있어야(텍스트를 읽고), 그 다음 같은 의미를 러시아어로 표현해야(텍스트를 쓰기) 하죠.
C#(.NET)에서는 StreamReader (읽기)와 StreamWriter (쓰기) 생성자에 적절한 Encoding 객체를 설정해서 이걸 구현합니다.
인코딩 개요와 클래스 Encoding
모든 “마법 같은” 문자 변환은 System.Text.Encoding 클래스로 처리됩니다.
주요 인코딩과 C#에서 얻는 방법을 표로 정리하면 다음과 같습니다:
| 인코딩 | 설명 | C# 상수 |
|---|---|---|
| UTF-8 | 범용, 기본적으로 BOM 없음 | |
| UTF-8 с BOM | 같지만 BOM 시그니처 포함 | |
| UTF-16 (LE/BE) | 'Wide char', little-endian | Encoding.Unicode (LE), Encoding.BigEndianUnicode (BE) |
| ASCII | 7비트 고전 | |
| Windows-1251 | 키릴 문자에 흔히 사용됨 | |
| ISO-8859-1 | 라틴 문자(유럽) | |
참고: 오래된 인코딩은 Encoding.GetEncoding을 사용해야 합니다. 숫자(1251)로 지정하거나, 문자열("windows-1251")로 지정할 수 있어요.
2. 재인코딩: C#에서 단계별 전략
실제로 재인코딩을 어떻게 구현하는지 살펴봅시다.
알고리즘
- 원본 파일을 StreamReader로 열고 초기 인코딩을 명시한다.
- 텍스트를 읽는다(전체 또는 라인 단위 — 파일 크기에 따라 선택).
- 출력 파일을 StreamWriter로 열고 원하는 대상 인코딩을 지정한다.
- 텍스트를 출력 파일에 쓴다.
- 스트림 닫는 것을 잊지 말자 — 안정성을 위해 using을 사용하자!
예제: Windows-1251 → UTF-8 재인코딩
예를 들어 원본이 "input-1251.txt" (Windows-1251)이고, 우리는 "output-utf8.txt"를 BOM 없는 순수 UTF-8로 만들고 싶다고 합시다.
// 원본 인코딩을 지정
using var reader = new StreamReader("input-1251.txt", Encoding.GetEncoding(1251));
using var writer = new StreamWriter("output-utf8.txt", false, Encoding.UTF8);
string line;
// 큰 파일에는 라인 단위로 읽는 게 편하다
while ((line = reader.ReadLine()) != null)
{
writer.WriteLine(line);
}
Console.WriteLine("파일을 Windows-1251에서 UTF-8로 성공적으로 변환했습니다!");
이 코드는 각 줄이 한 인코딩에서 다른 인코딩으로 올바르게 변환되도록 보장합니다.
개념적으로는 다음과 같습니다:
┌───────────────────────────────┐ ┌───────────────────┐ ┌───────────────────────────────┐
│ 파일 "input-1251.txt" (1251) │ --> │ StreamReader │ --> │ 메모리의 string 문자열 │
└───────────────────────────────┘ │ (Encoding 1251) │ └───────────────────────────────┘
└───────────────────┘
│
▼
┌────────────────────────┐
│ StreamWriter │
│ (Encoding UTF-8) │
└───────────┬────────────┘
│
▼
┌──────────────────────────────┐
│ "output-utf8.txt" (UTF-8) │
└──────────────────────────────┘
3. 실전 예제: 인코딩 컨버터
조금 복잡하게 해서, 사용자가 원본 파일과 대상 파일, 그리고 인코딩을 직접 선택할 수 있는 간단한 컨버터 프로그램을 만들어봅시다.
Console.WriteLine("원본 파일 경로를 입력하세요:");
string inputPath = Console.ReadLine();
Console.WriteLine("원본 파일 인코딩 (예: 1251, utf-8):");
string sourceEncodingName = Console.ReadLine();
Console.WriteLine("대상 파일 경로를 입력하세요:");
string outputPath = Console.ReadLine();
Console.WriteLine("저장할 인코딩 (예: utf-8, 1251):");
string destEncodingName = Console.ReadLine();
Encoding sourceEncoding = Encoding.GetEncoding(sourceEncodingName);
Encoding destEncoding = Encoding.GetEncoding(destEncodingName);
using var reader = new StreamReader(inputPath, sourceEncoding);
using var writer = new StreamWriter(outputPath, false, destEncoding);
string line;
while ((line = reader.ReadLine()) != null)
{
writer.WriteLine(line);
}
Console.WriteLine("완료! 결과를 확인해보세요.");
팁: 어떤 인코딩을 지정해야 할지 확실하지 않으면 문서나 에디터를 참고하거나 여러 가지를 시도해보세요. 때때로 파일의 인코딩은 누군가 README를 남기지 않았으면 실험으로만 확인할 수 있습니다.
4. 중요한 포인트: BOM, "보이지 않는" 문자와 특수 상황
UTF-8 저장 시 BOM 추가하려면?
기본적으로 Encoding.UTF8는 C#에서 BOM 없이 파일을 생성합니다. BOM을 포함해서 만들고 싶다면 다음을 사용하세요:
// true — 즉 "BOM 포함"
var utf8WithBom = new UTF8Encoding(true);
using var writer = new StreamWriter("with-bom.txt", false, utf8WithBom);
writer.WriteLine("BOM이 포함된 텍스트");
이 파일은 이제 보이지 않는 세 바이트로 시작합니다: 0xEF, 0xBB, 0xBF.
BOM이 문제를 일으키는 경우
- CSV를 Excel로 내보낼 때, 해외 버전의 Excel은 BOM 때문에 이상한 문자가 보일 수 있습니다.
- Unix 계열(Linux)에서는 BOM이 파일 처리에 문제를 일으킬 때가 있습니다.
- JSON 파일에서는 거의 항상 BOM이 문제를 일으킵니다. 많은 파서가 BOM을 못 처리해요!
팁: BOM이 필요한지 여부를 의식적으로 결정하고, 꼭 필요한 경우에만 추가하세요.
숨은 문자와 매직 넘버
재인코딩한 파일을 열었을 때 형식 오류가 나면, 파일 시작 부분의 보이지 않는 문자(BOM 등)를 고려하지 않았거나, 잘못된 인코딩으로 읽었을 가능성이 큽니다(예: ASCII로 읽었는데 실제로는 1251인 경우). 애플리케이션/동료/서버가 사용하는 인코딩을 항상 확인하세요.
5. 유용한 팁
큰 파일 다루기: 왜 라인 단위로 읽는가?
다음처럼 간단히 쓸 수도 있죠:
string allText = File.ReadAllText("input.txt", Encoding.GetEncoding(1251));
File.WriteAllText("output.txt", allText, Encoding.UTF8);
이 방법은 작은 파일이나 중간 크기 파일(예: 50MB 정도까지)에 적합합니다. 하지만 파일이 큰 경우 전체 텍스트를 메모리에 올리면 문제가 됩니다 — 수 GB짜리 파일이라면 메모리가 꽉 찹니다. 그래서 범용적으로는 라인 단위로 읽고 쓰는 방법을 권합니다.
덜 흔한 인코딩 간 재인코딩
예를 들어 ISO-8859-1로 된 파일이 있고 이를 Unicode로 바꿔야 한다면:
var sourceEnc = Encoding.GetEncoding("iso-8859-1");
var destEnc = Encoding.UTF8;
using var reader = new StreamReader("data-latin.txt", sourceEnc);
using var writer = new StreamWriter("data-unicode.txt", false, destEnc);
string line;
while ((line = reader.ReadLine()) != null)
{
writer.WriteLine(line);
}
이 방식은 .NET에서 지원하는 어떤 인코딩에도 적용됩니다.
바이너리 파일을 재인코딩하면 안 됩니다!
위에서 설명한 프로그램은 텍스트 파일 전용입니다. 파일이 바이너리(이미지, 오디오, 아카이브 등)라면 텍스트로 읽어 쓰면 바이트가 깨져 파일이 망가집니다. 바이너리 파일에는 “인코딩”이라는 개념 자체가 적용되지 않습니다.
매핑 표: 어떤 인코딩을 언제 쓰는가
| 인코딩 | 언제 사용? |
|---|---|
| UTF-8 | 범용 파일, 웹, 현대 앱 |
| UTF-8 c BOM | Notepad, Excel 호환을 위해 |
| Windows-1251 | 구시대(legacy) 러시아 로컬 앱용 |
| ASCII | 영문만 있는 파일 |
| UTF-16 | 특수한 경우, 특이한 앱용 |
보조 팁과 요령
파일 인코딩은 어떻게 알 수 있나?
- Notepad++, Visual Studio Code 같은 에디터는 보통 인코딩을 탐지해서 표시해줍니다.
- BOM이 없고 텍스트가 “겉보기엔” 정상인데 문자가 맞지 않다면 인코딩이 일치하지 않는 경우입니다.
인코딩 자동 판별을 할 수 있나?
- .NET에는 모든 파일의 인코딩을 100% 판별하는 마법은 없습니다. 보통 규칙 기반으로: BOM이 있으면 그게 우선이고, 없으면 내용으로 추측하거나 여러 인코딩을 시도해봐야 합니다.
일부 줄은 정상이고 일부는 깨져 보이면?
- 파일이 섞여서 저장되었거나 손상되었을 가능성이 있습니다. 여러 프로그램이 섞여서 파일을 작성했는지 확인하세요.
6. 흔한 실수와 회피 방법
이해를 돕기 위해 자주 발생하는 실수를 살펴봅시다:
잘못된 원본 인코딩. 파일이 UTF-8인 줄 알았는데 실제로는 Windows-1251이면, 키릴 문자가 계단식으로 깨집니다. 확실하지 않다면 Notepad++ 같은 에디터로 인코딩을 확인하세요.
BOM을 잘못 다룸. BOM을 추가하면 일부 파서가 망가질 수 있고, 반대로 BOM이 없으면 다른 소프트웨어가 인코딩을 잘못 판단할 수 있습니다.
파일을 메모리에 한 번에 로드. 큰 파일은 라인 단위로 처리하세요 — 그렇지 않으면 메모리를 전부 사용해 버릴 수 있습니다.
인코딩 지정 없이 쓰기. 기본적으로 StreamWriter는 BOM 없는 UTF-8을 사용합니다. 다른 걸 원하면 명시적으로 인코딩을 지정하세요.
GetEncoding를 잘못 사용. 예를 들어 "utf8" 대신 "utf-8"처럼 정확한 이름을 써야 합니다. 잘못된 이름을 쓰면 예외가 발생합니다. 정확한 이름이나 코드(예: 1251)를 사용하세요.
GO TO FULL VERSION