Cách hiệu quả để tìm mã hóa của bất kỳ tệp nào


115

Vâng là một câu hỏi thường gặp nhất, và vấn đề này đối với tôi là mơ hồ và vì tôi không biết nhiều về nó.

Nhưng tôi muốn một cách rất chính xác để tìm một tập tin Mã hóa. Chính xác như Notepad ++.



Những bảng mã nào? UTF-8 và UTF-16, endian lớn và nhỏ? Hoặc bạn đang đề cập đến các mã MSDos cũ, chẳng hạn như shift-JIS hoặc Cyrillic, v.v.?
dthorpe

Một khả năng trùng lặp: stackoverflow.com/questions/436220/...
Oded

@Oded: Trích dẫn "Phương thức getEncoding () sẽ trả về mã hóa đã được thiết lập (đọc JavaDoc) cho luồng. Nó sẽ không đoán mã hóa cho bạn.".
Fábio Antunes

2
Đối với một số cách đọc nền, joelonsoftware.com/articles/Unicode.html là một cách đọc tốt. Nếu có một điều bạn nên biết về văn bản, đó là không có thứ gọi là văn bản thuần túy.
Martijn

Câu trả lời:


155

Các StreamReader.CurrentEncodingtài sản hiếm khi trả về tập tin văn bản đúng mã hóa đối với tôi. Tôi đã thành công lớn hơn trong việc xác định độ bền của tệp, bằng cách phân tích dấu thứ tự byte (BOM) của nó. Nếu tệp không có BOM, điều này không thể xác định mã hóa của tệp.

* CẬP NHẬT 4/08/2020 để bao gồm phát hiện UTF-32LE và trả về mã hóa chính xác cho UTF-32BE

/// <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;
}

3
+1. Điều này cũng làm việc cho tôi (trong khi detectorFromByteOrderMarks thì không). Tôi đã sử dụng "FileStream mới (tên tệp, FileMode.Open, FileAccess.Read)" để tránh IOException vì tệp chỉ được đọc.
Polyfun

56
Các tệp UTF-8 có thể không có BOM, trong trường hợp này, nó sẽ trả về ASCII không chính xác.
user626528

3
Câu trả lời này là sai. Nhìn vào các nguồn tài liệu tham khảo cho StreamReader, thực hiện đó là những gì nhiều người sẽ muốn. Họ tạo các mã hóa mới thay vì sử dụng các Encoding.Unicodeđối tượng hiện có , vì vậy việc kiểm tra bình đẳng sẽ không thành công (điều này có thể hiếm khi xảy ra vì ví dụ: Encoding.UTF8có thể trả về các đối tượng khác nhau), nhưng nó (1) không sử dụng định dạng UTF-7 thực sự kỳ lạ, (2) mặc định là UTF-8 nếu không tìm thấy BOM và (3) có thể được ghi đè để sử dụng một mã hóa mặc định khác.
hangar

2
tôi đã thành công tốt hơn với StreamReader mới (filename, true) .CurrentEncoding
Benoit

4
Có một lỗi cơ bản trong mã; khi bạn phát hiện chữ ký UTF32 big-endian ( ), bạn sẽ trả về hệ thống cung cấp , đây là một mã hóa little-endian (như đã lưu ý ở đây ). Ngoài ra, theo ghi nhận của @Nyerguds, bạn vẫn chưa tìm UTF32LE, có chữ ký (theo en.wikipedia.org/wiki/Byte_order_mark ). Như người dùng đã lưu ý, bởi vì nó là cộng gộp, kiểm tra đó phải đến trước kiểm tra 2 byte. 00 00 FE FFEncoding.UTF32FF FE 00 00
Glenn Slayden

44

Mã sau hoạt động tốt đối với tôi, sử dụng StreamReaderlớp:

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

Bí quyết là sử dụng Peekcuộc gọi, nếu không, .NET đã không thực hiện bất kỳ điều gì (và nó không đọc phần mở đầu, BOM). Tất nhiên, nếu bạn sử dụng bất kỳ ReadXXXcuộc gọi nào khác trước khi kiểm tra mã hóa, nó cũng hoạt động.

