ようこそゲストさん

CPA-LABテクニカル

2008/05/15(木) 日本語文字列のバイト数取得にstrlenだけではダメな理由-PHP

日本語(マルチバイト)の処理において、その文字数ではなく、バイト数を取得したい場合があります。
マルチバイトは必ずしも1文字2バイトとは限らず、EUC-JPでは概ね2バイトだけど特殊文字で3バイトの場合があり、UTF-8では概ね3文字バイトで表されるけれども、そうでない場合もある。
マルチバイト文字はエスケープシーケンス等様々な理由で、日本語の文字数×nでは答えがでないのです。
もっと単純な話でいえば、シングルバイト文字とマルチバイト文字が混ざった文字列のバイト数は、もちろん文字数×nでは求めることができません。

まず現状での最善策をご紹介

$volm = strlen(bin2hex($data)) / 2;

$data:バイト数を取得したいデータ
$volm:データ長(byte)
これは私のオリジナルではありません。Script雑感-php:バイト数の取得(strlen は mb_strlen にオーバーロードされる)様の引用です。

Qdmailの修正中に、どんな環境でも問題なく、日本語(マルチバイト文字)のバイト数を取得する必要がでてきてきたのですが、どうしてもスマートな方法を考えつきませんでした。
そこに、Script雑感さんのページに遭遇しました。たいへん助かりました。

参考

 $str_len_byte = mb_ereg('.*', $string, $array);
という方法もあるようです。[pgsql-jp: 34062 データサイズの取得方法 ]しかし正規表現は速度的に不利なのとスマートでないのとmb_*環境がない場合には使えないので、bin2hex関数をお薦めします。

なぜ、strlen単独ではダメなのか?

オーバーロード


インターネットで検索するといくつか候補がみつかるのですが、その多くは次のようなものです。
(環境によって誤動作する例)

$volm = strlen($data);

$data:バイト数を取得したいデータ
$volm:データ長(byte)
しかし、これでは誤動作することがあるのです。

PHPでは、php.iniの設定によっては、勝手に strlen が mb_strlen としてオーバーロードされます。
つまり、strlenを使っているつもりで、いつのまにか、mb_strlenを使っていることがあるのです。
関数のオーバーロード機能-PHPマニュアル
したがって、次のような事態になることもあります。
バイト数を数えたかったのに、マルチバイトの文字数を数えてしまった。
これでは意図するプログラミングができません。

php.iniの設定を変えればいい話なのか?

関数のオーバーロード機能-PHPマニュアルにあるように、php.iniを変更すれば、これらの意図しない動作を押さえることができます。
しかし、このphp.iniのmbstring.func_overloadの値を変えると、他のスクリプトへの影響がでる可能性があり、別の誤動作を生む可能性があります。
また、set_ini では、このディレクティブを変更することはできない仕様になっています*1ので、スクリプト内で一時的にmbstring.func_overloadをゼロにする対応も難しいです。。
このディレクティブを変更できるかどうかもphp.iniで変更できないことはないのですが、話がややこしくなり、お薦めできません。

したがって、php.iniでの対応は、あまり望ましくないといえます。

また、「自分の環境では、mbstring.func_overloadはゼロだから、別にいいや」という考え方もできますが、そのスクリプトを別のサーバーに移動させたときなどに、異常がでる可能性があります。

bin2hex関数の働き

bin2hexは、シングルバイト文字を16進数表記に変換する関数です。16進数表記の場合、シングルバイト文字1文字は必ず2文字の16進数に変換されます*2
したがって、その16進数表記の半分が、当該バイト数というわけですね。

*1 : バージョン4.3以降

*2 : 9などの一桁数値もゼロつきの09で返してくれる

1: へろへろ 2009年09月09日(水) 午後5時50分

どのサイトを見てもstrlenは全角文字は2バイトですと嘘が書いてあり困っていました。ありがとうございました。

2: 浜村拓夫 2009年09月29日(火) 午前10時32分

参考になりました!
どうもありがとうございます。(・∀・)

3: ちびん 2009年12月13日(日) 午前11時42分

「あ」の長さ3バイトなんですよ。おかしいですよね。。
解決しました。本当ありがとうございましす!!

4: abc 2011年12月08日(木) 午後0時05分

意図しないところで勝手にオーバーロードされてるなんて怖いですね。

5: ぺろぺろ 2011年12月22日(木) 午後3時14分

unicodeが出る前までは日本語文字は2byteだったんだよ!

6: ぽんた 2012年10月26日(金) 午後4時57分

bin2hexはあくまで2進数表記を変換する関数であって、上記のような動作をするという表記は公式サイトに一言もありません。結果オーライで動作するというレベルでは、ほかの方法と変わりないような気がします...。


名前:  非公開コメント   

  • TB-URL  http://www.cpa-lab.com/tech/0144/tb/
  • CakePHP(RC1.2)のemailコンポーネントで日本語はまだ厳しい CPA-LABテクニカル spok
    ■CakePHP(1.2RC2)のemailコンポーネント評価CakePHP 1.2 RC2 がリリースされたので、新しいemailコンポーネントを評価してみた。全般的に言えば、改行コード&英文周りはかなり改善されており、英語メールを送る分には、大丈夫かと...
  • PHPのstrlen関数で全角文字が3バイトになる件 浜村拓夫の世界
    PHPのstrlen関数で、文字列のバイト数を取得しようとしたら、全角文字1文字が2バイトではなく3バイトとしてカウントされてしまった。全角文字の...