@maxbytelengthで勉強になったこと

@maxbytelength

SAStrutsの検証用のアノテーションのひとつに@maxbytelengthという、バイト単位で最大文字列の検証をしてくれるアノテーションがあります。


使い方は、maxbytelength属性にバイト数を指定して検証します。

@maxbytelength(maxbytelength = 1000)
public string contents;

はまったこと

maxbytelength属性に指定したバイト数*1で検証がうまくできない。

原因・対策

結論から言うと、charset属性にsjisを指定すれば期待通りに検証してくれる。


maxbytelengthでは"".getByte()でバイトの長さを取得して検証をしています。charsetを指定すると"".getByte(charset)という形でバイトに変換します。


デフォルトで"".getByte()は半角1Byte、全角2Byteの長さ*2になると思ってました。


String#getBytes()ではまる。 - うなの日記のコメントに書いてあったのですが、システムプロパティの"file.encoding"からデフォルトの文字セットを取得するみたいです。


今回はファイルのエンコーディングUTF-8にしていたので、"".getByte("UTF-8")という形でバイトに変換されていたため期待通りに検証されなかったみたいです。


試しに"".getByte()を"sjis"、"UTF-8"、"Unicode"で実行してみました。

"あああああ".getBytes("UTF-8");
"あああああ".getBytes("Unicode");
"あああああ".getBytes("sjis");


結果
UTF-8では漢字や仮名の表現に3Byte必要なため15Byte
Unicodeは2Byteで表現するが、先頭にBOMが付くため12Byte
sjisは2Byteのため10Byte

[ 0xE3 0x81 0x82 0xE3 0x81 0x82 0xE3 0x81 0x82 0xE3 0x81 0x82 0xE3 0x81 0x82 ]
15
[ 0xFE 0xFF 0x30 0x42 0x30 0x42 0x30 0x42 0x30 0x42 0x30 0x42 ]
12
[ 0x82 0xA0 0x82 0xA0 0x82 0xA0 0x82 0xA0 0x82 0xA0 ]
10

調べていて知ったこと

Unicodeは、16ビットで文字を表現します。16ビットということは、216 = 65536種類の文字を表現できます。実際に何文字が定義されているのかは、Unicodeのバージョンによって異なりますが、Javaで採用されている Unicode 2.1では、約4万字程度の文字が割り当てられています※1。

文字コードに関する議論には、いろいろと奥深いものがありますが、Javaの初心者プログラマーとしては、とりあえず以下のポイントくらいを理解しておけばよいと思います。

Unicode文字は1文字が16ビット
●最初の128文字はASCIIコードと一致
●コンピュータで通常使っている日本語文字が含まれている

ASCIIコードでは、1文字を7ビットで表現し、アルファベットや数字、記号、制御文字などを定義していますが、それらは、Unicodeの最初の 128文字と一致しています。どの文字が含まれているのかだけでなく、その順番も同じなので、7ビットのASCIIコードに、上位ビットとして0を9個追加して16ビットにすれば、Unicodeでの表現と同じになります。

例えば、「A」という文字は、ASCIIコードでは7ビットで「100 0001」と表しますが、この上位に0を9個追加して、「0000 0000 0100 0001」とすれば、Unicodeの「A」になります。

itarchitect.jp


最初の128文字は、ASCIIコードと同じということで、たまにASCIIコードと文字を比較したりするサンプルがあるのはそのためなんですね。

*1:このときの想定では半角1Byte、全角2Byte

*2:つまりsjisでバイトに変換