.NET(C#):文字コード(Encoding)とバイト順序タグ(BOM)

124551 ワード

文字順タグ(BOM)とは


コンピュータ内部のデータストレージはすべてバイナリで、1段のデータのバイナリストレージフォーマットを知っているだけで、このデータは意味があります.テキストファイルとは、特定の文字符号化でバイナリソースデータを文字に変換することである.多くのテキストエディタは異なる符号化されたテキストファイルを編集することができますが、テキストエディタはどのようにソースバイナリデータを通じてこのデータのテキスト符号化を知るのでしょうか.答えは文字順表記(Byte Order Mark)で、文章の中で私たちは英語でBOMという名詞を統一的に簡潔に書きます.
 
次にUnicodeコードをよく使うBOMを示します
UTF-8: EF BB BF UTF-16 big endian: FE FF UTF-16 little endian: FF FE UTF-32 big endian: 00 00 FE FF UTF-32 little endian: FF FE 00 00
 
ディレクトリに戻る

.NETのEncodingクラスとBOM


はい.NETの世界では,Encodingの静的属性を用いてEncodingクラスを得ることが多く,ここから得られる符号化のデフォルトはBOMを提供する(BOMがサポートされている場合).
指定された符号化にBOMを提供しないようにするには、この符号化クラスを手動で構築する必要があります.
// BOM Encoding 
Encoding utf8NoBom = new UTF8Encoding(false);
Encoding utf16NoBom = new UnicodeEncoding(false, false);
Encoding utf32NoBom = new UTF32Encoding(false, false);

またここではLittle-endianを構築しており,Big-endian構築関数にもパラメータがある.
次にUnicodeEncodingクラスはUTF 16符号化を表す.
 
EncodingクラスのGetPreambleメソッドは、現在の符号化によって提供されているBOMを返します.次のコードがあります.
public static void Main()
{
    Encoding utf32WithBom = Encoding.UTF32;
    Encoding utf32NoBom = new UTF32Encoding(false, false);

    Console.WriteLine("UTF32 With BOM");
    PrintBytes(utf32WithBom.GetPreamble());

    Console.WriteLine("UTF32 No Bom");
    PrintBytes(utf32NoBom.GetPreamble());
}

static void PrintBytes(byte[] bytes)
{
    if (bytes == null || bytes.Length == 0)
        Console.WriteLine("");
    foreach (var b in bytes)
        Console.Write("{0:X2} ", b);
    Console.WriteLine();
}

出力:
UTF32 With BOM
FF FE 00 00
UTF32 No Bom

 
ディレクトリに戻る

ファイルの読み書きとBOM


テキストの書き込み時、StreamWriterクラスとFile.WriteAllTextメソッドのデフォルト符号化はBOMを持たないUTF 8である.
もちろん、上記のように構造関数によって他の符号化を指定することができます.例:
public static void Main()
{
    Encoding utf32bigbom = new UTF32Encoding(true, true);
    Encoding utf32litbom = new UTF32Encoding(false, true);
    Encoding utf32litnobom = new UTF32Encoding(false, false);

    var content = "abcde";
    WriteAndPrint(content, utf32bigbom);
    WriteAndPrint(content, utf32litbom);
    WriteAndPrint(content, utf32litnobom);
}

static void WriteAndPrint(string content, Encoding enc)
{
    var path = Path.GetTempFileName();
    File.WriteAllText(path, content, enc);
    PrintBytes(File.ReadAllBytes(path));
}

static void PrintBytes(byte[] bytes)
{
    if (bytes == null || bytes.Length == 0)
        Console.WriteLine("");
    foreach (var b in bytes)
        Console.Write("{0:X2} ", b);
    Console.WriteLine();
}

出力:
00 00 FE FF 00 00 00 61 00 00 00 62 00 00 00 63 00 00 00 64 00 00 00 65
FF FE 00 00 61 00 00 00 62 00 00 00 63 00 00 00 64 00 00 00 65 00 00 00
61 00 00 00 62 00 00 00 63 00 00 00 64 00 00 00 65 00 00 00

00 FE FFはUTF 32 Big-endianのBOMであり、FFFE 00はUTF 32 Little-endianのBOMであり、3行目はBOMを加えていないUTF 32のソースバイナリデータであることがわかる.
 
テキストを読むとき、StringReaderクラスを構築して指定した文字列パスまたはStreamオブジェクトを入れると、StringReaderの表現は自動的にBOMによって文字符号化が判定されます.もちろん、手動で符号化を指定することもできます(特にBOMのテキストデータがなく、手動で符号化しないとテキストファイルを正しく読み取ることができません).
同様に、FileクラスのReadAllTextも同様の機能を備えているが、注意深く読者はReflectorのFileを発見する可能性がある.ReadAllTextのソースコードはUTF 8でエンコードされたStreamReaderでファイルを読み込みますが、実はStreamReaderのこのコンストラクション関数を呼び出しています.
public StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize)
{   /*  */  }

