Làm thế nào để phát hiện mã hóa ký tự của tệp văn bản?


76

Tôi cố gắng phát hiện mã hóa ký tự nào được sử dụng trong tệp của mình.

Tôi thử với mã này để có được mã hóa chuẩn

public static Encoding GetFileEncoding(string srcFile)
    {
      // *** Use Default of Encoding.Default (Ansi CodePage)
      Encoding enc = Encoding.Default;

      // *** Detect byte order mark if any - otherwise assume default
      byte[] buffer = new byte[5];
      FileStream file = new FileStream(srcFile, FileMode.Open);
      file.Read(buffer, 0, 5);
      file.Close();

      if (buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf)
        enc = Encoding.UTF8;
      else if (buffer[0] == 0xfe && buffer[1] == 0xff)
        enc = Encoding.Unicode;
      else if (buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff)
        enc = Encoding.UTF32;
      else if (buffer[0] == 0x2b && buffer[1] == 0x2f && buffer[2] == 0x76)
        enc = Encoding.UTF7;
      else if (buffer[0] == 0xFE && buffer[1] == 0xFF)      
        // 1201 unicodeFFFE Unicode (Big-Endian)
        enc = Encoding.GetEncoding(1201);      
      else if (buffer[0] == 0xFF && buffer[1] == 0xFE)      
        // 1200 utf-16 Unicode
        enc = Encoding.GetEncoding(1200);


      return enc;
    }

Năm byte đầu tiên của tôi là 60, 118, 56, 46 và 49.

Có biểu đồ nào cho thấy mã hóa nào phù hợp với năm byte đầu tiên đó không?


4
Dấu thứ tự byte không được sử dụng để phát hiện các bảng mã. Có những trường hợp không rõ ràng cách mã hóa nào được sử dụng: UTF-16 LE và UTF-32 LE đều bắt đầu bằng hai byte giống nhau. BOM chỉ nên được sử dụng để phát hiện thứ tự byte (do đó có tên là nó). Ngoài ra, UTF-8 nói đúng ra thậm chí không nên có dấu thứ tự byte và việc thêm một dấu có thể gây trở ngại cho một số phần mềm không mong đợi điều đó.
Đánh dấu Byers

@Mark Bayers, vậy có cách nào để tôi có thể phát hiện mã hóa phù thủy đang sử dụng trong tệp của mình không?
Cédric Boivin

4
@Mark Byers: UTF-32 LE bắt đầu bằng 2 byte giống như UTF-16 LE. Tuy nhiên, nó cũng theo sau với byte 00 00 (tôi nghĩ là rất khó) trong UTF-16 LE. Ngoài ra, BOM về lý thuyết nên chỉ ra như bạn nói, nhưng trong thực tế, nó hoạt động như một chữ ký để hiển thị mã hóa nó. Xem: unicode.org/faq/utf_bom.html#bom4
Dan W

UTF7 BOM có thực sự là một thứ có thật không? Tôi đã thử tạo một đối tượng UTF7Encoding và thực hiện GetPreamble () trên nó và nó trả về một mảng trống. Và không giống như utf8, nó không có tham số khởi tạo cho nó.
Nyerguds

Mark Beyers: Nhận xét của bạn HOÀN TOÀN sai. BOM là một cách chống đạn để phát hiện mã hóa. UTF16 BE và UTF32 BE không mơ hồ. Bạn nên nghiên cứu chủ đề trước khi viết những bình luận sai. Nếu một phần mềm không xử lý UTF8 BOM thì phần mềm này có thể là của những năm 1980 hoặc được lập trình tồi. Ngày nay, mọi phần mềm nên xử lý và nhận dạng BOM's.
Elmue

Câu trả lời:


84

Bạn không thể phụ thuộc vào việc tệp có BOM. UTF-8 không yêu cầu nó. Và các bảng mã không phải Unicode thậm chí không có BOM. Tuy nhiên, có những cách khác để phát hiện mã hóa.

UTF-32

BOM là 00 00 FE FF (cho BE) hoặc FF FE 00 00 (cho LE).

Nhưng UTF-32 rất dễ phát hiện ngay cả khi không có BOM. Điều này là do phạm vi điểm mã Unicode bị hạn chế ở U + 10FFFF và do đó, các đơn vị UTF-32 luôn có dạng 00 {00-10} xx xx (cho BE) hoặc xx xx {00-10} 00 (cho LE) . Nếu dữ liệu có độ dài là bội số của 4 và tuân theo một trong các mẫu này, bạn có thể yên tâm cho rằng đó là UTF-32. Kết quả dương tính giả gần như không thể xảy ra do sự hiếm hoi của 00 byte trong các mã hóa hướng byte.

