[c#] 모든 파일의 인코딩을 찾는 효과적인 방법

예는 가장 빈번한 질문입니다.이 문제는 저에게 모호하며 그것에 대해 많이 알지 못합니다.

하지만 파일 인코딩을 찾는 매우 정확한 방법을 원합니다. Notepad ++만큼 정확합니다.



답변

StreamReader.CurrentEncoding속성은 나를 위해 올바른 텍스트 파일 인코딩을 거의 반환하지 않습니다. 파일의 바이트 순서 표시 (BOM)를 분석하여 파일의 엔디안을 결정하는 데 더 큰 성공을 거두었습니다. 파일에 BOM이 없으면 파일의 인코딩을 결정할 수 없습니다.

* UTF-32LE 감지를 포함하고 UTF-32BE에 대한 올바른 인코딩을 반환하도록 2020 년 4 월 8 일 업데이트 됨

/// <summary>
/// Determines a text file's encoding by analyzing its byte order mark (BOM).
/// Defaults to ASCII when detection of the text file's endianness fails.
/// </summary>
/// <param name="filename">The text file to analyze.</param>
/// <returns>The detected encoding.</returns>
public static Encoding GetEncoding(string filename)
{
    // Read the BOM
    var bom = new byte[4];
    using (var file = new FileStream(filename, FileMode.Open, FileAccess.Read))
    {
        file.Read(bom, 0, 4);
    }

    // Analyze the BOM
    if (bom[0] == 0x2b && bom[1] == 0x2f && bom[2] == 0x76) return Encoding.UTF7;
    if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) return Encoding.UTF8;
    if (bom[0] == 0xff && bom[1] == 0xfe && bom[2] == 0 && bom[3] == 0) return Encoding.UTF32; //UTF-32LE
    if (bom[0] == 0xff && bom[1] == 0xfe) return Encoding.Unicode; //UTF-16LE
    if (bom[0] == 0xfe && bom[1] == 0xff) return Encoding.BigEndianUnicode; //UTF-16BE
    if (bom[0] == 0 && bom[1] == 0 && bom[2] == 0xfe && bom[3] == 0xff) return new UTF32Encoding(true, true);  //UTF-32BE

    // We actually have no idea what the encoding is if we reach this point, so
    // you may wish to return null instead of defaulting to ASCII
    return Encoding.ASCII;
}


답변

다음 코드는 StreamReader클래스를 사용하여 잘 작동합니다 .

  using (var reader = new StreamReader(fileName, defaultEncodingIfNoBom, true))
  {
      reader.Peek(); // you need this!
      var encoding = reader.CurrentEncoding;
  }

트릭은 Peek호출 을 사용하는 것입니다 . 그렇지 않으면 .NET이 아무 작업도 수행하지 않고 서문 인 BOM을 읽지 않았습니다. 물론 ReadXXX인코딩을 확인하기 전에 다른 호출 을 사용하는 경우 에도 작동합니다.

파일에 BOM이 없으면 defaultEncodingIfNoBom인코딩이 사용됩니다. 이 오버로드 메서드가없는 StreamReader도 있지만 (이 경우 기본 (ANSI) 인코딩이 defaultEncodingIfNoBom으로 사용됨) 컨텍스트에서 기본 인코딩을 고려하는 사항을 정의하는 것이 좋습니다.

UTF8, UTF16 / Unicode (LE & BE) 및 UTF32 (LE & BE) 용 BOM이있는 파일로 이것을 성공적으로 테스트했습니다. UTF7에서는 작동하지 않습니다.


답변

다음 단계를 시도해 보겠습니다.

1) Byte Order Mark가 있는지 확인

2) 파일이 유효한 UTF8인지 확인

3) 로컬 “ANSI”코드 페이지 사용 (Microsoft에서 정의한 ANSI)

2 단계는 UTF8 이외의 코드 페이지에서 대부분의 비 ASCII 시퀀스가 ​​유효한 UTF8이 아니기 때문에 작동합니다.


답변

이것을 확인하십시오.

UDE

이것은 Mozilla Universal Charset Detector의 포트이며 다음과 같이 사용할 수 있습니다.