Nếu tệp không có BOM, thì defaultEncodingIfNoBommã hóa sẽ được sử dụng. Cũng có một StreamReader không có phương thức quá tải này (trong trường hợp này, mã hóa Mặc định (ANSI) sẽ được sử dụng làm defaultEncodingIfNoBom), nhưng tôi khuyên bạn nên xác định những gì bạn coi là mã hóa mặc định trong ngữ cảnh của mình.

Tôi đã kiểm tra điều này thành công với các tệp có BOM cho UTF8, UTF16 / Unicode (LE & BE) và UTF32 (LE & BE). Nó không hoạt động cho UTF7.


Tôi lấy lại những gì được đặt làm mã hóa mặc định. Tôi có thể bị thiếu momething?
Ram

1
@DRAM - điều này có thể xảy ra nếu tệp không có BOM
Simon Mourier

Cảm ơn @Simon Mourier. Tôi không mong đợi tệp pdf / bất kỳ tệp nào của tôi sẽ không có bom. Liên kết này stackoverflow.com/questions/4520184/… có thể hữu ích cho những người cố gắng phát hiện mà không có bom.
Ram

1
Trong powershell, tôi phải chạy $ reader.close (), nếu không nó bị khóa không thể ghi. foreach($filename in $args) { $reader = [System.IO.StreamReader]::new($filename, [System.Text.Encoding]::default,$true); $peek = $reader.Peek(); $reader.currentencoding | select bodyname,encodingname; $reader.close() }
js2010

1
@SimonMourier này không làm việc nếu mã hóa của file làUTF-8 without BOM
Ozkan

11

Tôi sẽ thử các bước sau:

1) Kiểm tra xem có Dấu đơn hàng Byte không

2) Kiểm tra xem tệp có phải là UTF8 hợp lệ không

3) Sử dụng mã "ANSI" cục bộ (ANSI do Microsoft định nghĩa)

Bước 2 hoạt động vì hầu hết các chuỗi không phải ASCII trong các mã khác UTF8 không phải là UTF8 hợp lệ.


Đây có vẻ là câu trả lời đúng hơn, vì câu trả lời khác không phù hợp với tôi. Người ta có thể làm điều đó với File.OpenRead và .Read-ing một vài byte đầu tiên của tệp.
user420667

1
Tuy nhiên, bước 2 là một loạt các công việc lập trình để kiểm tra các mẫu bit.
Nyerguds

1
Tôi không chắc việc giải mã có thực sự ném ra ngoại lệ hay không, hay nếu nó chỉ thay thế các chuỗi không được công nhận bằng '?'. Dù sao thì tôi cũng đã viết một lớp kiểm tra mẫu bit.
Nyerguds

3
Khi bạn tạo một phiên bản của Utf8Encodingbạn, bạn có thể truyền vào một tham số bổ sung để xác định xem có nên đưa ra một ngoại lệ hay không hoặc nếu bạn thích tham nhũng dữ liệu im lặng.
CodesInChaos

1
Tôi thích câu trả lời này. Hầu hết các mã hóa (có thể là 99% các trường hợp sử dụng của bạn) sẽ là UTF-8 hoặc ANSI (Windows codepage 1252). Bạn có thể kiểm tra xem chuỗi có chứa ký tự thay thế (0xFFFD) hay không để xác định xem mã hóa không thành công.
marsze

10

Kiểm tra điều này.

UDE

Đây là một cổng của Mozilla Universal Charset Detector và bạn có thể sử dụng nó như thế này ...

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.");
        }
    }
}

Bạn nên biết rằng Ude là GPL
lindexi

Ok nếu bạn lo lắng về giấy phép thì bạn có thể sử dụng cái này. Được cấp phép dưới dạng MIT và bạn có thể sử dụng nó cho cả phần mềm nguồn mở và nguồn đóng. nuget.org/packages/SimpleHelpers.FileEncoding
Alexei Agüero Alba

Giấy phép là MPL ​​với tùy chọn GPL. The library is subject to the Mozilla Public License Version 1.1 (the "License"). Alternatively, it may be used under the terms of either the GNU General Public License Version 2 or later (the "GPL"), or the GNU Lesser General Public License Version 2.1 or later (the "LGPL").
jbtule

