Як виявити кодування символів текстового файлу?


76

Я намагаюся виявити, яке кодування символів використовується у моєму файлі.

Я намагаюся за допомогою цього коду отримати стандартне кодування

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

Мої п’ять перших байтів - це 60, 118, 56, 46 і 49.

Чи існує діаграма, яка показує, яке кодування відповідає цим першим п’яти байтам?


4
Знак порядку байтів не повинен використовуватися для виявлення кодування. Бувають випадки, коли неоднозначно, яке кодування використовується: UTF-16 LE та UTF-32 LE починаються з однакових двох байт. Специфікація повинна використовуватися лише для виявлення порядку байтів (звідси і його назва). Також UTF-8, строго кажучи, не повинен мати навіть позначки порядку байтів, а додавання може перешкоджати деякому програмному забезпеченню, яке цього не очікує.
Mark Byers

@Mark Bayers, отже, я можу виявити, як у моєму файлі використовується кодування відьом?
Cédric Boivin

4
@Mark Byers: UTF-32 LE починається з тих же 2 байтів, що і UTF-16 LE. Однак це також слід з байтами 00 00, що (я думаю, дуже) малоймовірно в UTF-16 LE. Крім того, технічна специфікація в теорії повинна вказувати, як ви говорите, але на практиці вона діє як підпис, щоб показати, що її кодує. Див .: unicode.org/faq/utf_bom.html#bom4
Dan W,

Чи справді специфікація UTF7 справді реальна? Я спробував створити об'єкт UTF7Encoding і виконати GetPreamble () на ньому, і він повернув порожній масив. І на відміну від utf8, для нього немає параметра конструктора.
Nyerguds

Марк Бейерс: Ваш коментар ЦІЛЬКИ неправильний. Специфікація - це маркеростійкий спосіб виявлення кодування. UTF16 BE та UTF32 BE не є двозначними. Вам слід вивчити тему, перш ніж писати неправильні коментарі. Якщо програмне забезпечення не обробляє специфікацію UTF8, це програмне забезпечення або з 1980-х, або погано запрограмоване. Сьогодні кожне програмне забезпечення має обробляти та розпізнавати специфікації.
Elmue

Відповіді:


84

Ви не можете залежати від того, який файл має специфікацію. UTF-8 цього не вимагає. А кодування, не пов’язані з Unicode, навіть не мають специфікації. Однак існують інші способи виявлення кодування.

UTF-32

BOM - 00 00 FE FF (для BE) або FF FE 00 00 (для LE).

Але UTF-32 легко виявити навіть без специфікації. Це тому, що діапазон кодових точок Unicode обмежений U + 10FFFF, і, отже, одиниці UTF-32 завжди мають шаблон 00 {00-10} xx xx (для BE) або xx xx {00-10} 00 (для LE) . Якщо дані мають довжину, кратну 4, і дотримуються одного з цих шаблонів, ви можете сміливо припустити, що це UTF-32. Помилкові спрацьовування майже неможливі через рідкість 00 байт у байтово-орієнтованих кодуваннях.

США-ASCII

Ніякої специфікації, але вона вам не потрібна. ASCII можна легко визначити за відсутністю байтів у діапазоні 80-FF.

UTF-8

BOM - це EF BB BF. Але на це не можна покладатися. Багато файлів UTF-8 не мають специфікації, особливо якщо вони походять із систем, які не є Windows.

Але можна сміливо припустити , що якщо файл Підтверджує як UTF-8, то є UTF-8. Помилкові позитивні результати трапляються рідко.

Зокрема, враховуючи, що дані не є ASCII, коефіцієнт хибнопозитивних даних для 2-байтової послідовності становить лише 3,9% (1920/49152). Для 7-байтової послідовності це менше 1%. Для 12-байтової послідовності це менше 0,1%. Для 24-байтової послідовності це менше 1 на мільйон.

UTF-16

BOM - це FE FF (для BE) або FF FE (для LE). Зверніть увагу, що специфікація UTF-16LE знаходиться на початку специфікації UTF-32LE, тому спочатку перевірте UTF-32.

Якщо у вас трапляється файл, який складається переважно із символів ISO-8859-1, наявність половини байтів у файлі 00 також буде сильним показником UTF-16.

В іншому випадку єдиним надійним способом розпізнавання UTF-16 без специфікації є пошук сурогатних пар (D [8-B] xx D [CF] xx), але символи, що не є BMP, використовуються занадто рідко, щоб зробити цей підхід практичним .

XML

Якщо ваш файл починається з байтів 3C 3F 78 6D 6C (тобто символи ASCII "<? Xml"), тоді шукайте encoding=декларацію. Якщо є, то скористайтеся цим кодуванням. Якщо немає, то припустимо UTF-8, який є кодуванням XML за замовчуванням.

Якщо вам потрібно підтримати EBCDIC, також шукайте еквівалентну послідовність 4C 6F A7 94 93.

Загалом, якщо у вас є формат файлу, який містить декларацію кодування, тоді шукайте цю декларацію, а не намагайтеся вгадати кодування.

Жоден з перерахованих вище

Існують сотні інших кодувань, які вимагають більше зусиль для виявлення. Я рекомендую спробувати детектор коду Mozilla або його .NET-порт .

Розумний дефолт

Якщо ви виключили кодування UTF і у вас немає декларації кодування або статистичного виявлення, що вказує на інше кодування, припустимо ISO-8859-1 або тісно пов'язану з ним Windows-1252 . (Зверніть увагу, що останній стандарт HTML вимагає, щоб декларація “ISO-8859-1” тлумачилася як Windows-1252.) Будучи кодовою сторінкою Windows за замовчуванням для англійської (та інших популярних мов, таких як іспанська, португальська, німецька та французька), це найчастіше зустрічається кодування, крім UTF-8.