では、特定の符号化が入力されますが、このdetectEncodingFromByteOrderMarksパラメータはtrueであり、StreamReaderはBOMを自動的に認識してファイルを読みます.
コード:
public static void Main()
{
    var path1 = Path.GetTempFileName();
    var path2 = Path.GetTempFileName();

    string content = "abc";

    // BOM UTF8 
    File.WriteAllText(path1, content);
    // BOM UTF8 
    File.WriteAllText(path2, content, Encoding.UTF8);

    PrintBytes(File.ReadAllBytes(path1));
    PrintBytes(File.ReadAllBytes(path2));

    Console.WriteLine(File.ReadAllText(path1));
    Console.WriteLine(File.ReadAllText(path2));

}

static void PrintBytes(byte[] bytes)
{
    foreach (var b in bytes)
        Console.Write("{0:X2} ", b);
    Console.WriteLine();
}

出力:
61 62 63
EF BB BF 61 62 63
abc
abc

上にはBOMがないファイルがありますが、デフォルトUTF 8のためエラーはありませんが、他の符号化はそうではありません.
 
例えば、次のコードをUTF 32で符号化します.
public static void Main()
{
    var path1 = Path.GetTempFileName();
    var path2 = Path.GetTempFileName();

    string content = "abc";

    // BOM UTF32 
    File.WriteAllText(path1, content, Encoding.Unicode);
    // BOM UTF32 
    File.WriteAllText(path2, content, new UnicodeEncoding(false, false));


    PrintBytes(File.ReadAllBytes(path1));
    PrintBytes(File.ReadAllBytes(path2));

    // BOM 
    string c1 = File.ReadAllText(path1);
    //path2 BOM, UTF8 
    string c2 = File.ReadAllText(path2);
    //path2 BOM, UTF16 
    string c3 = File.ReadAllText(path2, Encoding.Unicode);

    ShowContent(c1);
    ShowContent(c2);
    ShowContent(c3);
}

static void ShowContent(string content)
{
    Console.WriteLine(" :{0}   :{1}", content.Length, content);
}


static void PrintBytes(byte[] bytes)
{
    foreach (var b in bytes)
        Console.Write("{0:X2} ", b);
    Console.WriteLine();
}

出力:
FF FE 61 00 62 00 63 00       // 1  BOM UTF16
61 00 62 00 63 00             // 2  BOM UTF16
 :3   :abc   // 1
 :6   :a     // 2
 :3   :abc   // UTF16 2

4行目を見ると、BOMのないUTF 16ファイルはUTF 8として読まれているため、元の3文字は6文字と読まれていた.
ディレクトリに戻る

BOMの削除方法について


テキストのバイナリデータを処理する必要がある場合があります.この場合、すべてのテキストのバイナリ配列を得る必要があります.バイナリデータを読み取ることができる場合、BOMは先頭に添付され、符号化されたBOMの長さが異なります(BOMがない符号化もあります).この場合、BOMをフィルタリングする方法が必要です.
Encoding.GetPreambleメソッドの後(前述したように)、すべてが難しくないわけではありません.
ここでは2つの関数を与え,比較的一般的なシナリオでもある.
一つはBOMを除去するバイト配列を直接得ることである.もう1つは、ストリームの位置をBOMに移動した後、後続のストリーム操作が各文字のバイナリデータに直接適用されるようにすることである.
public static void Main()
{
    var path = Path.GetTempFileName();
    File.WriteAllText(path, "a123 ", Encoding.UTF8);

    PrintBytes(File.ReadAllBytes(path));

    //1
    PrintBytes(GetBytesWithoutBOM(path, Encoding.UTF8));

    //2
    using (Stream stream = File.OpenRead(path))
    {
        SkipBOM(stream, Encoding.UTF8);
        int data;
        while ((data = stream.ReadByte()) != -1)
            Console.Write("{0:X2} ", data);
        Console.WriteLine();
    }
}

static byte[] GetBytesWithoutBOM(string path, Encoding enc)
{
    //LINQ
    return File.ReadAllBytes(path).Skip(enc.GetPreamble().Length).ToArray();
}

static void SkipBOM(Stream stream, Encoding enc)
{
    stream.Seek(enc.GetPreamble().Length, SeekOrigin.Begin);
}


static void PrintBytes(byte[] bytes)
{
    foreach (var b in bytes)
        Console.Write("{0:X2} ", b);
    Console.WriteLine();
}

出力:
EF BB BF 61 31 32 33 E4 B8 80
61 31 32 33 E4 B8 80
61 31 32 33 E4 B8 80

(結果はいずれも正しい)
何か質問があれば、Coldfunctionに質問してもいいです.

Related Posts:


.NET(C#):Systemを使用します.Text.Decoderクラスは「ストリームテキスト」を処理する.
.NET(C#):ファイルから認識符号化.NET(C#):中国語コードファイルの正確な読み取りについて.NET(C#):反射による方法の解析