Эффективный способ найти кодировку любого файла

115

Да - самый частый вопрос, и этот вопрос для меня непонятен, так как я мало о нем знаю.

Но я хотел бы очень точный способ найти файл Encoding. Такой точный, как Notepad ++.

Фабио Антунес
источник
Какие кодировки? UTF-8 против UTF-16, big vs little endian? Или вы имеете в виду старые кодовые страницы MSDos, такие как shift-JIS или кириллица и т. Д.?
dthorpe
Еще один возможный дубликат: stackoverflow.com/questions/436220/…
Одед
@Oded: Цитата: «Метод getEncoding () вернет кодировку, которая была настроена (прочтите JavaDoc) для потока. Он не угадает за вас кодировку.».
Фабио Антунес,
2
Для ознакомления с дополнительными сведениями можно обратиться к joelonsoftware.com/articles/Unicode.html . Если есть одна вещь, которую вы должны знать о тексте, так это то, что такой вещи, как простой текст, не существует.
Martijn

Ответы:

155

StreamReader.CurrentEncodingСвойство редко возвращает правильный текстовый файл , кодирующий для меня. Я добился большего успеха в определении порядка байтов файла, анализируя его метку порядка байтов (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;
}
2Жаба
источник
3
+1. Это сработало и для меня (тогда как detectEncodingFromByteOrderMarks - нет). Я использовал «новый FileStream (имя файла, FileMode.Open, FileAccess.Read)», чтобы избежать исключения IOException, потому что файл доступен только для чтения.
Polyfun
56
Файлы UTF-8 могут быть без спецификации, в этом случае ASCII будет возвращаться некорректно.
user626528
3
Это неверный ответ. Глядя на справочный источник для StreamReader, что реализация того, что все больше людей хотят. Они создают новые кодировки, а не используют существующие Encoding.Unicodeобъекты, поэтому проверки на равенство завершатся ошибкой (что может случиться редко, потому что, например, Encoding.UTF8могут возвращать разные объекты), но он (1) не использует действительно странный формат UTF-7, (2) по умолчанию используется UTF-8, если спецификация не найдена, и (3) можно переопределить, чтобы использовать другую кодировку по умолчанию.
ангар
2
у меня был больший успех с новым StreamReader (имя файла, истина) .CurrentEncoding
Бенуа
4
В коде есть фундаментальная ошибка; когда вы обнаруживаете подпись UTF32 с прямым порядком байтов ( ), вы возвращаете предоставленную системой , которая является кодировкой с прямым порядком байтов (как указано здесь ). А также, как отмечает @Nyerguds, вы все еще не ищете UTF32LE, у которого есть подпись (согласно en.wikipedia.org/wiki/Byte_order_mark ). Как заметил этот пользователь, эта проверка должна происходить до двухбайтовых проверок, поскольку она является подпадающей. 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 ничего не сделал (и не прочитал преамбулу, спецификацию). Конечно, если вы используете любой другой ReadXXXвызов перед проверкой кодировки, он тоже работает.

Если у файла нет спецификации, то defaultEncodingIfNoBom будет использоваться кодировка. Существует также StreamReader без этого метода перегрузки (в этом случае кодировка по умолчанию (ANSI) будет использоваться как defaultEncodingIfNoBom), но я рекомендую определить, что вы считаете кодировкой по умолчанию в вашем контексте.

Я успешно протестировал это с файлами со спецификацией для UTF8, UTF16 / Unicode (LE & BE) и UTF32 (LE & BE). Это не работает для UTF7.

Саймон Мурье
источник
Я возвращаю то, что установлено по умолчанию. Могу ли я что-то упустить?
Ram
1
@DRAM - это может произойти, если у файла нет спецификации
Саймон Мурье,
Спасибо @Simon Mourier. Я не ожидал, что мой pdf / любой файл не будет создан. Эта ссылка stackoverflow.com/questions/4520184/… может быть полезна для тех, кто пытается обнаружить без бомбы.
Ram
1
В PowerShell мне пришлось запустить $ 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
Озкан
11

Я бы попробовал следующие шаги:

1) Проверьте, есть ли отметка порядка байтов

2) Убедитесь, что файл действителен UTF8

3) Используйте локальную кодовую страницу "ANSI" (ANSI, как ее определяет Microsoft)

Шаг 2 работает, потому что большинство последовательностей не ASCII в кодовых страницах, кроме UTF8, не являются допустимыми UTF8.

CodesInChaos
источник
Это кажется более правильным ответом, поскольку другой ответ мне не подходит. Это можно сделать с помощью File.OpenRead и .Read, прочитав первые несколько байтов файла.
user420667
1
Шаг 2 - это целая куча программных работ для проверки битовых шаблонов.
Nyerguds
1
Я не уверен, что декодирование действительно вызывает исключения или просто заменяет нераспознанные последовательности на '?'. В любом случае, я решил написать небольшой класс проверки шаблонов.
Nyerguds
3
Когда вы создаете экземпляр, Utf8Encodingвы можете передать дополнительный параметр, который определяет, следует ли генерировать исключение или вы предпочитаете скрытое повреждение данных.
CodesInChaos
1
Мне нравится этот ответ. Большинство кодировок (например, 99% ваших случаев использования) будут либо UTF-8, либо ANSI (кодовая страница Windows 1252). Вы можете проверить, содержит ли строка заменяющий символ (0xFFFD), чтобы определить, не удалось ли выполнить кодирование.
marsze
10

Проверь это.

УДЭНСКИЙ

Это порт 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.");
        }
    }
}
Алексей Агуэро Альба
источник
Вы должны знать , что это УДЭНСКИЙ 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
Похоже, что этот форк в настоящее время является наиболее активным и имеет пакет nuget 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(); }
Харисон Сильва
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 (Latin-1) или UTF-8 без спецификации, если ни то, ни другое, тогда предположите, что это GB18030. Я китаец, работающий во Франции, и MSVC сохраняет как Latin-1 на французском компьютере и сохраняет как GB на китайском компьютере, так что это помогает мне избежать проблем с кодированием при обмене исходными файлами между моей системой и моими коллегами.

Способ прост, если все символы находятся между x00-x7E, ASCII, UTF-8 и Latin-1 все одинаковы, но если я прочитаю файл не ASCII с помощью UTF-8, мы найдем специальный символ , поэтому попробуйте читать с помощью Latin-1. В Latin-1 между \ x7F и \ xAF пусто, в то время как GB использует full между x00-xFF, поэтому, если у меня есть что-то между ними, это не Latin-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

Ищите здесь С #

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);                        
}
Раушан
источник