Как определить кодировку символов текстового файла?

Я пытаюсь определить, какая кодировка символов используется в моем файле.

Я пытаюсь с помощью этого кода получить стандартную кодировку

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.

Есть ли диаграмма, показывающая, какая кодировка соответствует этим пяти первым байтам?


person Cédric Boivin    schedule 23.12.2010    source источник
comment
Метка порядка байтов не должна использоваться для обнаружения кодировок. Бывают случаи, когда неясно, какая кодировка используется: UTF-16 LE и UTF-32 LE начинаются с одних и тех же двух байтов. Спецификацию следует использовать только для определения порядка байтов (отсюда и ее название). Кроме того, строго говоря, UTF-8 не должен даже иметь метку порядка байтов, и добавление ее может мешать работе некоторого программного обеспечения, которое этого не ожидает.   -  person Mark Byers    schedule 23.12.2010
comment
@Mark Bayers, так есть ли способ обнаружить, что кодировка ведьм используется в моем файле?   -  person Cédric Boivin    schedule 23.12.2010
comment
@Mark Byers: UTF-32 LE начинается с тех же 2 байтов, что и UTF-16 LE. Однако это также следует с байтами 00 00, что (я думаю, очень) маловероятно в UTF-16 LE. Кроме того, спецификация теоретически должна указывать, как вы говорите, но на практике она действует как подпись, чтобы показать, что она кодирует. См. unicode.org/faq/utf_bom.html#bom4.   -  person Dan W    schedule 12.10.2012
comment
Действительно ли спецификация UTF7 реальна? Я попытался создать объект UTF7Encoding и выполнить для него GetPreamble (), и он вернул пустой массив. И, в отличие от utf8, для него нет параметра конструктора.   -  person Nyerguds    schedule 15.03.2016
comment
Марк Бейерс: Ваш комментарий СОВЕРШЕННО неверен. Спецификация - это пуленепробиваемый способ обнаружения кодировки. UTF16 BE и UTF32 BE неоднозначны. Вам следует изучить тему, прежде чем писать неправильные комментарии. Если программное обеспечение не поддерживает спецификацию UTF8, то это программное обеспечение либо из 1980-х годов, либо плохо запрограммировано. Сегодня каждое программное обеспечение должно обрабатывать и распознавать спецификации.   -  person Elmue    schedule 09.11.2016
comment
Возможный дубликат Как определить кодировку / кодовая страница текстового файла   -  person TarmoPikaro    schedule 31.12.2016
comment
Elmue явно никогда не использовал пакетную фильтрацию, конкатенацию и перенаправление каналов для потоков файлов с открытым текстом. В таких сценариях нереально обрабатывать / поддерживать спецификации.   -  person jstine    schedule 06.01.2018


Ответы (8)


Вы не можете полагаться на файл, имеющий спецификацию. UTF-8 этого не требует. А кодировки, отличные от Unicode, даже не имеют спецификации. Однако есть и другие способы определения кодировки.

UTF-32

Спецификация - 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 в байтовых кодировках.

US-ASCII

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

UTF-8

Спецификация - 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

Спецификация - это 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.

