はじめに
以下のコードを書いていたときに、BOMに関して挙動が違うことに気づきました。
// utf-8のBOM付き.txtを読み込みたい // パターンA var x = File.ReadAllText(path); // パターンB var tmp = File.ReadAllBytes(path); var y = Encoding.UTF8.GetString(tmp);
小ネタですが、地味に詰まったことがあるので書き残しておこうと思います。
実験
using System.Runtime.InteropServices; using System.Text; var path = "path/to/text1.txt"; // utf-8のBOM付き (EF BB BF) // EF BB BF 48 65 6C 6C 6F File.WriteAllText(path, "Hello", Encoding.UTF8); // File.ReaddAllTextはBOMを無視して読み込む var text1 = File.ReadAllText(path); // 48 0 65 0 6c 0 6c 0 6f 0 (BOMなし) foreach (var x in MemoryMarshal.Cast<char, byte>(text1.AsSpan())) { Console.Write($"{x:x} "); } Console.WriteLine(""); // File.ReaddAllText(Encoding指定)はBOMを無視して読み込む var text2 = File.ReadAllText(path, Encoding.UTF8); // 48 0 65 0 6c 0 6c 0 6f 0 (BOMなし) foreach (var x in MemoryMarshal.Cast<char, byte>(text2.AsSpan())) { Console.Write($"{x:x} "); } Console.WriteLine(""); // Encoding.UTF8.GetStringで読み込む var fileBytes = File.ReadAllBytes(path); // ef bb bf 48 65 6c 6c 6f (utf-8のBOMあり) Console.WriteLine(string.Join(" ", fileBytes.Select(x => x.ToString("x")))); var text3 = Encoding.UTF8.GetString(fileBytes); // ff fe 48 0 65 0 6c 0 6c 0 6f 0 (utf-16のBOMあり) foreach (var x in MemoryMarshal.Cast<char, byte>(text3.AsSpan())) { Console.Write($"{x:x} "); } // StreamReaderもBOMを無視して読み込む var text4 = new StreamReader(path, Encoding.UTF8, true).ReadToEnd(); // 48 0 65 0 6c 0 6c 0 6f 0 (BOMなし) foreach (var x in MemoryMarshal.Cast<char, byte>(text4.AsSpan())) { Console.Write($"{x:x} "); } Console.WriteLine("");
補足するとC#のstring
はutf-16
です。
Encoding.UTF8.GetString(bytes)
:utf-16
のBOM付きで格納File.ReadAllText(path)
: BOMを削除File.ReadAllText(path, Encoding.Utf8)
: BOMを削除new StreamReader(path, Encoding.UTF8, true).ReadToEnd()
; BOMを削除
という結果になりました。