US-ASCII

Không có BOM, nhưng bạn không cần một. ASCII có thể dễ dàng được xác định bằng cách thiếu byte trong phạm vi 80-FF.

UTF-8

BOM là EF BB BF. Nhưng bạn không thể dựa vào điều này. Rất nhiều tệp UTF-8 không có BOM, đặc biệt nếu chúng có nguồn gốc trên hệ thống không phải Windows.

Nhưng bạn có thể an toàn giả định rằng nếu một tệp xác thực là UTF-8, thì nó UTF-8. Dương tính giả rất hiếm.

Cụ thể, do dữ liệu không phải là ASCII, tỷ lệ dương tính giả đối với chuỗi 2 byte chỉ là 3,9% (1920/49152). Đối với một chuỗi 7 byte, nó nhỏ hơn 1%. Đối với chuỗi 12 byte, nó nhỏ hơn 0,1%. Đối với một chuỗi 24 byte, nó nhỏ hơn 1 trên một triệu.

UTF-16

BOM là FE FF (cho BE) hoặc FF FE (cho LE). Lưu ý rằng UTF-16LE BOM được tìm thấy ở đầu UTF-32LE BOM, vì vậy hãy kiểm tra UTF-32 trước.

Nếu bạn tình cờ có một tệp chủ yếu bao gồm các ký tự ISO-8859-1, thì việc có một nửa số byte của tệp là 00 cũng sẽ là một chỉ báo mạnh về UTF-16.

Mặt khác, cách đáng tin cậy duy nhất để nhận ra UTF-16 mà không có BOM là tìm các cặp thay thế (D [8-B] xx D [CF] xx), nhưng các ký tự không phải BMP quá hiếm khi được sử dụng để làm cho phương pháp này trở nên thực tế .

XML

Nếu tệp của bạn bắt đầu bằng các byte 3C 3F 78 6D 6C (tức là các ký tự ASCII "<? Xml"), thì hãy tìm một encoding=khai báo. Nếu có, hãy sử dụng bảng mã đó. Nếu không có, thì giả sử UTF-8, là kiểu mã hóa XML mặc định.

Nếu bạn cần hỗ trợ EBCDIC, hãy tìm chuỗi tương đương 4C 6F A7 94 93.

Nói chung, nếu bạn có định dạng tệp chứa khai báo mã hóa, thì hãy tìm khai báo đó thay vì cố gắng đoán mã hóa.

Không có cái nào ở trên

Có hàng trăm bảng mã khác, đòi hỏi nhiều nỗ lực hơn để phát hiện. Tôi khuyên bạn nên thử trình dò ​​mã ký tự của Mozilla hoặc cổng .NET của nó .

Một mặc định hợp lý

Nếu bạn đã loại trừ các mã hóa UTF và không có khai báo mã hóa hoặc phát hiện thống kê chỉ ra một mã hóa khác, hãy giả sử ISO-8859-1 hoặc Windows-1252 có liên quan chặt chẽ . (Lưu ý rằng tiêu chuẩn HTML mới nhất yêu cầu khai báo “ISO-8859-1” được hiểu là Windows-1252.) Là trang mã mặc định của Windows cho tiếng Anh (và các ngôn ngữ phổ biến khác như tiếng Tây Ban Nha, tiếng Bồ Đào Nha, tiếng Đức và tiếng Pháp), đó là kiểu mã hóa thường gặp nhất ngoài UTF-8.


1
OK, những gì tôi mong đợi. Bạn có thể nêu cách phân biệt UTF-8 / UTF-16 không? PS: Cảm ơn vì một câu trả lời rất hữu ích. +1
Ira Baxter

2
Đối với tệp văn bản UTF-16BE, nếu một tỷ lệ nhất định của các byte chẵn được làm bằng 0 (hoặc kiểm tra các byte lẻ để tìm UTF-16LE), thì có khả năng cao là mã hóa là UTF-16. Bạn nghĩ sao?
Dan W

1
Tính hợp lệ UTF-8 có thể được phát hiện một cách độc đáo bằng cách kiểm tra mẫu bit; mẫu bit của byte đầu tiên cho bạn biết chính xác bao nhiêu byte sẽ theo sau và các byte sau cũng có các bit điều khiển để kiểm tra. Tất cả các mẫu đều được hiển thị ở đây: ianthehenry.com/2015/1/17/decoding-utf-8
Nyerguds

