なぜExcelでCSVが文字化けするのか。BOMとShift_JISの話
CSVをExcelで開いたら日本語が記号の羅列になった、という経験は多いと思う。原因は壊れたファイルではなく、Excelが何のつもりでそのファイルを読んだかにある。
文字コードは「どのバイト列をどの文字と読むか」の取り決め
同じ内容のCSVを、4つの文字コードで保存してバイト数を測った。
名前,年齢,住所
山田太郎,30,東京都
| 文字コード | バイト数 | 先頭6バイト |
|---|---|---|
| UTF-8 | 47 | E5 90 8D E5 89 8D |
| UTF-8 + BOM | 50 | EF BB BF E5 90 8D |
| Shift_JIS (CP932) | 34 | 96 BC 91 4F 2C 94 |
| EUC-JP | 34 | CC BE C1 B0 2C C7 |
先頭の「名」という1文字が、UTF-8では E5 90 8D、Shift_JISでは 96 BC になる。同じ文字でも、バイト列は文字コードごとに違う。
だから、UTF-8で書かれたバイト列をShift_JISのつもりで読むと、E5 90 8D を「縺榊」のような別の文字として解釈してしまう。これが文字化けだ。
Excelは、目印が無いとShift_JISとして読む
Windowsの日本語環境では、ExcelはCSVを開くときCP932(Shift_JISの拡張)として読もうとする。UTF-8で保存したCSVをそのまま開くと化けるのはこのためだ。
これを避けるのがBOMで、ファイルの先頭に付ける3バイトの目印だ。
EF BB BF
上の表で、UTF-8が47バイト、UTF-8+BOMが50バイトになっているのがそれだ。この3バイトが「これはUTF-8です」と伝える。 Excelはそれを見てUTF-8として読む。
新しいバージョンのExcelでは改善されている場合もあるが、BOMを付けておけば環境を問わず確実になる。
このサイトのツールに「Excelで開けるようにする」というボタンを置いているが、中身はUTF-8 + BOM + CRLFにしているだけだ。
化けた文字は、たいてい元に戻せる
文字化けを見ると壊れたように見えるが、バイト列そのものは無傷のことが多い。読み方を間違えているだけなので、正しい文字コードで読み直せば元に戻る。
戻らないのは、化けた状態で保存し直したときだ。「縺榊」という文字として保存されてしまうと、そこから元の文字は復元できない。
だから、化けたファイルを見たら保存せずに閉じるのが正しい。
Shift_JISのほうが小さい
表を見て意外に思うかもしれないが、日本語のCSVはShift_JISのほうが小さい。47バイト対34バイトで、3割近く違う。1文字あたりで見るとこうなる。
| 文字 | UTF-8 | Shift_JIS |
|---|---|---|
| あ | 3バイト | 2バイト |
| 漢 | 3バイト | 2バイト |
| A | 1バイト | 1バイト |
| ア(半角カナ) | 3バイト | 1バイト |
UTF-8は世界中の文字を扱える代わりに、日本語1文字に3バイト使う。Shift_JISは日本語のために作られているので2バイトで済む。
とはいえ、いまどき数十%のサイズ差でShift_JISを選ぶ理由は薄い。選ぶとしたら、次の節の理由のほうが重い。
Shift_JISにすると、消える文字がある
Shift_JISに変換できない文字がある。変換すると、その文字は失われる。
| 文字 | Shift_JIS(CP932) | |
|---|---|---|
| ① | 丸数字 | 87 40 |
| ㈱ | 括弧付き | 87 8A |
| Ⅷ | ローマ数字 | 87 5B |
| ♥ | ハート | 変換不可 |
| 𠮟 | 「叱」の異体字 | 変換不可 |
| 🍣 | 絵文字 | 変換不可 |
丸数字や㈱は通る。これらは「機種依存文字」と呼ばれて嫌われてきたが、CP932には含まれている。
一方、絵文字とUnicodeの拡張領域の漢字は通らない。人名に使われる異体字がここに入ることがあるので、名簿をShift_JISにするときは注意がいる。
「ソ」と「表」が壊れる話
もうひとつ、Shift_JIS特有の厄介な性質がある。
Shift_JISの2バイト文字のうち、2バイト目が 0x5C になるものがある。0x5C はバックスラッシュ(日本語環境では円記号)のコードだ。
CP932で調べると、該当する文字は37文字あった。
ソ 予 偆 兔 十 喀 噂 圭 媾 彌 拿 暴 曾 杤 構 欺 歃 浬 濬 申 畚
砡 禄 秉 箪 綵 能 臀 藹 蚕 表 觸 貼 軆 鐔 饅 鷭
「ソ」「表」「十」「能」「貼」「構」「暴」あたりは、普通のCSVに出てくる。
何が起きるかというと、文字コードを意識せずに1バイトずつ読むプログラムが、2バイト目の 0x5C をエスケープ記号だと勘違いする。結果、その次の文字が消えたり、引用符の対応が崩れて列がずれたりする。
「ソ」で始まるデータで不具合が出る、という話は昔から知られていて、「ダメ文字」と呼ばれている。UTF-8では起きない。
どれを選べばいいか
| やりたいこと | 選ぶもの |
|---|---|
| Excelで開きたいだけ | UTF-8 + BOM。文字が失われない |
| 古いシステムに渡す | Shift_JIS。ただし消える文字が無いか確認する |
| プログラムで処理する | UTF-8(BOMなし) |
| 相手の環境が分からない | UTF-8 + BOM |
迷ったらUTF-8 + BOMでいい。Excelで開けて、文字も失われない。Shift_JISを選ぶのは、相手がそれしか受け付けないと分かっているときだけだ。
計測の条件
Python 3 の標準エンコーダで測った。Shift_JIS は Windows で使われる拡張版のCP932 を指定している。ダメ文字の一覧は、U+3000 から U+9FFF の範囲でCP932 に変換し、2バイト目が 0x5C になるものを数えた。