Ефективний спосіб знайти кодування будь-якого файлу


115

Так, це найчастіше запитання, і це питання для мене розпливчасте, тому що я мало про це знаю.

Але я хотів би дуже точний спосіб знайти файли кодування. Настільки точний, як Notepad ++.



Які кодування? UTF-8 проти UTF-16, великий проти маленького ендіана? Або ви посилаєтесь на старі кодові сторінки MSDos, наприклад, shift-JIS або кирилицю тощо?
dthorpe

Інший можливий дублікат: stackoverflow.com/questions/436220/…
відмінено

@Oded: Цитата "Метод getEncoding () поверне кодування, яке було встановлено (читайте JavaDoc) для потоку. Він не здогадається про кодування для вас.".
Фабіо Антунес

2
Для певного фонового читання joelonsoftware.com/articles/Unicode.html добре читати. Якщо є одна річ, яку ви повинні знати про текст, це те, що не існує такого поняття, як звичайний текст.
Martijn

Відповіді:


155

StreamReader.CurrentEncodingВластивість рідко повертає правильний текстовий файл , який кодує для мене. Я мав більший успіх у визначенні витривалості файлу, аналізуючи його порядок байт (BOM). Якщо у файлі немає BOM, це не може визначити кодування файлу.

* ОНОВЛЕНО 4/08/2020 для включення виявлення UTF-32LE та повернення правильного кодування для UTF-32BE

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

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

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

3
+1. Це працювало і для мене (тоді як detectEncodingFromByteOrderMarks цього не робив). Я використовував "новий FileStream (ім'я файлу, FileMode.Open, FileAccess.Read)", щоб уникнути IOException, тому що файл читається тільки.
Поліфун

56
Файли UTF-8 можуть бути без BOM, в цьому випадку він поверне ASCII неправильно.
користувач626528

3
Ця відповідь неправильна. Дивлячись на довідковий джерело для StreamReader, що реалізація того, що все більше людей хочуть. Вони роблять нові кодування, а не використовують існуючі Encoding.Unicodeоб'єкти, тому перевірки рівності не вдасться (що в будь-якому випадку рідко трапляється, оскільки, наприклад, Encoding.UTF8можуть повертати різні об'єкти), але воно (1) не використовує дійсно дивний формат UTF-7, (2) за замовчуванням UTF-8, якщо BOM не знайдено, і (3) можна змінити, щоб використовувати інше кодування за замовчуванням.
ангар

2
Я мав кращий успіх з новим StreamReader (ім'я файлу, правда) .CurrentEncoding
Бенуа

4
У коді є принципова помилка; виявивши підпис UTF32 з великим ендіанітом ( ), ви повернете надане системою , що є кодування мало-ендіанським (як зазначено тут ). А також, як зазначає @Nyerguds, ви все ще не шукаєте UTF32LE, який має підпис (згідно з en.wikipedia.org/wiki/Byte_order_mark ). Як зауважив цей користувач, оскільки він перебуває в підпорядкуванні, ця перевірка повинна відбуватися перед 2-байтними чеками. 00 00 FE FFEncoding.UTF32FF FE 00 00
Гленн Слейден

44

Наступний код добре працює для мене, використовуючи StreamReaderклас:

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

Хитрість полягає у використанні Peekвиклику, інакше .NET нічого не зробив (і не прочитав преамбулу, BOM). Звичайно, якщо ви використовуєте будь-який інший ReadXXXвиклик перед перевіркою кодування, він також працює.

Якщо у файлі немає BOM, тоді defaultEncodingIfNoBomбуде використано кодування. Існує також StreamReader без цього методу перевантаження (у цьому випадку кодування за замовчуванням (ANSI) буде використовуватися як defaultEncodingIfNoBom), але я рекомендую визначити, що ви вважаєте кодуванням за замовчуванням у вашому контексті.

Я успішно протестував це з файлами з BOM для UTF8, UTF16 / Unicode (LE & BE) та UTF32 (LE & BE). Це не працює для UTF7.


Я повертаю те, що встановлено як кодування за замовчуванням. Чи можу я пропустити щось?
Рам

1
@DRAM - це може статися, якщо у файлі немає BOM
Simon Mourier

Дякую @Simon Mourier Я очікую, що мій pdf / жоден файл не матиме бомби. Це посилання stackoverflow.com/questions/4520184/… може бути корисним для тих, хто намагається виявити без бомби.
Рам

1
У повному обсязі мені довелося запустити $ reader.close (), інакше це було заблоковано від написання. foreach($filename in $args) { $reader = [System.IO.StreamReader]::new($filename, [System.Text.Encoding]::default,$true); $peek = $reader.Peek(); $reader.currentencoding | select bodyname,encodingname; $reader.close() }
js2010

1
@SimonMourier Це не працює, якщо кодування файлуUTF-8 without BOM
Ozkan,

11

Я б спробував виконати наступні дії:

1) Перевірте, чи є марка порядку байтів

2) Перевірте, чи правильний файл UTF8

3) Використовуйте локальну кодову сторінку "ANSI" (ANSI, як це визначає Microsoft)

Крок 2 працює тому, що більшість послідовностей, що не належать до ASCII, у кодових сторінках, інших, що UTF8, не є дійсними UTF8.


Це здається більш правильною відповіддю, оскільки інша відповідь не працює для мене. Можна зробити це за допомогою File.OpenRead та .Read-ing перших кількох байтів файлу.
user420667

1
Крок 2 - це ціла купа роботи з програмування для перевірки бітових моделей.
Nyerguds