2
@marsze Đây không phải là câu trả lời của tôi ... và nó không được đề cập bởi vì đây là về phát hiện và, như tôi đã đề cập, bạn thực sự không thể phát hiện các mã hóa đơn giản một byte cho mỗi ký hiệu. Cá nhân tôi đã đăng một câu trả lời trên địa điểm này về việc xác định (một cách mơ hồ) nó.
Nyerguds

2
@marsze: Ở đó, tôi đã thêm một phần cho Latin-1.
dan04,

11

Nếu bạn muốn theo đuổi một giải pháp "đơn giản", bạn có thể thấy lớp học này mà tôi tổng hợp lại rất hữu ích:

http://www.architectshack.com/TextFileEncodingDetector.ashx

Nó thực hiện tự động phát hiện BOM trước, và sau đó cố gắng phân biệt giữa các bảng mã Unicode không có BOM, với một số mã hóa mặc định khác (thường là Windows-1252, được gắn nhãn không chính xác là Encoding.ASCII trong .Net).

Như đã lưu ý ở trên, giải pháp "nặng hơn" liên quan đến NCharDet hoặc MLang có thể phù hợp hơn và như tôi lưu ý trên trang tổng quan của lớp này, tốt nhất là cung cấp một số hình thức tương tác với người dùng nếu có thể, bởi vì đơn giản là không có tỷ lệ phát hiện 100% có thể!

Đoạn mã trong trường hợp trang web ngoại tuyến:

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

namespace KlerksSoft
{
    public static class TextFileEncodingDetector
    {
        /*
         * Simple class to handle text file encoding woes (in a primarily English-speaking tech 
         *      world).
         * 
         *  - This code is fully managed, no shady calls to MLang (the unmanaged codepage
         *      detection library originally developed for Internet Explorer).
         * 
         *  - This class does NOT try to detect arbitrary codepages/charsets, it really only
         *      aims to differentiate between some of the most common variants of Unicode 
         *      encoding, and a "default" (western / ascii-based) encoding alternative provided
         *      by the caller.
         *      
         *  - As there is no "Reliable" way to distinguish between UTF-8 (without BOM) and 
         *      Windows-1252 (in .Net, also incorrectly called "ASCII") encodings, we use a 
         *      heuristic - so the more of the file we can sample the better the guess. If you 
         *      are going to read the whole file into memory at some point, then best to pass 
         *      in the whole byte byte array directly. Otherwise, decide how to trade off 
         *      reliability against performance / memory usage.
         *      
         *  - The UTF-8 detection heuristic only works for western text, as it relies on 
         *      the presence of UTF-8 encoded accented and other characters found in the upper 
         *      ranges of the Latin-1 and (particularly) Windows-1252 codepages.
         *  
         *  - For more general detection routines, see existing projects / resources:
         *    - MLang - Microsoft library originally for IE6, available in Windows XP and later APIs now (I think?)
         *      - MLang .Net bindings: http://www.codeproject.com/KB/recipes/DetectEncoding.aspx
         *    - CharDet - Mozilla browser's detection routines
         *      - Ported to Java then .Net: http://www.conceptdevelopment.net/Localization/NCharDet/
         *      - Ported straight to .Net: http://code.google.com/p/chardetsharp/source/browse
         *  
         * Copyright Tao Klerks, 2010-2012, tao@klerks.biz
         * Licensed under the modified BSD license:
         * 
Redistribution and use in source and binary forms, with or without modification, are 
permitted provided that the following conditions are met:
 - Redistributions of source code must retain the above copyright notice, this list of 
conditions and the following disclaimer.
 - Redistributions in binary form must reproduce the above copyright notice, this list 
of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
 - The name of the author may not be used to endorse or promote products derived from 
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
OF SUCH DAMAGE.
         * 
         * CHANGELOG:
         *  - 2012-02-03: 
         *    - Simpler methods, removing the silly "DefaultEncoding" parameter (with "??" operator, saves no typing)
         *    - More complete methods
         *      - Optionally return indication of whether BOM was found in "Detect" methods
         *      - Provide straight-to-string method for byte arrays (GetStringFromByteArray)
         */

        const long _defaultHeuristicSampleSize = 0x10000; //completely arbitrary - inappropriate for high numbers of files / high speed requirements

        public static Encoding DetectTextFileEncoding(string InputFilename)
        {
            using (FileStream textfileStream = File.OpenRead(InputFilename))
            {
                return DetectTextFileEncoding(textfileStream, _defaultHeuristicSampleSize);
            }
        }