Có vẻ như fork này hiện đang hoạt động mạnh nhất và có gói nuget UDE.Net Standardard. github.com/yinyue200/ude
jbtule

thư viện rất hữu ích, đối phó với rất nhiều bảng mã khác nhau và bất thường! xe tăng!
mshakurov

6

Cung cấp chi tiết triển khai cho các bước do @CodesInChaos đề xuất:

1) Kiểm tra xem có Dấu đơn hàng Byte không

2) Kiểm tra xem tệp có phải là UTF8 hợp lệ không

3) Sử dụng mã "ANSI" cục bộ (ANSI do Microsoft định nghĩa)

Bước 2 hoạt động vì hầu hết các chuỗi không phải ASCII trong các mã khác UTF8 không phải là UTF8 hợp lệ. https://stackoverflow.com/a/4522251/867248 giải thích chiến thuật chi tiết hơn.

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();
        ...

Cảm ơn bạn! Điều này đã giải quyết cho tôi. Nhưng tôi muốn sử dụng chỉ reader.Peek() thay vì while (!reader.EndOfStream) { var line = reader.ReadLine(); }
Harison Silva

reader.Peek()không đọc toàn bộ luồng. Tôi thấy rằng với các luồng lớn hơn, Peek()là không đủ. Tôi đã sử dụng reader.ReadToEndAsync()thay thế.
Gary Pendlebury

Và Utf8EncodingVerifier là gì?
Peter Moore

1
@PeterMoore Đây là mã hóa cho utf8, var Utf8EncodingVerifier = Encoding.GetEncoding("utf-8", new EncoderExceptionFallback(), new DecoderExceptionFallback());Nó được sử dụng trong trykhối khi đọc một dòng. Nếu bộ mã hóa không phân tích được văn bản được cung cấp (văn bản không được mã hóa bằng utf8), Utf8EncodingVerifier sẽ ném ra. Ngoại lệ được giải quyết và sau đó chúng tôi biết văn bản không phải là utf8 và mặc định là ISO-8859-1
Berthier Lemieux

2

Các mã sau là mã Powershell của tôi để xác định xem một số tệp cpp hoặc h hoặc ml có đang mã hóa ISO-8859-1 (Latin-1) hoặc UTF-8 không mà không có BOM hay không, nếu không thì giả sử đó là GB18030. Tôi là người Trung Quốc làm việc tại Pháp và MSVC lưu dưới dạng Latin-1 trên máy tính Pháp và lưu dưới dạng GB trên máy tính Trung Quốc, vì vậy điều này giúp tôi tránh được sự cố mã hóa khi trao đổi tệp nguồn giữa hệ thống của tôi và đồng nghiệp.

Cách làm rất đơn giản, nếu tất cả các ký tự nằm giữa x00-x7E, ASCII, UTF-8 và Latin-1 đều giống nhau, nhưng nếu tôi đọc tệp không phải ASCII bởi UTF-8, chúng tôi sẽ thấy ký tự đặc biệt hiển thị , vì vậy hãy cố gắng đọc bằng Latin-1. Trong Latinh-1, giữa \ x7F và \ xAF trống, trong khi GB sử dụng đầy đủ giữa x00-xFF, vì vậy nếu tôi có bất kỳ cái nào giữa hai, nó không phải là Latinh-1

Mã được viết bằng PowerShell, nhưng sử dụng .net nên dễ dàng được dịch sang C # hoặ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');

2

.NET không hữu ích lắm, nhưng bạn có thể thử thuật toán sau:

  1. cố gắng tìm mã hóa bằng BOM (dấu thứ tự byte) ... rất có thể không tìm thấy
  2. thử phân tích cú pháp thành các bảng mã khác nhau

Đây là cuộc gọi:

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");

Đây là mã:

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;    // 
    }
}


0

Nó có thể hữu ích

string path = @"address/to/the/file.extension";

using (StreamReader sr = new StreamReader(path))
{ 
    Console.WriteLine(sr.CurrentEncoding);                        
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.