1
Я не впевнений, що декодування насправді викидає винятки, або якщо воно просто замінює нерозпізнані послідовності на "?". Я все-таки пішов писати клас перевірки шаблону.
Nyerguds

3
Коли ви створюєте екземпляр, Utf8Encodingви можете передати додатковий параметр, який визначає, чи слід викидати виняток або якщо ви віддаєте перевагу беззвучній корупції даних.
CodesInChaos

1
Мені подобається ця відповідь. Більшість кодувань (наприклад, 99% випадків використання) становлять або UTF-8, або ANSI (кодова сторінка Windows 1252). Ви можете перевірити, чи містить рядок символ заміни (0xFFFD), щоб визначити, чи не вдалося кодувати.
марш

10

Перевір це.

UDE

Це порт Mozilla Universal Charset Detector, і ви можете ним користуватися так ...

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

Вам слід знати, що UDE - це GPL
lindexi

Гаразд, якщо ви переживаєте за ліцензію, то можете скористатися цією. Ліцензований як MIT, і ви можете використовувати його як для програм із відкритим кодом, так і для закритих програм. nuget.org/packages/SimpleHelpers.FileEncoding
Олексій

Ліцензія - MPL з опцією GPL. The library is subject to the Mozilla Public License Version 1.1 (the "License"). Alternatively, it may be used under the terms of either the GNU General Public License Version 2 or later (the "GPL"), or the GNU Lesser General Public License Version 2.1 or later (the "LGPL").
jbtule

Здається, ця вилка на даний момент є найбільш активною і має цілий пакет UDE.Netstandard. github.com/yinyue200/ude
jbtule

дуже корисна бібліотека, впоралася з безліччю різних і незвичайних кодувань! танки!
мшакуров

6

Надання деталей щодо впровадження кроків, запропонованих @CodesInChaos:

1) Перевірте, чи є марка порядку байтів

2) Перевірте, чи правильний файл UTF8

3) Використовуйте локальну кодову сторінку "ANSI" (ANSI, як це визначає Microsoft)

Крок 2 працює тому, що більшість послідовностей, що не належать до ASCII, у кодових сторінках, інших, що UTF8, не є дійсними UTF8. https://stackoverflow.com/a/4522251/867248 пояснює тактику детальніше.

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

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

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


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

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

Дякую! Це вирішило для мене. Але я вважаю за краще використовувати саме reader.Peek() замість while (!reader.EndOfStream) { var line = reader.ReadLine(); }
Harison Silva

reader.Peek()не читає весь потік. Я виявив, що з більшими потоками Peek()був недостатнім. Я використовував reader.ReadToEndAsync()замість цього.
Гері Пендлбері

А що таке Utf8EncodingVerifier?
Пітер Мур

1
@PeterMoore Це кодування для utf8, var Utf8EncodingVerifier = Encoding.GetEncoding("utf-8", new EncoderExceptionFallback(), new DecoderExceptionFallback());воно використовується в tryблоці при читанні рядка. Якщо кодер не зможе розібрати наданий текст (текст не кодується utf8), Utf8EncodingVerifier перекине. Виняток виловлюється, і тоді ми знаємо, що текст не utf8, а за замовчуванням ISO-8859-1
Бертьє Леме

2

Наступні коди - це мої коди Powershell, щоб визначити, чи деякі файли cpp або h або ml кодують ISO-8859-1 (латинська-1) або UTF-8 без BOM, якщо жодне з них не вважає, що це GB18030. Я є китайцем, який працює у Франції, а MSVC економить як латинський-1 на французькому комп’ютері і економить як ГБ на китайському комп’ютері, тому це допомагає мені уникнути проблеми кодування під час обміну вихідними файлами між моєю системою та моїми колегами.

Шлях простий, якщо всі символи знаходяться між x00-x7E, ASCII, UTF-8 та Latin-1 - все одно, але якщо я прочитаю файл, що не є ASCII UTF-8, ми знайдемо спеціальний символ з'явиться , тому спробуйте читати з латиниці-1. У латинській-1 між \ x7F та \ xAF порожньо, тоді як ГБ використовує повний розмір між x00-xFF, тому якщо я отримав будь-який між ними, це не латинська-1

Код написано в PowerShell, але використовує .net, тому його легко перекласти на C # або F #

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

2

.NET не дуже корисний, але ви можете спробувати наступний алгоритм:

  1. спробуйте знайти кодування BOM (байт порядку позначення) ... дуже ймовірно, що його не знайдуть
  2. спробуйте розібратися в різних кодуваннях

Ось дзвінок:

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

Ось код:

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

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

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

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

        return null;   // no encoding found
    }

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

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

        return null;    // no BOM found
    }

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

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

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

        return null;    // 
    }
}

1

Подивіться тут на c #

https://msdn.microsoft.com/en-us/library/system.io.streamreader.currentencoding%28v=vs.110%29.aspx

string path = @"path\to\your\file.ext";

using (StreamReader sr = new StreamReader(path, true))
{
    while (sr.Peek() >= 0)
    {
        Console.Write((char)sr.Read());
    }

    //Test for the encoding after reading, or at least
    //after the first read.
    Console.WriteLine("The encoding used was {0}.", sr.CurrentEncoding);
    Console.ReadLine();
    Console.WriteLine();
}

0

Це може бути корисно

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

using (StreamReader sr = new StreamReader(path))
{ 
    Console.WriteLine(sr.CurrentEncoding);                        
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.