person dan04    schedule 23.12.2010
comment
Не могли бы вы пояснить свой анализ UTF-8 выше? Я думаю, вы говорите, что если у вас есть случайное [плоское] распределение символов, из которых состоит файл, у вас низкая вероятность запутаться. С практической точки зрения, никакие текстовые файлы не имеют такого плоского распределения ... поэтому я ожидал бы серьезного влияния на анализ с гораздо более высоким уровнем ложных срабатываний. Как отличить UTF-16 от UTF-8, если файлы имеют четное количество байтов? - person Ira Baxter; 16.07.2012
comment
Да, это для случайного распределения октетов. Для реальных данных посчитать сложнее. Но дело в том, что для того, чтобы файл с устаревшей кодировкой (например, windows-1252) был неправильно истолкован как UTF-8, он должен был бы содержать странные последовательности символов, такие как ’. - person dan04; 18.07.2012
comment
Хорошо, чего я ожидал. Можете ли вы обратиться к различению UTF-8 / UTF-16? PS: Спасибо за очень полезный ответ. +1 - person Ira Baxter; 18.07.2012
comment
Вы не можете обнаружить UTF-16 с помощью проверки, как вы можете с UTF-8, потому что количество ложных срабатываний намного выше. Например, около 93,8% случайных 4-байтовых последовательностей оказываются действительными UTF-16, единственными недопустимыми из них являются несимволы и непарные суррогаты. И 100% строк ASCII четной длины являются действительными UTF-16 (даже если это бессмысленное китайское предложение вроде 畂桳栠摩琠敨映捡獴). Единственный надежный способ определить UTF-16 - это поискать в спецификации, FE FF или FF FE. Вы все равно получите ложное срабатывание для Latin1 þÿ или ÿþ, но это маловероятные комбинации. - person dan04; 18.07.2012
comment
Для текстовых файлов UTF-16BE, если определенный процент четных байтов обнулен (или проверьте нечетные байты для UTF-16LE), то с большой вероятностью будет использоваться кодировка UTF-16. Что вы думаете? - person Dan W; 12.10.2012
comment
Да, в большинстве случаев это сработает. Вы по-прежнему можете получать ложноотрицательные результаты из файлов, содержащих только символы, отличные от Latin1. - person dan04; 20.10.2012
comment
@ dan04 что такое | в 0x | 10 ?? это постоянная величина? - person EProgrammerNotFound; 26.04.2013
comment
Оператор или. И x обозначает любую шестнадцатеричную цифру. То есть этот байт может быть любым из 17 значений от 00 до 10 включительно. - person dan04; 26.04.2013
comment
Правильность UTF-8 можно легко определить, выполнив проверку битового шаблона; битовая комбинация первого байта точно сообщает вам, сколько байтов последует, а следующие байты также содержат контрольные биты для проверки. Все шаблоны показаны здесь: ianthehenry.com/2015/1/17/ декодирование-UTF-8 - person Nyerguds; 13.11.2015
comment
Я удивлен, что в этом ответе не упоминается Windows ANSI, учитывая, насколько это распространено. - person marsze; 18.01.2017
comment
@marsze Это потому, что это тупая 8-битная кодировка по одному байту на символ. Каждый байт в нем технически допустим (хотя большая часть диапазона ‹0x20 не рекомендуется), поэтому его нельзя обнаружить, кроме как с помощью языковой эвристики ... целый другой бардак. Не говоря уже о том, что ANSI - это не одна кодировка; для каждого языкового региона существует кодировка Windows ANSI. - person Nyerguds; 14.11.2018
comment
@Nyerguds Я не сказал, что это хорошая кодировка. Я сказал, что это очень часто используется, и поэтому удивлен, что вы не упомянули об этом. Точки, которые вы только что высказали, отвечая на мой комментарий, были бы очень полезны в вашем ответе. - person marsze; 14.11.2018
comment
@marsze Это не мой ответ ... и он не упоминается, потому что речь идет о обнаружении, и, как я уже упоминал, вы не можете обнаружить простой - побайтно-символьное кодирование. Тем не менее, я лично опубликовал здесь ответ о (нечетко) его идентификации. - person Nyerguds; 15.11.2018
comment
@marsze: Там я добавил раздел для Latin-1. - person dan04; 16.11.2018
comment
Извините, @Nyerguds, я думаю, вы слишком увлеченно защищали ответ. И dan04 спасибо! Читает хорошо. - person marsze; 16.11.2018

Если вы хотите найти "простое" решение, вы можете найти этот класс, который я собрал, полезным:

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, [email protected]
         * 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;
        }

    }
}
person Tao    schedule 29.04.2011
comment
Фактически, Encoding.GetEncoding("Windows-1252") дает другой класс объекта, чем Encoding.ASCII. Во время отладки Windows-1252 отображается как объект System.Text.SBCSCodePageEncoding, а ascii - как объект System.Text.ASCIIEncoding. Я никогда не использую ASCII, когда мне нужна Windows-1252 - person Nyerguds; 13.11.2015
comment
Чтобы сопоставить регулярные выражения с двоичными данными (байтами), правильный метод: string data = Encoding.GetEncoding("iso-8859-1").GetString(bytes); Поскольку это единственная однобайтовая кодировка, которая имеет сопоставление байтов 1 к 1 со строкой. - person Amr Ali; 23.07.2020