public static void Main(String[] args)
{
    string filename = args[0];
    using (FileStream fs = File.OpenRead(filename)) {
        Ude.CharsetDetector cdet = new Ude.CharsetDetector();
        cdet.Feed(fs);
        cdet.DataEnd();
        if (cdet.Charset != null) {
            Console.WriteLine("Charset: {0}, confidence: {1}", 
                 cdet.Charset, cdet.Confidence);
        } else {
            Console.WriteLine("Detection failed.");
        }
    }
}


답변

@CodesInChaos에서 제안한 단계에 대한 구현 세부 정보 제공 :

1) Byte Order Mark가 있는지 확인

2) 파일이 유효한 UTF8인지 확인

3) 로컬 “ANSI”코드 페이지 사용 (Microsoft에서 정의한 ANSI)

2 단계는 UTF8 이외의 코드 페이지에서 대부분의 비 ASCII 시퀀스가 ​​유효한 UTF8이 아니기 때문에 작동합니다. https://stackoverflow.com/a/4522251/867248 은 전술을 더 자세히 설명합니다.

using System; using System.IO; using System.Text;

// Using encoding from BOM or UTF8 if no BOM found,
// check if the file is valid, by reading all lines
// If decoding fails, use the local "ANSI" codepage

public string DetectFileEncoding(Stream fileStream)
{
    var Utf8EncodingVerifier = Encoding.GetEncoding("utf-8", new EncoderExceptionFallback(), new DecoderExceptionFallback());
    using (var reader = new StreamReader(fileStream, Utf8EncodingVerifier,
           detectEncodingFromByteOrderMarks: true, leaveOpen: true, bufferSize: 1024))
    {
        string detectedEncoding;
        try
        {
            while (!reader.EndOfStream)
            {
                var line = reader.ReadLine();
            }
            detectedEncoding = reader.CurrentEncoding.BodyName;
        }
        catch (Exception e)
        {
            // Failed to decode the file using the BOM/UT8. 
            // Assume it's local ANSI
            detectedEncoding = "ISO-8859-1";
        }
        // Rewind the stream
        fileStream.Seek(0, SeekOrigin.Begin);
        return detectedEncoding;
   }
}


