Cara efektif untuk menemukan Encoding file apa pun

115

Ya adalah pertanyaan yang paling sering, dan masalah ini tidak jelas bagi saya dan karena saya tidak tahu banyak tentangnya.

Tetapi saya ingin cara yang sangat tepat untuk menemukan Encoding file. Setepat Notepad ++.

Fábio Antunes
sumber
Pengodean yang mana? UTF-8 vs UTF-16, big vs little endian? Atau apakah Anda merujuk ke halaman kode MSDos lama, seperti shift-JIS atau Cyrillic dll?
dthorpe
Kemungkinan duplikat lainnya: stackoverflow.com/questions/436220/…
Oded
@Oded: Kutipan "Metode getEncoding () akan mengembalikan encoding yang telah disiapkan (baca JavaDoc) untuk streaming. Metode tersebut tidak akan menebak encoding untuk Anda.".
Fábio Antunes
2
Untuk beberapa bacaan latar belakang, joelonsoftware.com/articles/Unicode.html adalah bacaan yang bagus. Jika ada satu hal yang perlu Anda ketahui tentang teks, itu adalah tidak ada yang namanya teks biasa.
Martijn

Jawaban:

155

The StreamReader.CurrentEncodingproperti jarang mengembalikan file teks yang benar pengkodean bagi saya. Saya lebih berhasil dalam menentukan keuletan file, dengan menganalisis byte order mark (BOM). Jika file tidak memiliki BOM, ini tidak dapat menentukan pengkodean file.

* DIPERBARUI 4/08/2020 untuk menyertakan deteksi UTF-32LE dan mengembalikan pengkodean yang benar untuk 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 Beban
sumber
3
+1. Ini bekerja untuk saya juga (sedangkan detectEncodingFromByteOrderMarks tidak). Saya menggunakan "FileStream baru (nama file, FileMode.Open, FileAccess.Read)" untuk menghindari IOException karena file tersebut hanya dapat dibaca.
Polyfun
56
File UTF-8 bisa tanpa BOM, dalam hal ini akan mengembalikan ASCII dengan tidak benar.
pengguna626528
3
Jawaban ini salah. Melihat sumber referensi untuk StreamReader, implementasi itulah yang lebih banyak orang akan ingin. Mereka membuat pengkodean baru daripada menggunakan Encoding.Unicodeobjek yang sudah ada , sehingga pemeriksaan kesetaraan akan gagal (yang mungkin jarang terjadi karena, misalnya, Encoding.UTF8dapat mengembalikan objek yang berbeda), tetapi (1) tidak menggunakan format UTF-7 yang benar-benar aneh, (2) default ke UTF-8 jika tidak ada BOM yang ditemukan, dan (3) dapat diganti untuk menggunakan encoding default yang berbeda.
hanggar
2
saya lebih sukses dengan StreamReader baru (nama file, benar) .CurrentEncoding
Benoit
4
Ada kesalahan mendasar dalam kode; saat Anda mendeteksi tanda tangan UTF32 big-endian ( ), Anda mengembalikan sistem yang disediakan , yang merupakan encoding little-endian (seperti disebutkan di sini ). Dan juga, seperti dicatat oleh @Nyerguds, Anda masih belum mencari UTF32LE, yang memiliki tanda tangan (menurut en.wikipedia.org/wiki/Byte_order_mark ). Seperti yang dicatat oleh pengguna tersebut, karena ini sedang berlangsung, pemeriksaan tersebut harus dilakukan sebelum pemeriksaan 2-byte. 00 00 FE FFEncoding.UTF32FF FE 00 00
Glenn Slayden
44

Kode berikut berfungsi dengan baik untuk saya, menggunakan StreamReaderkelas:

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

Triknya adalah menggunakan Peekpanggilan, jika tidak, .NET belum melakukan apa-apa (dan belum membaca pembukaan, BOM). Tentu saja, jika Anda menggunakan ReadXXXpanggilan lain sebelum memeriksa encoding, itu juga berfungsi.

Jika file tidak memiliki BOM, maka defaultEncodingIfNoBompengkodean akan digunakan. Ada juga StreamReader tanpa metode kelebihan beban ini (dalam hal ini, pengkodean Default (ANSI) akan digunakan sebagai defaultEncodingIfNoBom), tetapi saya merekomendasikan untuk menentukan apa yang Anda anggap sebagai pengkodean default dalam konteks Anda.

Saya telah menguji ini dengan sukses dengan file dengan BOM untuk UTF8, UTF16 / Unicode (LE & BE) dan UTF32 (LE & BE). Ini tidak bekerja untuk UTF7.

Simon Mourier
sumber
Saya mendapatkan kembali apa yang ditetapkan sebagai pengkodean default. Mungkinkah saya melewatkan sesuatu?
Ram
1
@DRAM - ini dapat terjadi jika file tidak memiliki BOM
Simon Mourier
Terima kasih @Simon Mourier. Saya tidak berharap pdf / file apa pun saya tidak akan memiliki bom. Link stackoverflow.com/questions/4520184/… ini mungkin berguna bagi seseorang yang mencoba mendeteksi tanpa bom.
Ram
1
Di PowerShell saya harus menjalankan $ reader.close (), atau terkunci dari penulisan. 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 Ini tidak berfungsi jika pengkodean file adalahUTF-8 without BOM
Ozkan
11

Saya akan mencoba langkah-langkah berikut:

1) Periksa apakah ada Byte Order Mark

2) Periksa apakah file tersebut UTF8 valid

3) Gunakan halaman kode "ANSI" lokal (ANSI seperti yang didefinisikan oleh Microsoft)

Langkah 2 berfungsi karena sebagian besar urutan non ASCII dalam halaman kode selain UTF8 bukan UTF8 yang valid.