        public static Encoding DetectTextFileEncoding(FileStream InputFileStream, long HeuristicSampleSize)
        {
            bool uselessBool = false;
            return DetectTextFileEncoding(InputFileStream, _defaultHeuristicSampleSize, out uselessBool);
        }

        public static Encoding DetectTextFileEncoding(FileStream InputFileStream, long HeuristicSampleSize, out bool HasBOM)
        {
            if (InputFileStream == null)
                throw new ArgumentNullException("Must provide a valid Filestream!", "InputFileStream");

            if (!InputFileStream.CanRead)
                throw new ArgumentException("Provided file stream is not readable!", "InputFileStream");

            if (!InputFileStream.CanSeek)
                throw new ArgumentException("Provided file stream cannot seek!", "InputFileStream");

            Encoding encodingFound = null;

            long originalPos = InputFileStream.Position;

            InputFileStream.Position = 0;


            //First read only what we need for BOM detection
            byte[] bomBytes = new byte[InputFileStream.Length > 4 ? 4 : InputFileStream.Length];
            InputFileStream.Read(bomBytes, 0, bomBytes.Length);

            encodingFound = DetectBOMBytes(bomBytes);

            if (encodingFound != null)
            {
                InputFileStream.Position = originalPos;
                HasBOM = true;
                return encodingFound;
            }


            //BOM Detection failed, going for heuristics now.
            //  create sample byte array and populate it
            byte[] sampleBytes = new byte[HeuristicSampleSize > InputFileStream.Length ? InputFileStream.Length : HeuristicSampleSize];
            Array.Copy(bomBytes, sampleBytes, bomBytes.Length);
            if (InputFileStream.Length > bomBytes.Length)
                InputFileStream.Read(sampleBytes, bomBytes.Length, sampleBytes.Length - bomBytes.Length);
            InputFileStream.Position = originalPos;

            //test byte array content
            encodingFound = DetectUnicodeInByteSampleByHeuristics(sampleBytes);

            HasBOM = false;
            return encodingFound;
        }

        public static Encoding DetectTextByteArrayEncoding(byte[] TextData)
        {
            bool uselessBool = false;
            return DetectTextByteArrayEncoding(TextData, out uselessBool);
        }

        public static Encoding DetectTextByteArrayEncoding(byte[] TextData, out bool HasBOM)
        {
            if (TextData == null)
                throw new ArgumentNullException("Must provide a valid text data byte array!", "TextData");

            Encoding encodingFound = null;

            encodingFound = DetectBOMBytes(TextData);

            if (encodingFound != null)
            {
                HasBOM = true;
                return encodingFound;
            }
            else
            {
                //test byte array content
                encodingFound = DetectUnicodeInByteSampleByHeuristics(TextData);

                HasBOM = false;
                return encodingFound;
            }
        }

        public static string GetStringFromByteArray(byte[] TextData, Encoding DefaultEncoding)
        {
            return GetStringFromByteArray(TextData, DefaultEncoding, _defaultHeuristicSampleSize);
        }

        public static string GetStringFromByteArray(byte[] TextData, Encoding DefaultEncoding, long MaxHeuristicSampleSize)
        {
            if (TextData == null)
                throw new ArgumentNullException("Must provide a valid text data byte array!", "TextData");

            Encoding encodingFound = null;

            encodingFound = DetectBOMBytes(TextData);

            if (encodingFound != null)
            {
                //For some reason, the default encodings don't detect/swallow their own preambles!!
                return encodingFound.GetString(TextData, encodingFound.GetPreamble().Length, TextData.Length - encodingFound.GetPreamble().Length);
            }
            else
            {
                byte[] heuristicSample = null;
                if (TextData.Length > MaxHeuristicSampleSize)
                {
                    heuristicSample = new byte[MaxHeuristicSampleSize];
                    Array.Copy(TextData, heuristicSample, MaxHeuristicSampleSize);
                }
                else
                {
                    heuristicSample = TextData;
                }

                encodingFound = DetectUnicodeInByteSampleByHeuristics(TextData) ?? DefaultEncoding;
                return encodingFound.GetString(TextData);
            }
        }