[Test]
public void Test1()
{
    Stream fs = File.OpenRead(@".\TestData\TextFile_ansi.csv");
    var detectedEncoding = DetectFileEncoding(fs);

    using (var reader = new StreamReader(fs, Encoding.GetEncoding(detectedEncoding)))
    {
       // Consume your file
        var line = reader.ReadLine();
        ...


답변

다음 코드는 일부 cpp 또는 h 또는 ml 파일이 BOM없이 ISO-8859-1 (Latin-1) 또는 UTF-8로 인코딩되는지 확인하는 Powershell 코드입니다. 둘 다 GB18030이라고 가정하지 않습니다. 저는 프랑스에서 일하는 중국인이고 MSVC는 프랑스 컴퓨터에서 Latin-1로 저장하고 중국 컴퓨터에서 GB로 저장하므로 시스템과 동료간에 소스 파일을 교환 할 때 인코딩 문제를 방지하는 데 도움이됩니다.

방법은 간단합니다. 모든 문자가 x00-x7E 사이에 있고 ASCII, UTF-8 및 Latin-1이 모두 동일하지만 ASCII가 아닌 파일을 UTF-8로 읽으면 특수 문자가 표시됩니다. , 그러니 Latin-1로 읽으십시오. Latin-1에서 \ x7F와 \ xAF 사이는 비어 있고 GB는 x00-xFF 사이에서 전체를 사용하므로 둘 사이에 있으면 Latin-1이 아닙니다.

코드는 PowerShell로 작성되었지만 .net을 사용하므로 C # 또는 F #으로 쉽게 변환 할 수 있습니다.

$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding($False)
foreach($i in Get-ChildItem .\ -Recurse -include *.cpp,*.h, *.ml) {
    $openUTF = New-Object System.IO.StreamReader -ArgumentList ($i, [Text.Encoding]::UTF8)
    $contentUTF = $openUTF.ReadToEnd()
    [regex]$regex = '�'
    $c=$regex.Matches($contentUTF).count
    $openUTF.Close()
    if ($c -ne 0) {
        $openLatin1 = New-Object System.IO.StreamReader -ArgumentList ($i, [Text.Encoding]::GetEncoding('ISO-8859-1'))
        $contentLatin1 = $openLatin1.ReadToEnd()
        $openLatin1.Close()
        [regex]$regex = '[\x7F-\xAF]'
        $c=$regex.Matches($contentLatin1).count
        if ($c -eq 0) {
            [System.IO.File]::WriteAllLines($i, $contentLatin1, $Utf8NoBomEncoding)
            $i.FullName
        }
        else {
            $openGB = New-Object System.IO.StreamReader -ArgumentList ($i, [Text.Encoding]::GetEncoding('GB18030'))
            $contentGB = $openGB.ReadToEnd()
            $openGB.Close()
            [System.IO.File]::WriteAllLines($i, $contentGB, $Utf8NoBomEncoding)
            $i.FullName
        }
    }
}
Write-Host -NoNewLine 'Press any key to continue...';
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown');


답변

.NET은 그다지 도움이되지 않지만 다음 알고리즘을 시도해 볼 수 있습니다.

  1. BOM (byte order mark)으로 인코딩을 찾으십시오.
  2. 다른 인코딩으로 구문 분석을 시도하십시오.

전화는 다음과 같습니다.

var encoding = FileHelper.GetEncoding(filePath);
if (encoding == null)
    throw new Exception("The file encoding is not supported. Please choose one of the following encodings: UTF8/UTF7/iso-8859-1");

다음은 코드입니다.

public class FileHelper
{
    /// <summary>
    /// Determines a text file's encoding by analyzing its byte order mark (BOM) and if not found try parsing into diferent encodings       
    /// Defaults to UTF8 when detection of the text file's endianness fails.
    /// </summary>
    /// <param name="filename">The text file to analyze.</param>
    /// <returns>The detected encoding or null.</returns>
    public static Encoding GetEncoding(string filename)
    {
        var encodingByBOM = GetEncodingByBOM(filename);
        if (encodingByBOM != null)
            return encodingByBOM;

        // BOM not found :(, so try to parse characters into several encodings
        var encodingByParsingUTF8 = GetEncodingByParsing(filename, Encoding.UTF8);
        if (encodingByParsingUTF8 != null)
            return encodingByParsingUTF8;

        var encodingByParsingLatin1 = GetEncodingByParsing(filename, Encoding.GetEncoding("iso-8859-1"));
        if (encodingByParsingLatin1 != null)
            return encodingByParsingLatin1;

        var encodingByParsingUTF7 = GetEncodingByParsing(filename, Encoding.UTF7);
        if (encodingByParsingUTF7 != null)
            return encodingByParsingUTF7;

        return null;   // no encoding found
    }

    /// <summary>
    /// Determines a text file's encoding by analyzing its byte order mark (BOM)  
    /// </summary>
    /// <param name="filename">The text file to analyze.</param>
    /// <returns>The detected encoding.</returns>
    private static Encoding GetEncodingByBOM(string filename)
    {
        // Read the BOM
        var byteOrderMark = new byte[4];
        using (var file = new FileStream(filename, FileMode.Open, FileAccess.Read))
        {
            file.Read(byteOrderMark, 0, 4);
        }

        // Analyze the BOM
        if (byteOrderMark[0] == 0x2b && byteOrderMark[1] == 0x2f && byteOrderMark[2] == 0x76) return Encoding.UTF7;
        if (byteOrderMark[0] == 0xef && byteOrderMark[1] == 0xbb && byteOrderMark[2] == 0xbf) return Encoding.UTF8;
        if (byteOrderMark[0] == 0xff && byteOrderMark[1] == 0xfe) return Encoding.Unicode; //UTF-16LE
        if (byteOrderMark[0] == 0xfe && byteOrderMark[1] == 0xff) return Encoding.BigEndianUnicode; //UTF-16BE
        if (byteOrderMark[0] == 0 && byteOrderMark[1] == 0 && byteOrderMark[2] == 0xfe && byteOrderMark[3] == 0xff) return Encoding.UTF32;

        return null;    // no BOM found
    }

    private static Encoding GetEncodingByParsing(string filename, Encoding encoding)
    {
        var encodingVerifier = Encoding.GetEncoding(encoding.BodyName, new EncoderExceptionFallback(), new DecoderExceptionFallback());

        try
        {
            using (var textReader = new StreamReader(filename, encodingVerifier, detectEncodingFromByteOrderMarks: true))
            {
                while (!textReader.EndOfStream)
                {
                    textReader.ReadLine();   // in order to increment the stream position
                }

                // all text parsed ok
                return textReader.CurrentEncoding;
            }
        }
        catch (Exception ex) { }

        return null;    // 
    }
}