文字コード これだけは覚えておこう ~UTF-8編~
Back to Top
はじめに
#今回は新人さんに向けての記事ということで、我々日本語を扱うプログラマーが長年捕らわれ続けている問題である「文字コード」の問題について語りたいと思います。
現場で起きやすい不具合の多くが文字コードに起因しており、避けて通れない知識です。
「文字コード」には非常に長い歴史があり、当時の技術者たちが直面した制約の中で生まれた、苦悩や試行錯誤の積み重ねの産物です。そのため、現在の姿だけを見て「いまいち」「分かりにくい」と単純に批評できません。しかしながら、私たちはこの問題を避けて通ることはできません。そこで、本稿では文字コードの性質と、陥りやすい事象について、ケースごとに対処方法を示します。
前回のシフトJISに続き、今回は文字コード「Unicode」の「UTF-8」について、これだけは知っておいてほしいポイントを解説します。
今回言いたいポイント
#- もうシフトJISとかやめてUnicodeに統一したいよね
- Unicodeを使う上での注意すべきポイント
- UTF-8におけるBOMについて
Unicodeってなんですか
#Unicodeは、今や世界標準と言ってよいほど広く浸透しており、Javaの内部でも使用されているため、避けては通れない文字コードとなっています。
正確には、Unicodeとは「文字集合」であり、世界中のあらゆる文字(英数字・記号・ひらがな・カタカナ・漢字など)を扱うことを目的としています。各文字には「コードポイント」と呼ばれる一意の番号が割り当てられており、たとえば「A」(半角のA)は U+0041
、「あ」は U+3042
というコードポイントが付けられています。
Unicodeですべての文字を扱えるのであれば、シフトJISなどを使う必要はなく、すべてUnicodeに統一すれば文字コードの問題は解決しそうにも思えますね。
しかし、実際にはUnicodeを使えばすべてが解決するというわけではありません。
今回は、Unicodeのエンコーディング方式のひとつである「UTF-8」で発生しうる問題点について解説します。
UTF-8ってなんですか
#具体的な問題点を解説する前に、Unicodeのエンコーディング方式である「UTF-8」について簡単に触れてみます。
UTF-8というエンコーディング方式は、Unicodeのコードポイント(U+0000
- U+10FFFF
)を、1〜4バイトの可変長で表現します。
たとえば、「A」(U+0041)や「あ」(U+3042)は、次のようにUTF-8で表されます。
- 「A」(
U+0041
) :0x41
(1バイト) - 「あ」(
U+3042
) :0xE3 0x81 0x82
(3バイト)
UTF-8では、コードポイントの値に応じて必要なバイト数が変わります。
- 1バイト:
U+0000
-U+007F
(ASCII) - 2バイト:
U+0080
-U+07FF
- 3バイト:
U+0800
-U+FFFF
(ほとんどの日本語の文字はここに含まれる) - 4バイト:
U+10000
-U+10FFFF
(補助面の文字)
そのため、文字によってUTF-8のバイト列の長さが異なるという特徴があります。
なお、UTF-8の1バイトで表される文字はASCIIと互換性があり、インターネット上ではデファクトスタンダードとして広く採用されています。
BOMとは
#今回の問題点を解説する前に「BOM」にも触れておきます。
BOM(Byte Order Mark)は、テキストの先頭に付けられるバイト順(エンディアン)を示す目印です。
UTF-16やUTF-32といったエンコーディング方式では、どの順序でバイトを並べるかを指定する必要があります。このバイトの順序のことを「エンディアン」と呼びます。
たとえば、文字「A」(U+0041)はUTF-16では以下のように表されます。
- ビッグエンディアン(BE):
0x00 0x41
(上位バイトを先に書く) - リトルエンディアン(LE):
0x41 0x00
(下位バイトを先に書く)
UTF-8におけるBOM
#UTF-8はエンディアンに依存しないエンコーディング方式であるため、通常はBOMを付与する必要はありません。ただし、ファイルがUTF-8であることを明示する目的で、あえてBOMを付けることもあります。
身近な例としては、ExcelでCSVファイルをUTF-8形式で出力する際、BOMが自動的に付与されます。これが原因で、さまざまな場面で問題の原因となることがあります。
例えば、JSPファイルやThymeleafのテンプレートはHTMLレスポンスを生成します。しかし、UTF-8のBOM付きで記述されていると、HTMLの先頭にあるべき <!DOCTYPE html>
の前に 0xEF 0xBB 0xBF
が出力されてしまいます。ブラウザによっては、これによりHTMLとして正しく解釈されないことがあります。
また、CSVファイルの読み込み時も注意が必要です。
BOMの存在を想定していないコードでは、先頭の数バイトがゴミとして扱われたり、ヘッダ名を正しく読み取れずエラーになる場合があります。
BOMが厄介な理由
#この問題の厄介な点は、BOM(Byte Order Mark)がエディタ上では目に見えないことです。
そのため、JSPファイルやCSVファイルでエラーが発生しても、ファイルの先頭にBOMが含まれていることに気づきにくく、原因の特定に遅れるケースが少なくありません。
この記事を読んでくださっている皆さんは、UTF-8のファイルで予期しない問題が発生した際には、「先頭にBOMが含まれていないか」という観点からも調査してみてください。
たとえば、バイナリエディタやcertutil -dump sample.csv
コマンドなどで、ファイルの先頭に0xEF 0xBB 0xBF
が含まれていないかを確認できます。
この問題への対処
#さて、BOMが入っていることが問題の原因と判明した場合、取るべき対処は以下の2つに絞られるかと思います。
- BOMを削除する
- BOMを読み飛ばす
BOMを削除する
#これはJSPやThymeleafなどのテンプレートでの対処です。
期待されないBOMが先頭にあることで、ブラウザによるHTMLの解析を妨げているので、BOMを削除してください。
また、HTMLのエンコーディングを指定する場合は、ヘッダータグで<meta charset="utf-8">
を指定することを検討してください。
BOMを読み飛ばす
#意図しないBOMは読み飛ばすこともできます。こちらはファイルの読み込みを実装している側での対処です。
例えば、ユーザーが作成したUTF-8のCSVを取り込むようなシステムにおいては、ユーザーがExcelなどでBOM付きのCSVファイルを生成することを制限できません。
このように外部入力に柔軟に対応したい場合は、先頭にBOMがはいっている「かもしれない」ことを前提にBOMがあれば読み飛ばすように実装することで問題を解決できます。
まとめ
#- BOMは目に見えない「地雷」になりやすいため、問題発生時には真っ先にチェックすべきポイントの1つです。
- アプリケーション側で用意しているリソースであれば削除するべきです。
- 外部入力情報でありアプリケーション側でコントロールが難しいものであれば、BOMが入ってくることを前提として、BOMを読み飛ばすなどの対処を検討するべきです。
いずれにしても、文字コードやエンコードを統一すればすべての問題を回避できるわけではありません。その前提を踏まえたうえで、採用する文字コードやエンコード方式についての理解を深めてください。
文字コードに関するトラブルも、仕組みを正しく理解して対処できれば、逆にそれが自分の技術的な武器になります。