วิธีที่มีประสิทธิภาพในการค้นหาการเข้ารหัสของไฟล์


115

ใช่เป็นคำถามที่พบบ่อยที่สุดและเรื่องนี้คลุมเครือสำหรับฉันและเนื่องจากฉันไม่ค่อยรู้เรื่องนี้มากนัก

แต่ฉันต้องการวิธีที่แม่นยำมากในการค้นหาการเข้ารหัสไฟล์ แม่นยำเหมือน Notepad ++



การเข้ารหัสใด UTF-8 vs UTF-16, big vs little endian? หรือคุณกำลังอ้างถึงรหัสหน้า MSDos แบบเก่าเช่น shift-JIS หรือ Cyrillic เป็นต้น
dthorpe

ซ้ำกันได้อีก: stackoverflow.com/questions/436220/…
Oded

@Oded: Quote "เมธอด getEncoding () จะส่งคืนการเข้ารหัสที่ตั้งค่าไว้ (อ่าน JavaDoc) สำหรับสตรีมโดยจะไม่เดาการเข้ารหัสให้คุณ"
Fábio Antunes

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 เนื่องจากไฟล์เป็นแบบอ่านอย่างเดียว
Polyfun

56
ไฟล์ UTF-8 สามารถไม่มี BOM ได้ในกรณีนี้จะส่งคืน ASCII ไม่ถูกต้อง
user626528

3
คำตอบนี้ผิด มองไปที่แหล่งที่มาของการอ้างอิงสำหรับStreamReaderว่าการดำเนินการเป็นสิ่งที่คนอื่น ๆ จะต้องการ พวกเขาทำการเข้ารหัสใหม่แทนที่จะใช้Encoding.Unicodeวัตถุที่มีอยู่ดังนั้นการตรวจสอบความเท่าเทียมกันจะล้มเหลว (ซึ่งอาจไม่ค่อยเกิดขึ้นอยู่ดีเพราะเช่นEncoding.UTF8สามารถส่งคืนวัตถุที่แตกต่างกัน) แต่ (1) ไม่ได้ใช้รูปแบบ UTF-7 ที่แปลกจริงๆ (2) ค่าเริ่มต้นเป็น UTF-8 หากไม่พบ BOM และ (3) สามารถแทนที่เพื่อใช้การเข้ารหัสเริ่มต้นอื่นได้
โรงเก็บเครื่องบิน

2
ฉันประสบความสำเร็จมากขึ้นกับ StreamReader ใหม่ (ชื่อไฟล์จริง) CurrentEncoding
Benoit

4
มีข้อผิดพลาดพื้นฐานในรหัส เมื่อคุณตรวจพบลายเซ็น UTF32 แบบbig-endian ( ) คุณจะคืนค่าที่ระบบให้มาซึ่งเป็นการเข้ารหัสแบบendian น้อย (ตามที่ระบุไว้ที่นี่ ) และตามที่ระบุไว้โดย @Nyerguds คุณยังไม่พบ UTF32LE ซึ่งมีลายเซ็น(อ้างอิงจากen.wikipedia.org/wiki/Byte_order_mark ) ตามที่ผู้ใช้ระบุไว้เนื่องจากเป็นการย่อยการตรวจสอบนั้นจะต้องมาก่อนการตรวจสอบแบบ 2 ไบต์ 00 00 FE FFEncoding.UTF32FF FE 00 00
Glenn Slayden

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


ฉันได้รับคืนสิ่งที่ตั้งไว้เป็นการเข้ารหัสเริ่มต้น ฉันจะขาด momething ได้ไหม?
ราม

1
@DRAM - สิ่งนี้สามารถเกิดขึ้นได้หากไฟล์ไม่มี BOM
Simon Mourier

ขอบคุณ @Simon Mourier ฉันไม่คาดหวังว่า pdf / ไฟล์ใด ๆ ของฉันจะไม่มี Bom ลิงค์นี้stackoverflow.com/questions/4520184/…อาจเป็นประโยชน์สำหรับคนที่พยายามตรวจจับโดยไม่ต้องระเบิด
ราม

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
Ozkan

11

ฉันจะลองทำตามขั้นตอนต่อไปนี้:

1) ตรวจสอบว่ามี Byte Order Mark หรือไม่

2) ตรวจสอบว่าไฟล์เป็น UTF8 ที่ถูกต้องหรือไม่

3) ใช้ codepage "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 codepage 1252) คุณสามารถตรวจสอบว่าสตริงมีอักขระแทนที่ (0xFFFD) หรือไม่เพื่อตรวจสอบว่าการเข้ารหัสล้มเหลวหรือไม่
marsze

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
Alexei Agüero Alba

ใบอนุญาตคือ 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

ห้องสมุดที่มีประโยชน์มากจัดการกับการเข้ารหัสที่แตกต่างและผิดปกติมากมาย! รถถัง!
mshakurov

6

ให้รายละเอียดการใช้งานสำหรับขั้นตอนที่เสนอโดย @CodesInChaos:

1) ตรวจสอบว่ามี Byte Order Mark หรือไม่

2) ตรวจสอบว่าไฟล์เป็น UTF8 ที่ถูกต้องหรือไม่

3) ใช้ codepage "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()แทน
Gary Pendlebury

แล้ว Utf8EncodingVerifier คืออะไร?
Peter Moore

1
@PeterMoore เป็นการเข้ารหัสสำหรับ utf8 var Utf8EncodingVerifier = Encoding.GetEncoding("utf-8", new EncoderExceptionFallback(), new DecoderExceptionFallback());ซึ่งใช้ในtryบล็อกเมื่ออ่านบรรทัด หากตัวเข้ารหัสไม่สามารถแยกวิเคราะห์ข้อความที่ให้มา (ข้อความไม่ได้เข้ารหัสด้วย utf8) Utf8EncodingVerifier จะโยน ข้อยกเว้นถูกจับแล้วเรารู้ว่าข้อความไม่ใช่ utf8 และเริ่มต้นเป็น ISO-8859-1
Berthier Lemieux

2

รหัสต่อไปนี้เป็นรหัส Powershell ของฉันเพื่อตรวจสอบว่าไฟล์ cpp หรือ h หรือ ml บางไฟล์เข้ารหัสด้วย ISO-8859-1 (Latin-1) หรือ UTF-8 โดยไม่มี BOM ถ้าไม่สมมติว่าเป็น GB18030 ฉันเป็นคนจีนที่ทำงานในฝรั่งเศสและ MSVC บันทึกเป็นภาษาละติน -1 บนคอมพิวเตอร์ฝรั่งเศสและบันทึกเป็น GB ในคอมพิวเตอร์ภาษาจีนดังนั้นสิ่งนี้จะช่วยให้ฉันหลีกเลี่ยงปัญหาการเข้ารหัสเมื่อมีการแลกเปลี่ยนไฟล์ต้นฉบับระหว่างระบบของฉันกับเพื่อนร่วมงาน

วิธีง่ายๆคือถ้าอักขระทั้งหมดอยู่ระหว่าง x00-x7E, ASCII, UTF-8 และ Latin-1 จะเหมือนกันหมด แต่ถ้าฉันอ่านไฟล์ที่ไม่ใช่ ASCII โดย UTF-8 เราจะพบว่าอักขระพิเศษ แสดงขึ้น ดังนั้นพยายามอ่านด้วยภาษาละติน -1 ในภาษาลาติน -1 ระหว่าง \ x7F และ \ xAF ว่างเปล่าในขณะที่ GB ใช้เต็มระหว่าง 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);                        
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.