Используйте 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 для переключения логики в зависимости от этого.

person Phil Hunt    schedule 23.12.2010
comment
Не работает, StreamReader предполагает, что ваш файл находится в UTF-8 - person Cédric Boivin; 23.12.2010
comment
@Cedric: проверьте MSDN для этот конструктор. Есть ли у вас доказательства того, что конструктор не работает в соответствии с документацией? Конечно, это возможно в документах Microsoft :-) - person Phil Hunt; 23.12.2010
comment
извините, ваше право. Но не работает :-( кодировка плохая - person Cédric Boivin; 23.12.2010
comment
Эта версия также проверяет только спецификацию - person Daniel Bişar; 27.07.2012
comment
Эм, разве тебе не нужно звонить Read() перед чтением CurrentEncoding? MSDN для CurrentEncoding говорит, что значение может отличаться после первого вызова любого метода Read StreamReader, поскольку автоопределение кодирования не выполняется до первого вызова метода Read. - person Carl Walsh; 25.10.2013
comment
Мое тестирование показывает, что это нельзя использовать надежно, поэтому не следует использовать вообще. - person Geoffrey McGrath; 26.02.2014
comment
Из документов MSDN упомянутая вами перегрузка StreamReader [...] инициализирует кодировку UTF8Encoding, затем второй параметр, detectEncodingFromByteOrderMarks, [...] автоматически распознает UTF-8, Unicode с прямым порядком байтов и текст Unicode с прямым порядком байтов, если файл начинается с соответствующих отметок порядка байтов. Сначала он по умолчанию будет использовать UTF8, а затем он не будет автоматически определять все кодировки, так что это не то, что ищет OP. (msdn.microsoft.com/en-us/ библиотека / 7bc2hwcb (v = vs.110) .aspx) - person AbeyMarquez; 17.01.2018

Здесь есть несколько ответов, но никто не опубликовал полезный код.

Вот мой код, который обнаруживает все кодировки, которые Microsoft обнаруживает в Framework 4 в классе StreamReader.

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

Эта функция требует Stream, который может искать (например, 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;
}
person Elmue    schedule 09.11.2016

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

person Julien Jacobs    schedule 01.08.2014
comment
Отлично, работает. - person Suyon Won; 27.05.2021


Вам следует прочитать следующее: Как я могу обнаружить кодировка / кодовая страница текстового файла

person Steven K.    schedule 23.12.2010

Если ваш файл начинается с байтов 60, 118, 56, 46 и 49, то у вас неоднозначный случай. Это может быть UTF-8 (без спецификации) или любая из однобайтовых кодировок, например ASCII, ANSI, ISO-8859-1 и т. Д.

person Codo    schedule 23.12.2010
comment
Хммм ... так что мне нужно все протестировать? - person Cédric Boivin; 23.12.2010
comment
Это просто ascii. UTF-8 без специальных символов просто равен ASCII, а если есть специальные символы, они используют определенные обнаруживаемые битовые шаблоны. - person Nyerguds; 15.03.2016
comment
@Nyerguds, может быть, и нет. У меня есть текстовый файл UTF-8 (без определенных обнаруживаемых битовых шаблонов - и в основном всех английских символов). Если я прочитал его с помощью ASCII, он не смог прочитать один конкретный символ. - person Amit; 14.06.2019
comment
Невозможно. Если символ не является ascii, он будет закодирован с использованием этих определенных обнаруживаемых битовых шаблонов; Вот как работает utf-8 . Скорее всего, ваш текст не является ни ascii, ни utf-8, а просто 8-битной кодировкой, такой как Windows-1252. - person Nyerguds; 15.06.2019