CodeGym /행동 /C# SELF /손상된 파일과 인코딩

손상된 파일과 인코딩

C# SELF
레벨 38 , 레슨 3
사용 가능

1. 도입

파일 작업이 때때로 (꽤나 수수께끼 같은) 오류들과 마주치는 불쾌한 상황으로 바뀔 때가 있어요. 만약 System.Text.DecoderFallbackException 같은 오류를 본 적이 있다면, 이 주제에 대해 이미 체감해봤을 거예요!

이 강의에서 다룰 내용:

  • .NET에서 발생하는 인코딩 관련 오류 종류;
  • 손상되었거나 잘못된 파일이 어떻게 드러나는지;
  • 그런 오류를 잡고 처리하는 실제 예제;
  • 다른 사람이 준 파일(또는 오래된 디스크에서 찾은 “복고풍” 파일)을 다룰 때 주의할 점.

요약하자면, ASCII가 너무 단순하고 Unicode가 너무 똑똑해 보일 때도, 아무도 못 읽는 파일이 가끔 등장합니다. 그때 예외가 나죠.

왜 이런 일이 생길까?

StreamReader로 파일을 열 때 인코딩을 지정하거나 기본 인코딩을 쓰면, .NET은 파일의 모든 바이트가 해당 인코딩으로 올바르게 문자로 변환될 수 있다고 가정합니다. 그런데 파일에 그 인코딩에서 아무 문자에도 대응하지 않는 바이트가 섞여 있으면 디코딩 오류가 발생해요.

2. 잘못된 인코딩으로 파일을 읽을 때의 예외

가장 흔한 예외 — DecoderFallbackException

이 예외는 .NET이 기대한 인코딩에서 바이트 시퀀스를 어떤 문자와도 매칭할 수 없을 때 던집니다.

간단한 예제로 명확히 해보자:


// 예를 들어, 오래된 파일이 Windows-1251(키릴 문자)로 저장되어 있다고 하자
string win1251File = "win1251_test.txt";
File.WriteAllText(win1251File, "안녕, 세상!", Encoding.GetEncoding("windows-1251"));

try
{
    // 이 파일을 UTF-8로 읽으려 시도해보자
    using var reader = new StreamReader(win1251File, Encoding.UTF8);
    string content = reader.ReadToEnd();
    Console.WriteLine(content); // ...그리고 깨진 문자들이 나오거나 예외가 날 수 있음
}
catch (DecoderFallbackException ex)
{
    Console.WriteLine("디코딩 오류: " + ex.Message);
}

대부분의 경우 Windows-1251로 저장된 파일을 UTF-8로 읽으면 의미있는 텍스트 대신에 문자 깨짐이 나옵니다. 기본적으로 StreamReader는 이런 상황에서 예외를 던지지 않고, 이해할 수 없는 바이트 자리에 대체 문자 "�"를 넣습니다. 하지만 명시적으로 DecoderExceptionFallback 같이 엄격한 설정을 해두었거나 스트림에 특히 “소화 불가능한” 바이트가 있으면 DecoderFallbackException이 발생합니다.

DecoderFallbackException 자세히 보기

  • 언제 발생하나: 현재 인코딩으로 변환할 수 없는 바이트 시퀀스를 읽으려 할 때.
  • 해결책: 올바른 인코딩으로 읽으세요! 파일 인코딩을 모르면 BOM이나 파일 이름으로 추측해 보거나, 파일 만든 사람에게 물어보세요.

3. 명백히 손상된 파일 예제

이제 상황을 좀 더 복잡하게 해봅시다. 파일이 손상되어 바이트 시퀀스 사이에 잘린 문자 조각이 있을 수 있습니다. 이런 건 파일 쓰기가 중단되었거나 네트워크 오류, 잘못된 변환 또는 실수로 파일을 잘라먹었을 때 종종 발생합니다.

“손상된” 파일을 만들어보자


// 유효한 문자열을 UTF-8 바이트로 만들기
byte[] valid = Encoding.UTF8.GetBytes("안녕, 세상!");
// 이제 잘못된 바이트 배열을 만들자 (문자의 일부를 잘라냄)
byte[] corrupted = new byte[valid.Length - 1];
Array.Copy(valid, corrupted, valid.Length - 1); // 마지막 바이트를 잘라냄
        
// 파일로 저장
File.WriteAllBytes("corrupted.txt", corrupted);

try
{
    using var reader = new StreamReader("corrupted.txt", Encoding.UTF8);
    string s = reader.ReadToEnd();
    Console.WriteLine("읽어온 텍스트: " + s);
}
catch (DecoderFallbackException ex)
{
    Console.WriteLine("파일이 손상됐어! " + ex.Message);
}

결과: .NET은 마지막 문자를 제대로 복원하지 못합니다. 기본 설정이면 그 자리를 특수 문자 "�" (또는 "?")로 채우고, 특정 인코딩 설정에서는 DecoderFallbackException을 던집니다.

4. Fallback 전략: 예외를 피할 수 있을까?

때로는 문자가 이해할 수 없을 때 예외를 던지기보다 "?" 같은 문자로 대체하거나 다른 처리를 하고 싶을 수 있습니다. .NET에는 이런 상황을 위한 여러 fallback 전략이 있습니다.

예제: 예외 대신 대체 문자 사용하기


// UTF-8에 적합하지 않은 시퀀스를 포함한 바이트 배열
byte[] data = { 0xD0, 0x9F, 0xD1, 0x80, 0xD0, 0xB8, 0xD0, 0xB2, 0xD0, 0xB5, 0xD1, 0x82, 0xD1 }; // 마지막 바이트가 잘려있음
File.WriteAllBytes("broken_utf8.txt", data);