1
Добре, що я очікував. Чи можете ви звернутися до відмінних UTF-8 / UTF-16? PS: Дякую за дуже корисну відповідь. +1
Іра Бакстер,

2
Для текстових файлів UTF-16BE, якщо певний відсоток парних байтів обнулено (або перевірити непарні байти для UTF-16LE), тоді є велика ймовірність того, що кодування UTF-16. Як ти гадаєш?
Dan W

1
Дійсність UTF-8 можна добре виявити, перевіряючи бітові шаблони; бітовий шаблон першого байта точно повідомляє, скільки байт буде слідувати, а наступні байти також мають контрольні біти для перевірки. Всі схеми показані тут: ianthehenry.com/2015/1/17/decoding-utf-8
Nyerguds

2
@marsze Це не моя відповідь ... і вона не згадується, оскільки мова йде про виявлення, і, як я вже згадував, ви не можете насправді виявити прості кодування по одному байту на символ. Однак я особисто розмістив відповідь на цьому місці про (неясно) її ідентифікацію.
Nyerguds

2
@marsze: Там я додав розділ для латиниці-1.
dan04

11

Якщо ви хочете знайти "просте" рішення, вам може бути корисним цей клас, який я склав:

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

Спочатку він автоматично виявляє специфікацію, а потім намагається розрізнити кодування Unicode без специфікації та інше кодування за замовчуванням (зазвичай Windows-1252, неправильно позначене як Encoding.ASCII у .Net).

Як зазначалося вище, "більш важке" рішення за участю NCharDet або MLang може бути більш доречним, і, як я зазначаю на оглядовій сторінці цього класу, найкращим є надання певної форми інтерактивності користувачеві, якщо це можливо, оскільки просто неможливий 100% рівень виявлення!

Фрагмент, якщо сайт офлайн:

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
Насправді, Encoding.GetEncoding("Windows-1252")дає інший клас об'єктів, ніж Encoding.ASCII. Під час налагодження Windows-1252 відображається як System.Text.SBCSCodePageEncodingоб'єкт, тоді як ascii є System.Text.ASCIIEncodingоб'єктом. Я ніколи не використовую ASCII, коли мені потрібна Windows-1252
Nyerguds,

Для узгодження регулярних виразів із двійковими даними (байтами) правильним методом є: string data = Encoding.GetEncoding("iso-8859-1").GetString(bytes); Оскільки це єдине однобайтове кодування, яке має відображення байтів 1 до 1 у рядок.
Амр Алі

6

Використовуйте StreamReaderта направляйте його, щоб визначити кодування для вас:

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

І використовуйте ідентифікатори кодових сторінок https://msdn.microsoft.com/en-us/library/windows/desktop/dd317756(v=vs.85).aspx , щоб переключити логіку залежно від цього.


3
Не працює, StreamReader припустимо, що ваш файл знаходиться в UTF-8
Седрик Бойвін

@Cedric: Перевірте MSDN для цього конструктора. Чи є у вас докази того, що конструктор не працює відповідно до документації? Звичайно, це можливо в документах Microsoft :-)
Філ Хант,

5
Ця версія також перевіряє лише специфікацію
Daniel Bişar

2
Гм, чи не потрібно телефонувати Read()перед читанням CurrentEncoding? MSDN для CurrentEncoding говорить «значення може відрізнятися після першого виклику будь-якого методу Розпізнати StreamReader, оскільки кодування автоопределение не робиться до першого виклику методу читання.»
Carl Walsh

1
Моє тестування показує, що це не можна використовувати надійно, тому не слід використовувати взагалі.
Джеффрі Макграт

5

Тут є кілька відповідей, але ніхто не опублікував корисного коду.

Ось мій код, який виявляє всі кодування, які Microsoft виявляє в Framework 4 у класі StreamReader.

Очевидно, що ви повинні викликати цю функцію відразу після відкриття потоку, перш ніж читати щось інше з потоку, оскільки специфікація - це перші байти в потоці.

Для цієї функції потрібен потік, який може шукати (наприклад, FileStream). Якщо у вас є потік, який не може шукати, ви повинні написати більш складний код, який повертає буфер байт з байтами, які вже були прочитані, але не є специфікацією.

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

Якщо ваш файл починається з байтів 60, 118, 56, 46 і 49, то у вас неоднозначний випадок. Це може бути UTF-8 (без специфікації) або будь-яке з однобайтових кодувань, таких як ASCII, ANSI, ISO-8859-1 тощо.


Хмммм ... так мені потрібно все перевірити?
Седрік Бойвін,

Це просто чисті асії. UTF-8 без спеціальних символів просто дорівнює ASCII, і якщо є спеціальні символи, вони використовують специфічні виявляються бітові шаблони.
Nyerguds

@Nyerguds, можливо, ні. У мене є текстовий файл UTF-8 (без "певних розпізнавальних шаблонів бітів" - і переважно всіх англійських символів). Якщо я читаю його за допомогою ASCII, він не може прочитати один конкретний символ "-".
Аміт

Неможливо. Якщо символ не ascii, тоді він буде кодований з використанням тих специфічних виявлених бітових шаблонів; ось як UTF-8 робіт . Швидше за все, ваш текст не є ні ascii, ні utf-8, а просто 8-бітовим кодуванням, як Windows-1252.
Nyerguds,

1

Я використовую Ude, який є портом C # в універсальному детекторі символів Mozilla. Він простий у використанні і дає справді хороші результати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.