        public static Encoding DetectBOMBytes(byte[] BOMBytes)
        {
            if (BOMBytes == null)
                throw new ArgumentNullException("Must provide a valid BOM byte array!", "BOMBytes");

            if (BOMBytes.Length < 2)
                return null;

            if (BOMBytes[0] == 0xff 
                && BOMBytes[1] == 0xfe 
                && (BOMBytes.Length < 4 
                    || BOMBytes[2] != 0 
                    || BOMBytes[3] != 0
                    )
                )
                return Encoding.Unicode;

            if (BOMBytes[0] == 0xfe 
                && BOMBytes[1] == 0xff
                )
                return Encoding.BigEndianUnicode;

            if (BOMBytes.Length < 3)
                return null;

            if (BOMBytes[0] == 0xef && BOMBytes[1] == 0xbb && BOMBytes[2] == 0xbf)
                return Encoding.UTF8;

            if (BOMBytes[0] == 0x2b && BOMBytes[1] == 0x2f && BOMBytes[2] == 0x76)
                return Encoding.UTF7;

            if (BOMBytes.Length < 4)
                return null;

            if (BOMBytes[0] == 0xff && BOMBytes[1] == 0xfe && BOMBytes[2] == 0 && BOMBytes[3] == 0)
                return Encoding.UTF32;

            if (BOMBytes[0] == 0 && BOMBytes[1] == 0 && BOMBytes[2] == 0xfe && BOMBytes[3] == 0xff)
                return Encoding.GetEncoding(12001);

            return null;
        }

        public static Encoding DetectUnicodeInByteSampleByHeuristics(byte[] SampleBytes)
        {
            long oddBinaryNullsInSample = 0;
            long evenBinaryNullsInSample = 0;
            long suspiciousUTF8SequenceCount = 0;
            long suspiciousUTF8BytesTotal = 0;
            long likelyUSASCIIBytesInSample = 0;

            //Cycle through, keeping count of binary null positions, possible UTF-8 
            //  sequences from upper ranges of Windows-1252, and probable US-ASCII 
            //  character counts.

            long currentPos = 0;
            int skipUTF8Bytes = 0;

            while (currentPos < SampleBytes.Length)
            {
                //binary null distribution
                if (SampleBytes[currentPos] == 0)
                {
                    if (currentPos % 2 == 0)
                        evenBinaryNullsInSample++;
                    else
                        oddBinaryNullsInSample++;
                }

                //likely US-ASCII characters
                if (IsCommonUSASCIIByte(SampleBytes[currentPos]))
                    likelyUSASCIIBytesInSample++;

                //suspicious sequences (look like UTF-8)
                if (skipUTF8Bytes == 0)
                {
                    int lengthFound = DetectSuspiciousUTF8SequenceLength(SampleBytes, currentPos);

                    if (lengthFound > 0)
                    {
                        suspiciousUTF8SequenceCount++;
                        suspiciousUTF8BytesTotal += lengthFound;
                        skipUTF8Bytes = lengthFound - 1;
                    }
                }
                else
                {
                    skipUTF8Bytes--;
                }

                currentPos++;
            }

            //1: UTF-16 LE - in english / european environments, this is usually characterized by a 
            //  high proportion of odd binary nulls (starting at 0), with (as this is text) a low 
            //  proportion of even binary nulls.
            //  The thresholds here used (less than 20% nulls where you expect non-nulls, and more than
            //  60% nulls where you do expect nulls) are completely arbitrary.

            if (((evenBinaryNullsInSample * 2.0) / SampleBytes.Length) < 0.2 
                && ((oddBinaryNullsInSample * 2.0) / SampleBytes.Length) > 0.6
                )
                return Encoding.Unicode;


            //2: UTF-16 BE - in english / european environments, this is usually characterized by a 
            //  high proportion of even binary nulls (starting at 0), with (as this is text) a low 
            //  proportion of odd binary nulls.
            //  The thresholds here used (less than 20% nulls where you expect non-nulls, and more than
            //  60% nulls where you do expect nulls) are completely arbitrary.

            if (((oddBinaryNullsInSample * 2.0) / SampleBytes.Length) < 0.2 
                && ((evenBinaryNullsInSample * 2.0) / SampleBytes.Length) > 0.6
                )
                return Encoding.BigEndianUnicode;


            //3: UTF-8 - Martin Dürst outlines a method for detecting whether something CAN be UTF-8 content 
            //  using regexp, in his w3c.org unicode FAQ entry: 
            //  http://www.w3.org/International/questions/qa-forms-utf-8
            //  adapted here for C#.
            string potentiallyMangledString = Encoding.ASCII.GetString(SampleBytes);
            Regex UTF8Validator = new Regex(@"\A(" 
                + @"[\x09\x0A\x0D\x20-\x7E]"
                + @"|[\xC2-\xDF][\x80-\xBF]"
                + @"|\xE0[\xA0-\xBF][\x80-\xBF]"
                + @"|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}"
                + @"|\xED[\x80-\x9F][\x80-\xBF]"
                + @"|\xF0[\x90-\xBF][\x80-\xBF]{2}"
                + @"|[\xF1-\xF3][\x80-\xBF]{3}"
                + @"|\xF4[\x80-\x8F][\x80-\xBF]{2}"
                + @")*\z");
            if (UTF8Validator.IsMatch(potentiallyMangledString))
            {
                //Unfortunately, just the fact that it CAN be UTF-8 doesn't tell you much about probabilities.
                //If all the characters are in the 0-127 range, no harm done, most western charsets are same as UTF-8 in these ranges.
                //If some of the characters were in the upper range (western accented characters), however, they would likely be mangled to 2-byte by the UTF-8 encoding process.
                // So, we need to play stats.

                // The "Random" likelihood of any pair of randomly generated characters being one 
                //   of these "suspicious" character sequences is:
                //     128 / (256 * 256) = 0.2%.
                //
                // In western text data, that is SIGNIFICANTLY reduced - most text data stays in the <127 
                //   character range, so we assume that more than 1 in 500,000 of these character 
                //   sequences indicates UTF-8. The number 500,000 is completely arbitrary - so sue me.
                //
                // We can only assume these character sequences will be rare if we ALSO assume that this
                //   IS in fact western text - in which case the bulk of the UTF-8 encoded data (that is 
                //   not already suspicious sequences) should be plain US-ASCII bytes. This, I 
                //   arbitrarily decided, should be 80% (a random distribution, eg binary data, would yield 
                //   approx 40%, so the chances of hitting this threshold by accident in random data are 
                //   VERY low). 

                if ((suspiciousUTF8SequenceCount * 500000.0 / SampleBytes.Length >= 1) //suspicious sequences
                    && (
                           //all suspicious, so cannot evaluate proportion of US-Ascii
                           SampleBytes.Length - suspiciousUTF8BytesTotal == 0 
                           ||
                           likelyUSASCIIBytesInSample * 1.0 / (SampleBytes.Length - suspiciousUTF8BytesTotal) >= 0.8
                       )
                    )
                    return Encoding.UTF8;
            }

            return null;
        }

        private static bool IsCommonUSASCIIByte(byte testByte)
        {
            if (testByte == 0x0A //lf
                || testByte == 0x0D //cr
                || testByte == 0x09 //tab
                || (testByte >= 0x20 && testByte <= 0x2F) //common punctuation
                || (testByte >= 0x30 && testByte <= 0x39) //digits
                || (testByte >= 0x3A && testByte <= 0x40) //common punctuation
                || (testByte >= 0x41 && testByte <= 0x5A) //capital letters
                || (testByte >= 0x5B && testByte <= 0x60) //common punctuation
                || (testByte >= 0x61 && testByte <= 0x7A) //lowercase letters
                || (testByte >= 0x7B && testByte <= 0x7E) //common punctuation
                )
                return true;
            else
                return false;
        }

        private static int DetectSuspiciousUTF8SequenceLength(byte[] SampleBytes, long currentPos)
        {
            int lengthFound = 0;

            if (SampleBytes.Length >= currentPos + 1 
                && SampleBytes[currentPos] == 0xC2
                )
            {
                if (SampleBytes[currentPos + 1] == 0x81 
                    || SampleBytes[currentPos + 1] == 0x8D 
                    || SampleBytes[currentPos + 1] == 0x8F
                    )
                    lengthFound = 2;
                else if (SampleBytes[currentPos + 1] == 0x90 
                    || SampleBytes[currentPos + 1] == 0x9D
                    )
                    lengthFound = 2;
                else if (SampleBytes[currentPos + 1] >= 0xA0 
                    && SampleBytes[currentPos + 1] <= 0xBF
                    )
                    lengthFound = 2;
            }
            else if (SampleBytes.Length >= currentPos + 1 
                && SampleBytes[currentPos] == 0xC3
                )
            {
                if (SampleBytes[currentPos + 1] >= 0x80 
                    && SampleBytes[currentPos + 1] <= 0xBF
                    )
                    lengthFound = 2;
            }
            else if (SampleBytes.Length >= currentPos + 1 
                && SampleBytes[currentPos] == 0xC5
                )
            {
                if (SampleBytes[currentPos + 1] == 0x92 
                    || SampleBytes[currentPos + 1] == 0x93
                    )
                    lengthFound = 2;
                else if (SampleBytes[currentPos + 1] == 0xA0 
                    || SampleBytes[currentPos + 1] == 0xA1
                    )
                    lengthFound = 2;
                else if (SampleBytes[currentPos + 1] == 0xB8 
                    || SampleBytes[currentPos + 1] == 0xBD 
                    || SampleBytes[currentPos + 1] == 0xBE
                    )
                    lengthFound = 2;
            }
            else if (SampleBytes.Length >= currentPos + 1 
                && SampleBytes[currentPos] == 0xC6
                )
            {
                if (SampleBytes[currentPos + 1] == 0x92)
                    lengthFound = 2;
            }
            else if (SampleBytes.Length >= currentPos + 1 
                && SampleBytes[currentPos] == 0xCB
                )
            {
                if (SampleBytes[currentPos + 1] == 0x86 
                    || SampleBytes[currentPos + 1] == 0x9C
                    )
                    lengthFound = 2;
            }
            else if (SampleBytes.Length >= currentPos + 2 
                && SampleBytes[currentPos] == 0xE2
                )
            {
                if (SampleBytes[currentPos + 1] == 0x80)
                {
                    if (SampleBytes[currentPos + 2] == 0x93 
                        || SampleBytes[currentPos + 2] == 0x94
                        )
                        lengthFound = 3;
                    if (SampleBytes[currentPos + 2] == 0x98 
                        || SampleBytes[currentPos + 2] == 0x99 
                        || SampleBytes[currentPos + 2] == 0x9A
                        )
                        lengthFound = 3;
                    if (SampleBytes[currentPos + 2] == 0x9C 
                        || SampleBytes[currentPos + 2] == 0x9D 
                        || SampleBytes[currentPos + 2] == 0x9E
                        )
                        lengthFound = 3;
                    if (SampleBytes[currentPos + 2] == 0xA0 
                        || SampleBytes[currentPos + 2] == 0xA1 
                        || SampleBytes[currentPos + 2] == 0xA2
                        )
                        lengthFound = 3;
                    if (SampleBytes[currentPos + 2] == 0xA6)
                        lengthFound = 3;
                    if (SampleBytes[currentPos + 2] == 0xB0)
                        lengthFound = 3;
                    if (SampleBytes[currentPos + 2] == 0xB9 
                        || SampleBytes[currentPos + 2] == 0xBA
                        )
                        lengthFound = 3;
                }
                else if (SampleBytes[currentPos + 1] == 0x82 
                    && SampleBytes[currentPos + 2] == 0xAC
                    )
                    lengthFound = 3;
                else if (SampleBytes[currentPos + 1] == 0x84 
                    && SampleBytes[currentPos + 2] == 0xA2
                    )
                    lengthFound = 3;
            }

            return lengthFound;
        }

    }
}

1
Trên thực tế, Encoding.GetEncoding("Windows-1252")cung cấp một lớp đối tượng khác với Encoding.ASCII. Trong khi gỡ lỗi, Windows-1252 hiển thị dưới dạng một System.Text.SBCSCodePageEncodingđối tượng, trong khi ascii là một System.Text.ASCIIEncodingđối tượng. Tôi không bao giờ sử dụng ASCII khi tôi cần Windows-1252
Nyerguds

Để đối sánh các biểu thức chính quy với dữ liệu nhị phân (byte), phương pháp đúng là: string data = Encoding.GetEncoding("iso-8859-1").GetString(bytes); Bởi vì đây là mã hóa byte đơn duy nhất có ánh xạ 1 đến 1 byte thành chuỗi.
Amr Ali

6

Sử dụng StreamReadervà hướng nó để phát hiện mã hóa cho bạn:

using (var reader = new System.IO.StreamReader(path, true))
{
    var currentEncoding = reader.CurrentEncoding;
}

Và sử dụng Số nhận dạng trang mã https://msdn.microsoft.com/en-us/library/windows/desktop/dd317756(v=vs.85).aspx để chuyển đổi logic tùy thuộc vào nó.


3
Không làm việc, StreamReader giả sử rằng tập tin bạn đang ở trong UTF-8
Cédric Boivin

@Cedric: Kiểm tra MSDN cho hàm tạo này. Bạn có bằng chứng rằng hàm tạo không hoạt động nhất quán với tài liệu không? Được cấp, điều đó có thể thực hiện được trong tài liệu của Microsoft :-)
Phil Hunt

5
Phiên bản này cũng chỉ kiểm tra cho BOM
Daniel Bişar

2
Ừm, bạn không phải gọi điện Read()trước khi đọc CurrentEncodingsao? Các MSDN cho CurrentEncoding nói "Giá trị có thể khác nhau sau khi cuộc gọi đầu tiên đối với bất kỳ phương pháp đọc của StreamReader, vì mã hóa dò không được thực hiện cho đến khi cuộc gọi đầu tiên đến một phương pháp đọc."
Carl Walsh

