基礎知識
- コンピュータで扱えるデータの最小単位は1バイト(8ビット:2進数8桁分)であり、これは符号無し10進数表記で**
0〜255
**(2進数表記で00000000〜11111111
)を表すことが出来ます。 - 半角英数字や半角記号、改行コードなどの世界共通で多用される文字は1バイト文字として**
0〜127
**の範囲に割り当てられており、これらは「ASCII文字」と呼ばれます。 - ASCII以外の文字コードでは余った
128〜255
の範囲を他の文字を表現するために利用しています。但しこれらを1バイト単位で使うだけでは残り128種類の文字しか表せなくなってしまうので、実際には複数桁組み合わせて用いられます。これらは「マルチバイト文字」と呼ばれます。
以下は参考リンクです。初学者の方は、この記事を読む前にひと通り目を通されることをおすすめします。
文字コードに関するおさらい
UTF-8
- マルチバイト文字は
2〜4
バイトの可変長で表されます。 - 接頭符号です。 バイト列をどこで切っても、そのバイトを他の文字の先頭バイトと間違えることはありません。先頭バイトと後続バイトの領域がはっきり区別されているからです。
文字種 | 表現 |
---|---|
ASCII文字 | [00-7F] |
ラテン文字など | [C0-DF][80-BF] |
ひらがな・カタカナ・漢字など | [E0-EF][80-BF][80-BF] |
一部の漢字・絵文字など | [F0-F7][80-BF][80-BF][80-BF] |
注意: 厳密な定義ではありません
→ UTF-8 の後続バイトの範囲チェックは 0x80 から 0xBF までだけでは不十分
EUC-JP
- マルチバイト文字は
2
バイトの固定長で表されます。 - 接頭符号ではありません。マルチバイト文字の1バイト目と2バイト目の範囲が重複します。
- **マルチバイト文字はASCII文字とは重複しません。**生成過程で両コードポイントに
A0
を足しているためです。ASCII文字は7F
で終わっているため、確実にこれより大きな値になります。
文字種 | 表現 |
---|---|
ASCII文字 | [00-7F] |
ひらがな・カタカナ・漢字など | [A0-FE][A0-FE] |
半角カタカナ | [8E][A0-DF] |
Shift_JIS
- マルチバイト文字は
2
バイトの固定長で表されます。 - 半角カタカナは1バイトです。
- 接頭符号ではありません。マルチバイト文字の1バイト目と2バイト目の範囲が重複します。
- **マルチバイト文字の2バイト目がASCII文字および半角カタカナと重複します。**幸い1バイト目は重複しないので、後述するJISよりは扱うのが容易です。
- Microsoftによる独自拡張としてWindows-31J(別名:CP932,SJIS-win)があります。扱える文字が少し増えているようです。
文字種 | 表現 |
---|---|
ASCII文字 | [00-7F] |
半角カタカナ | [A1-DF] |
マルチバイト文字前半 | [81-9F][40-FC] |
マルチバイト文字後半 | [E0-EF][40-FC] |
ISO-2022-JP (JIS)
- マルチバイト文字は
2
バイトの固定長で表されます。 - 半角カタカナは1バイトです。
- 接頭符号ではありません。マルチバイト文字の1バイト目と2バイト目の範囲が重複します。
- **マルチバイト文字がASCII文字および半角カタカナと重複します。**実際に運用する際にはエスケープシーケンスを用いて「ここからはASCII文字」「ここからは半角カタカナ」「ここからはマルチバイト文字」として表現する必要があります。生成過程で両コードポイントに
20
を足しているため、ASCII文字のうちエスケープシーケンスの先頭バイトとは重複しません。 - ISO-2022-JPという規格自体には半角カタカナは存在せず、拡張された文字コードでそれぞれ独自に採用されているようです。そのうちの一つとしてMicrosoftのISO-2022-JP-MS(7bit)があります。
文字種 | 表現 |
---|---|
ASCII文字 | [00-7F] |
半角カタカナ(7bit) | [21-5F] |
半角カタカナ(8bit) | [A1-DF] |
マルチバイト文字 | [21-7E][21-7E] |
この文字コードはPHPのソースコードとして有効ではないので、以降は割愛します。(旧来のメールぐらいでしか使われないんじゃないかな…?)
UTF-16 / UTF-16BE / UTF-16LE
- ASCII文字を含め、ほとんど全ての文字が
2
バイト固定長で表されます。 - 2バイトに収まりきらない一部の文字は「サロゲートペア」と呼ばれ、4バイトで表されます。
- 接頭符号ではありません。1バイト目と2バイト目の範囲が重複します。
- ビッグエンディアンとリトルエンディアンの2通りが存在します。これをBOMを使って表現します。UTF-8と異なり、UTF-16にはBOMが必須です。但し、UTF-16BE/UTF-16LEとして明示する場合には逆にBOMを付加してはなりません。
この文字コードはPHPのソースコードとして有効ではないので、以降は割愛します。
PHPで扱う上での注目ポイント
UTF-8 / EUC-JP / Shift_JIS についてのみ取り扱います。
正規表現の文字クラス
これは全ての文字コードにおいて留意する必要があります。
var_dump(preg_match('/[あい]/', 'う')); // int(1)
「あ」という文字は E3
81
82
で表され、「う」は E3
81
84
で表されます。PHPは基本的に全ての操作をバイト単位で行うので、このコードはバイトコード E3
または 81
または 82
を探してしまうことになり、マッチするわけです。解決策としてはバイト単位ではなく文字単位であることを明示する必要があります。
var_dump(preg_match('/[あい]/u', 'う')); // int(0)
mb_regex_encoding('EUC-JP');
var_dump(mb_ereg('[あい]', 'う')); // bool(false)
ダメ文字
これはShift_JISにおいて留意する必要があります。
- PHPソースコードのパースエラーが起こる
- 正規表現のパースエラーが起こる
これらは全てマルチバイト文字の2バイト目がASCII文字と重複していることにより発生するものです。最も有名なのは\
で、特別にこれは5C問題と呼ばれることもあります。例えば「表」は 95
5C
と表されますが、\
の5C
と重複しています。
$str = '表\';
php.iniの設定を変えることによっても対応出来そうです。
別の例として、「曽」は 91
5D
で、]
の5D
と重複しています。これを正規表現の文字クラス内で用いる場合は、最初の例同様にマルチバイト対応の関数を使うことで解決出来ます。しかし、PHPソースコードレベルで影響してくる\
については明示的な対応が必要となってくるのは言うまでもありません。
接頭符号ではない仲間にEUC-JPがいますが、こちらはASCII文字に関しては無問題なので心配する必要はありません。
str_replace
explode
などにおけるマルチバイト文字列に対するマルチバイト文字列による操作
これはEUC-JPおよびShift_JISにおいて留意する必要があります。両者とも接頭符号ではなく、マルチバイト文字列に対してマルチバイト文字列による検索/置換を行う場合で問題が発生します。
(EUC-JP)
官: B4
B1
庁: C4
A3
営: B1
C4
var_dump(str_replace("営", "休", "官庁の営業")); // 患截の休業
strpos
に対してはmb_strpos
という関数が存在しますが、mb_str_replace
やmb_explode
は存在しません。正規表現用の関数はマルチバイト処理に対応しているので、こちらで代用しましょう。mb_str_replace
を自作している人を見たことがありますが、手元の環境では組み込みのmb_ereg_replace
を使ったほうがパフォーマンスは明らかに上でした。
通常の関数 | マルチバイト対応の正規表現用の関数 |
---|---|
str_replace |
mb_ereg_replace |
explode |
mb_split |
なお、EUC-JPの場合はマルチバイト文字列に対してASCII文字列による検索/置換を行う場合は問題ありません。ASCII文字列に対してマルチバイト文字列による、という場合も同様です。この点はShift_JISと差異があります。
var_dump(str_replace($from, "abc", $str));
もっとラクしようよ!
ここまで見てきて、(EUC-JPはまだ許せるとして)UTF-8以外でPHPコードは書きたくない、というのが一般論だと思います。しかし、例えばもしめちゃくちゃ古いガラケー向けにShift_JISで出力しなければならない、というケースがあったとしても…
PHPコードはUTF-8で書き、出力時に自動変換させることが出来ます。
<?php
ob_start(function ($str) {
return mb_convert_encoding($str, 'SJIS-win', 'UTF-8');
});
またShift_JISのCSVファイルを扱うケースもあると思いますが、こういう場合でもパース時および出力時に自動変換をかければ扱いやすくなるはずです。ストリームフィルタを使う方法が最もおすすめです。