// Fallback 전략: 문제 문자를 물음표로 대체
var encodingWithFallback = Encoding.GetEncoding(
    "UTF-8",
    new EncoderReplacementFallback("?"),
    new DecoderReplacementFallback("?")
);

using var reader = new StreamReader("broken_utf8.txt", encodingWithFallback);
string s = reader.ReadToEnd();
Console.WriteLine("텍스트 (오류 대체 적용): " + s);

결과: 파일에서 읽은 문자열의 알 수 없는 문자가 "?"로 대체되어 프로그램이 중단되지 않습니다. 다만 원래의 정확한 텍스트는 아니죠.

5. BOM 문제와 불일치

정리하자면, BOM은 파일 시작 부분의 특별한 바이트 시퀀스로, “이 파일은 이런 인코딩이다!”라고 알려주는 역할을 합니다.

BOM이 골칫거리가 될 때

  • 파일에 BOM이 있는데 애플리케이션이 이를 처리하지 못하면 첫 문자가 이상하게 보일 수 있습니다(예: "" 또는 보이지 않는 문자).
  • BOM이 없으면 인코딩을 잘못 추측하게 되는 경우도 있습니다.

BOM 관련 예외

대부분의 경우 C#은 파일 읽을 때 BOM을 알아서 처리하지만, 잘못된 인코딩을 지정하거나 BOM을 수동으로 잘라내면:

  • 처음에 이상한 문자가 나타날 수 있습니다(예: "�");
  • 인코딩이 예외를 던지도록 설정되어 있으면 BOM을 잘못된 바이트 시퀀스로 판단해 예외가 발생할 수 있습니다.

실용 팁: 가능하면 읽기/쓰기를 할 때 인코딩을 명시적으로 지정하세요. 인코딩 종류가 중요하면 항상 명시하는 게 안전합니다.

6. 다른 흥미로운 예외와 시나리오

잘못된 인코딩으로 쓰기

문자열에 포함된 문자가 선택한 인코딩에 없을 때 문제가 생깁니다. 예를 들어 이모지 “😊”를 Encoding.ASCII로 저장하려고 하면:


try
{
    using var writer = new StreamWriter("ascii.txt", false, Encoding.ASCII);
    writer.WriteLine("이건 테스트야 😊");
}
catch (EncoderFallbackException ex)
{
    Console.WriteLine("인코딩 오류: " + ex.Message);
}

결과: EncoderFallbackException이 발생하거나, 대체 문자 "?"로 바뀔 수 있습니다 — 선택한 fallback 전략에 따라 다릅니다.

인코딩 간 변환 문제(데이터 손실)

파일을 다른 인코딩으로 변환할 때 대상 인코딩에 원본의 모든 문자가 없으면 일부 데이터가 손실될 수 있습니다(예: UTF-8에서 Windows-1251로 변환할 때 일본어가 포함되어 있으면 손실 발생).

디스크, 네트워크, 또는 “수동 편집”으로 인한 파일 손상

디스크 오류나 바이너리 파일을 텍스트 에디터로 잘못 편집하는 등의 이유로 파일에 랜덤 바이트가 끼어들면, 그런 파일을 읽을 때 디코딩 예외가 자주 발생합니다.

7. 실전에서 오류를 잡고 처리하는 방법

오류는 파일 작업의 여러 단계에서 발생할 수 있으니, 권장사항은 다음과 같습니다:

  • try-catch 블록을 사용해 예외를 잡으세요 — 특히 DecoderFallbackExceptionEncoderFallbackException을 우선적으로 처리하세요.
  • 사용자에게 친절하게 알리세요: 파일이 손상됐거나 인코딩이 다르면 이상한 텍스트를 보여주느니 알려주는 게 낫습니다.
  • 가능하면 자동으로 인코딩을 감지하도록 하되(예: BOM이나 Ude 같은 라이브러리 사용), 실패하면 항상 사용자가 인코딩을 선택할 수 있게 하세요.

일반적인 코드 구조 예:


try
{
    using var reader = new StreamReader("file.txt", Encoding.GetEncoding("windows-1251"));
    string s = reader.ReadToEnd();
    Console.WriteLine(s);
}
catch (DecoderFallbackException ex)
{
    Console.WriteLine($"파일을 읽을 수 없었어: {ex.Message}");
    // 사용자에게 다른 인코딩을 시도해 보라고 제안할 수 있음
}
catch (IOException ex)
{
    Console.WriteLine($"입출력 오류: {ex.Message}");
}

8. 자주 범하는 실수들

UTF-8 파일을 Windows-1251로 읽으려 하기: 최선의 경우 문자 깨짐을 보게 되고, 최악의 경우(인코딩이 예외를 던지도록 설정한 경우) 예외가 발생합니다.

러시아어 텍스트를 포함한 파일을 ASCII로 쓰기: 영어 알파벳이 아닌 모든 문자는 "?"로 바뀌거나 EncoderFallbackException을 유발합니다.

BOM이 없는 파일을 UTF-8로 읽는데 실제론 UTF-16인 경우: 엉뚱한 문자들이 출력되거나 파일을 읽을 수 없게 됩니다.

명시적 인코딩 없이 신뢰할 수 없는 소스의 파일 열기: 파일이 "오류 없이 열렸다" 하더라도 결과가 올바르다는 보장은 없습니다. 항상 주의하세요.

2
과제
C# SELF, 레벨 38, 레슨 3
잠금
잘못된 인코딩으로 파일 읽기
잘못된 인코딩으로 파일 읽기
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION