κeenのHappy Hacκing Blog | Lispエイリアンの狂想曲

誰がUTF-32が使われてないなんて言ったんだ

最近ではUTF-8でソースコードを書いてUTF-8で出入力をする。それ以外のエンコーディングは使われていない。…だと?誰がそんなこと言ったんだ

ASCIIと古いUnicodeと新しいUnicode

少し長くなるが文字コードの話から始まる。ASCIIはお馴染み最低7bitあればASCIIの定義する文字集合を表せる。

古いUnicodeは16bitで全ての文字を表わすことを目標に作られた。

新しいUnicodeは文字(主に漢字)が多過ぎて16bitでは表せなかったので21bitに拡張された。

文字コードとエンコーディング

文字コードをどういう形式で表すかがエンコーディングだ。文字コードが7bit、16bit、21bitだからといってそのままのサイズで表わす訳ではない。ASCIIは普通8bitの型で表わすし21bitの型を用意するよりは32bitの型に格納した方が扱い易そうだ。あるいは8bit型の配列に16bitや21bitを収めるとプログラムコードの変更が少なそうだ。

UTF-8とUTF-16とUTF-32

さて、ここまで来ればお分かりだろう。UTF-8はASCIIとの互換性を保つ8bitベースのエンコーディング、UTF-16は古いUnicodeの時に作られて新しいUnicodeにも対応した16bitベースのエンコーディング、UTF-32は新しいUnicodeのために作られた32bitベースのエンコーディングだ。 ここで注意して欲しいのはUTF-8とUTF-16は8bitや16bitの単位を複数使うことで新しいUnicodeの範囲も扱える点、逆に言えば1単位が1文字に対応しない点だ。日本語を含んだ文字列に対してlengthを使うと正しい結果が帰って来ないなどの経験は誰しもあるだろう

UTF-8とUTF-16とUTF-32それぞれの特徴

UTF-8

8bit(オクテット)の配列に文字列を収めることになる。16bitや21bitの範囲の文字を表す時は8bitに対してASCIIは7bitしか使わないのでその1bitを使って次に続くことを表わす。最大4オクテットまで使うことがある。 UTF-8の特徴はなんと言ってもASCIIとの互換性があること。UTF-8でASCIIの範囲のみの文字をエンコードするとASCIIと同一になるし、ASCIIでエンコードされたものはUTF-8で読んでも同じ内容になる。なのでどんなエンコードか分からないものはとりあえずUTF-8で読んでおくと間違いが少ない。よって冒頭で書いたように出入力、保存ファイル、GUIに表示する文字列などに使われる。

UTF-8にも欠点はあり、ASCII外の文字を大量に扱うと空間効率が悪い。さらに先頭から順番に辿らないとどのオクテットが何文字目かも分からないので(配列長が既知であっても)文字列へのlengthやランダムアクセスなどの多くの操作がO(n)になってしまう点だ。文字の置換に至ってはin placeでは出来ないことすらある。

UTF-16

16bitの配列に文字列を収める。21bitまで扱うために特定の範囲の値が来たら次の16bitも読んで2つ併わせて1つの文字とみなす。このペアをサロゲートペアという。C言語にはwcharなるマルチバイト文字用の型があるが、仕様では最低で16bitという要求になっているのでwcharを使う時はUTF-16でエンコードする(と思う)。UTF-16もUTF-8と同じく文字列操作がO(n)になる。Unicode公式のエンコーディングであるという点を除いて特に良い点が見当たらないので後方互換性の必要な場面でないと使われてなさそうな気がする。例えばWindowsのファイルシステムはファイル名をUTF-16で保持しているらしい。

尚、16bitのエンディアンは指定されてないのでファイルの先頭にバイトオーダーマーク(BOM)と呼ばれる空白文字を置いてそれでエンディアンを指定/判別する慣習になっている。

UTF-32

32bitの配列に文字列を収める。必ず32bitで1文字を表すので文字列操作が効率的に行なえる。欠点はASCIIの範囲の文字が多いとメモリを無駄にし易い点。どんな文字列を扱うか分からない(ASCIIの範囲が多いとは言い切れない)汎用ライブラリや言語処理系そのものでよく使われる。

言語処理系の内部表現と外部エンコーディング

ここで罠になるのがソースコードをUTF-8で書いて出入力をUTF-8で行なう処理系でも内部では先述の理由でUTF-8以外を使うことがあることだ。メジャーな言語について少し調べてみた。

  • SBCL(Common Lispのメジャーな処理系)はUTF-32を使う
  • SpiderMonkey(FirefoxのJavaScript処理系)はUTF-8を使う。HTMLはマルチバイト文字を使う言語で書かれていても大半がASCIIになるからとのこと。また、文字列をropeで表現しており、UTF-8のデメリットを軽減出来る。
  • Rubyは多言語対応の時に内部表現はなんでもアリ(渡された文字列そのまま)になった
  • PerlはUTF-8を使う
  • PythonはconfigureのオプションでUCS2(ほぼUTF-16に同じ)やUCS4(UTF-32形式のUnicodeと概ね互換)が選べる。FedoraやUbuntuはUCS4でビルドしている
  • PHPは言語で動的に変更出来る

ソースコードをUTF-8で書いて出入力をUTF-8で行なうから変換コストなんてないと思ったら大間違いだ。裏ではUTF-32が使われていて、出入力の度に変換が走る。誰がUTF-32が使われてないなんて言ったんだ。

Written by κeen