1
Thử nghiệm của tôi cho thấy rằng điều này không thể được sử dụng một cách đáng tin cậy, do đó không nên sử dụng tất cả.
Geoffrey McGrath

5

Một số câu trả lời có ở đây nhưng không ai đã đăng mã hữu ích.

Đây là mã của tôi phát hiện tất cả các mã hóa mà Microsoft phát hiện trong Framework 4 trong lớp StreamReader.

Rõ ràng là bạn phải gọi hàm này ngay sau khi mở luồng trước khi đọc bất kỳ thứ gì khác từ luồng vì BOM là các byte đầu tiên trong luồng.

Chức năng này yêu cầu một Luồng có thể tìm kiếm (ví dụ: một luồng File). Nếu bạn có một Luồng không thể tìm kiếm, bạn phải viết một mã phức tạp hơn trả về bộ đệm Byte với các byte đã được đọc nhưng không phải là BOM.

/// <summary>
/// UTF8    : EF BB BF
/// UTF16 BE: FE FF
/// UTF16 LE: FF FE
/// UTF32 BE: 00 00 FE FF
/// UTF32 LE: FF FE 00 00
/// </summary>
public static Encoding DetectEncoding(Stream i_Stream)
{
    if (!i_Stream.CanSeek || !i_Stream.CanRead)
        throw new Exception("DetectEncoding() requires a seekable and readable Stream");

    // Try to read 4 bytes. If the stream is shorter, less bytes will be read.
    Byte[] u8_Buf = new Byte[4];
    int s32_Count = i_Stream.Read(u8_Buf, 0, 4);
    if (s32_Count >= 2)
    {
        if (u8_Buf[0] == 0xFE && u8_Buf[1] == 0xFF)
        {
            i_Stream.Position = 2;
            return new UnicodeEncoding(true, true);
        }

        if (u8_Buf[0] == 0xFF && u8_Buf[1] == 0xFE)
        {
            if (s32_Count >= 4 && u8_Buf[2] == 0 && u8_Buf[3] == 0)
            {
                i_Stream.Position = 4;
                return new UTF32Encoding(false, true);
            }
            else
            {
                i_Stream.Position = 2;
                return new UnicodeEncoding(false, true);
            }
        }

        if (s32_Count >= 3 && u8_Buf[0] == 0xEF && u8_Buf[1] == 0xBB && u8_Buf[2] == 0xBF)
        {
            i_Stream.Position = 3;
            return Encoding.UTF8;
        }

        if (s32_Count >= 4 && u8_Buf[0] == 0 && u8_Buf[1] == 0 && u8_Buf[2] == 0xFE && u8_Buf[3] == 0xFF)
        {
            i_Stream.Position = 4;
            return new UTF32Encoding(true, true);
        }
    }

    i_Stream.Position = 0;
    return Encoding.Default;
}



1

Nếu tệp của bạn bắt đầu bằng các byte 60, 118, 56, 46 và 49 thì bạn có một trường hợp không rõ ràng. Nó có thể là UTF-8 (không có BOM) hoặc bất kỳ mã hóa byte đơn nào như ASCII, ANSI, ISO-8859-1, v.v.


Hummmm ... vậy tôi cần phải kiểm tra tất cả?
Cédric Boivin

Đó chỉ là ascii thuần túy. UTF-8 không có các ký tự đặc biệt chỉ đơn giản là ASCII và nếu có các ký tự đặc biệt, chúng sử dụng các mẫu bit có thể phát hiện cụ thể.
Nyerguds

@Nyerguds có thể không. Tôi có một tệp văn bản UTF-8 (không có "các mẫu bit có thể phát hiện cụ thể" - và hầu hết là tất cả các ký tự tiếng Anh). Nếu tôi đọc nó bằng ASCII, nó không đọc được một ký hiệu "-" cụ thể.
Amit

Không thể nào. Nếu ký tự không phải là ascii, thì nó sẽ được mã hóa bằng cách sử dụng các mẫu bit có thể phát hiện cụ thể đó; đó là cách utf-8 hoạt động . Nhiều khả năng, văn bản của bạn không phải là ascii hay utf-8, mà chỉ là mã hóa 8-bit như Windows-1252.
Nyerguds

1

Tôi sử dụng Ude là một cổng C # của Mozilla Universal Charset Detector. Nó rất dễ sử dụng và mang lại một số kết quả thực sự tốt.

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.