CodesInChaos
sumber
Sepertinya ini jawaban yang lebih benar, karena jawaban lain tidak berhasil untuk saya. Seseorang dapat melakukannya dengan File.OpenRead dan .Read-ing beberapa byte pertama dari file tersebut.
user420667
1
Langkah 2 adalah sejumlah pekerjaan pemrograman untuk memeriksa pola bit.
Nyerguds
1
Saya tidak yakin decoding benar-benar melempar pengecualian, atau jika itu hanya menggantikan urutan yang tidak dikenali dengan '?'. Lagipula, aku pergi dengan menulis kelas memeriksa pola sedikit.
Nyerguds
3
Saat Anda membuat sebuah instance, Utf8EncodingAnda dapat mengirimkan parameter tambahan yang menentukan apakah pengecualian harus dilemparkan atau jika Anda lebih suka kerusakan data diam.
CodesInChaos
1
Saya suka jawaban ini. Sebagian besar pengkodean (mungkin 99% dari kasus penggunaan Anda) akan berupa UTF-8 atau ANSI (halaman kode Windows 1252). Anda dapat memeriksa apakah string berisi karakter pengganti (0xFFFD) untuk menentukan apakah encoding gagal.
marsze
10

Periksa ini.

UDE

Ini adalah port Mozilla Universal Charset Detector dan Anda dapat menggunakannya seperti ini ...

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.");
        }
    }
}
Alexei Agüero Alba
sumber
Anda harus tahu bahwa UDE adalah GPL
lindexi
Ok jika Anda khawatir tentang lisensinya maka Anda dapat menggunakan yang ini. Berlisensi sebagai MIT dan Anda dapat menggunakannya untuk perangkat lunak sumber terbuka dan sumber tertutup. nuget.org/packages/SimpleHelpers.FileEncoding
Alexei Agüero Alba
Lisensi adalah MPL dengan opsi 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
Tampaknya garpu ini saat ini yang paling aktif dan memiliki paket nuget UDE.Netstandard. github.com/yinyue200/ude
jbtule
perpustakaan yang sangat berguna, mengatasi banyak pengkodean yang berbeda dan tidak biasa! tank!
mshakurov
6

Memberikan detail implementasi untuk langkah-langkah yang diusulkan oleh @CodesInChaos:

1) Periksa apakah ada Byte Order Mark

2) Periksa apakah file tersebut UTF8 valid

3) Gunakan halaman kode "ANSI" lokal (ANSI seperti yang didefinisikan oleh Microsoft)

Langkah 2 berfungsi karena sebagian besar urutan non ASCII dalam halaman kode selain UTF8 bukan UTF8 yang valid. https://stackoverflow.com/a/4522251/867248 menjelaskan taktik ini lebih detail.

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();
        ...
Berthier Lemieux
sumber
Terima kasih! Ini terpecahkan untuk saya. Tapi saya lebih suka menggunakan reader.Peek() daripada while (!reader.EndOfStream) { var line = reader.ReadLine(); }
Harison Silva
reader.Peek()tidak membaca keseluruhan aliran. Saya menemukan bahwa dengan aliran yang lebih besar, Peek()tidak memadai. Saya menggunakan reader.ReadToEndAsync()sebagai gantinya.
Gary Pendlebury
Dan apakah Utf8EncodingVerifier itu?
Peter Moore
1
@PeterMoore Ini adalah pengkodean untuk utf8, var Utf8EncodingVerifier = Encoding.GetEncoding("utf-8", new EncoderExceptionFallback(), new DecoderExceptionFallback());Ini digunakan di tryblok saat membaca baris. Jika pembuat enkode gagal mengurai teks yang disediakan (teks tidak dienkode dengan utf8), Utf8EncodingVerifier akan melempar. Pengecualian ditangkap dan kita kemudian tahu teksnya bukan utf8, dan default ke ISO-8859-1
Berthier Lemieux
2

Kode berikut adalah kode Powershell saya untuk menentukan apakah beberapa file cpp atau h atau ml dikodekan dengan ISO-8859-1 (Latin-1) atau UTF-8 tanpa BOM, jika tidak ada maka anggaplah itu GB18030. Saya orang China yang bekerja di Prancis dan MSVC menyimpan sebagai Latin-1 di komputer Prancis dan menyimpan sebagai GB di komputer China jadi ini membantu saya menghindari masalah encoding saat melakukan pertukaran file sumber antara sistem saya dan kolega saya.

Caranya sederhana, jika semua karakter antara x00-x7E, ASCII, UTF-8 dan Latin-1 semuanya sama, tetapi jika saya membaca file non ASCII oleh UTF-8, kita akan menemukan karakter khusus muncul , jadi cobalah membaca dengan Latin-1. Dalam Latin-1, antara \ x7F dan \ xAF kosong, sedangkan GB menggunakan penuh antara x00-xFF jadi jika saya mendapatkan di antara keduanya, itu bukan Latin-1

Kode tersebut ditulis dalam PowerShell, tetapi menggunakan .net sehingga mudah untuk diterjemahkan ke dalam C # atau 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');
Enzojz
sumber
2

.NET tidak terlalu membantu, tetapi Anda dapat mencoba algoritme berikut:

  1. mencoba untuk menemukan pengkodean dengan BOM (tanda urutan byte) ... sangat mungkin tidak dapat ditemukan
  2. coba parsing menjadi penyandiaksaraan yang berbeda

Inilah panggilannya:

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");

Ini kodenya:

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;    // 
    }
}
Pacurar Stefan
sumber
1

Cari di sini untuk 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();
}
Sedrick
sumber
0

Semoga bermanfaat

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

using (StreamReader sr = new StreamReader(path))
{ 
    Console.WriteLine(sr.CurrentEncoding);                        
}
raushan
sumber