LoginSignup
1
1

More than 3 years have passed since last update.

utf8mb3に入るようにJavaで制限をかけたい

Posted at

やりたいことは簡単なのに。。。

MySqlのvarcharが扱える文字コード(character set)は、デフォルトでutf8であり、これはutf8mb3(3バイト文字)のエイリアスでもあるようです(バージョンがあがるごとにだんだんutf8mb4に移行しつつあるようですが)。
で、業務で使うmysql5.7には、utf8で接続することになっているため、4バイト文字が入りません。
というわけで、サーバー側(java)で文字を切ってあげればよいことになりました。

調べてみたものの、なんかスマートな方法がぱっと出てこなかったので、なんとなく調べたことをまとめておきます。

サロゲートペアと4バイト文字の違い

まずこれがわからなかった。
上司に言われた指示は「絵文字を使えないようにしたい」だった。
自分の会社の先達が作ったコードでは、バリデーションでサロゲートペアをひっかけており、これを参考にしろと提示された。

で、サロゲートペアって?

わからんので調べてみる

いったんUnicodeとutf8、サロゲートペアの話をある程度理解してから進みます。

  • ある文字があったとき、Unicodeとutf8では、それぞれ違う符号であらわす
    • 通信上はUnicodeで行い、実際プログラムが利用するときにutf8の符号にする
  • utf8は1~3バイトで表す
    • データ通信等ではUnicodeを使用し、実際のプログラムでは符号化(1~3バイト)して使う
      • 文字「あ」は、Unicodeでは「3042」、utf8では「E3 81 82」
  • Unicodeは当初の設計上、2バイト一文字で表現しようとしていたが、表現する文字が増えてきたため、2バイト*2で 一文字で表現する方法が追加された
    • これがサロゲートペアで、前半がU+D800~U+DBFF、後半がU+DC00~U+DFFFの範囲と定義されている
  • UTF-16ではサロゲートペアで表されるような、基本多言語面外の符号位置をUTF-8で表す時は(中略)U+10000~U+10FFFFの符号位置にデコードしてから変換する(Wiki参照)
    • BPMの表現 = UTF8では3バイト文字で表現可能
    • 1~3バイト内で表現しきれなかったutf8の文字を表現するために4バイト使って表現する
      • utf8mb3ではこの4バイト文字が入らない

こちらの話から、utf8においては、Unicodeのサロゲートペアはutf8で4バイトで符号化されるのと同義であることがわかりました(でいいのかな?たぶん)。
理解力が乏しいせいか、なかなか把握できず困りました。。。。

でこれをいざJavaでかいてみるとするとどうなるか。
いったん、上記の話を確かめるためJshellで確認してみます。

import java.util.stream.IntStream;

String word = "𠮷";
System.out.println("this word has length of " + word.length());

if (word.length() != word.codePointCount(0, word.length())) {
    System.out.println("code point count " + word.codePointCount(0, word.length()));
}

IntStream.range(0, word.length()).forEach(i -> {
    System.out.println("code point is " + Integer.toHexString(word.codePointAt(i)));
    var target = word.charAt(i);
    if (Character.isSurrogate(target)){
        System.out.println("this is surrogate pair");
    }
    System.out.println("check "+ target);
});

-------------------
this word has length of 2

code point count 1

code point is 20bb7
this is surrogate pair
check ?
code point is dfb7
this is surrogate pair
check ?

ちなみに𠮷は「つちよし」で変換できます。
ためしにMysql5.7のvarcharにいれるとエラーになりました。
Incorrect string value: '\xF0\xA0\xAE\xB7' for column 'name' at row 1
名前にいれられないとか。。。。
まあ、今回の要件はそこではないのでよいのです。

javaのcharには2バイト文字が格納されるので、string.length()は文字数でなく、左記ユニット数がカウントされます。
コードポイントが実際のUnicodeで表現する文字1つにあたるため、サロゲートペアを確認する時はcodePointCountと比較することも可能です。

結論?

ということで、結局サロゲートペア = UTF8の4バイト文字の認識でよい、のかな?
どちらにしろ、Character.isSurrogate()が最も便利そうなので、これでサロゲートペアを確認し、4バイト文字を制限することとします。

参考

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1