lenb,midb?
2017/01/21
2021/03/14
■
bytebuffer
CharsetEncoder
java
sjis
javaです。
たとえばですが、最新じゃないoracle dbを使用していて、項目を、文字数でなくバイト数で定義してたりするとします。
文字コードは、Shift_JISとします。
そういう場合、アプリケーション側から、dbに登録する場合に、文字をバイト数で切って登録しないと、
桁数オーバーとなってしまいます。
なので、バイト数で切り出して登録するとします。
Shift_JISを以下、sjisといいます。
sjisは、1バイト文字、2バイト文字が混合です。
クリアしなければいけない課題があります。
2バイト固定であったりすればバイト数で切る場合は、2の倍数で切ればよいのですが、
素直にバイト数で切ると文字列の一番最後の文字の2バイト文字を半分で切ってしまうことになる場合があります。
実装方式その1
1文字づつ切り出して、sjis変換して、バイト数チェックしつながら、バイト数を加算していきます。
sjis変換は、javaのStringクラスのgetBytesを使います。
この実装方式はまちがいないです。メリットはコードがわかりやすいことです。デメリットは、文字数が多い場合、切り出しのバイト数が多い場合、1文字1文字の処理なのでそれなりのコストがかかること。
実装方式その2
1の方法ですと、文字列が多いと効率が悪くなので、少しだけ効率をよくしてみます。
ただし、実装が面倒となります。
はじめにバイト数を切り取り、最後の1バイトが2文字の片割れかどうか判定し、片割れだとそれ除く処理を行います。
コード表はこちらのサイトを参考にさせてもらっています。
Shift_JIS 文字コード表
キャプチャ(http://seiai.ed.jp/sys/text/java/shiftjis_table.html)
2バイトの上位
sjisの1バイト目と2バイト目なのかは、単純に区別できない場合があります。
例)キャプチャ(http://seiai.ed.jp/sys/text/java/shiftjis_table.html)
ですので、一度、切り出したバイト列を変換して、最後の文字が化けていないかチェックします。
化けていたら、その文字を除いて返却するという処理を行います。
実装方式その3
高速版です。なんとなく、できる方法あるなーとは気がついてはいたのですが、自力ではないですが、がんばってコードをシンプルにしました。
実は、発想は、実装方式その1と変わらないですが、javaのもっている文字コード処理を直接呼び出すイメージでしょうか。
使っているので重要なのは、CharsetEncoderです。Stringのbyte[] getBytes(String charsetName)で使われているのもこれですね。
参考
文字列を指定の文字エンコーディングでのバイト数で切る - kameidの備忘録 - Sharpen the Saw!
以下、ソースコード
たとえばですが、最新じゃないoracle dbを使用していて、項目を、文字数でなくバイト数で定義してたりするとします。
文字コードは、Shift_JISとします。
そういう場合、アプリケーション側から、dbに登録する場合に、文字をバイト数で切って登録しないと、
桁数オーバーとなってしまいます。
なので、バイト数で切り出して登録するとします。
Shift_JISを以下、sjisといいます。
sjisは、1バイト文字、2バイト文字が混合です。
クリアしなければいけない課題があります。
2バイト固定であったりすればバイト数で切る場合は、2の倍数で切ればよいのですが、
素直にバイト数で切ると文字列の一番最後の文字の2バイト文字を半分で切ってしまうことになる場合があります。
実装方式その1
1文字づつ切り出して、sjis変換して、バイト数チェックしつながら、バイト数を加算していきます。
sjis変換は、javaのStringクラスのgetBytesを使います。
この実装方式はまちがいないです。メリットはコードがわかりやすいことです。デメリットは、文字数が多い場合、切り出しのバイト数が多い場合、1文字1文字の処理なのでそれなりのコストがかかること。
実装方式その2
1の方法ですと、文字列が多いと効率が悪くなので、少しだけ効率をよくしてみます。
ただし、実装が面倒となります。
はじめにバイト数を切り取り、最後の1バイトが2文字の片割れかどうか判定し、片割れだとそれ除く処理を行います。
コード表はこちらのサイトを参考にさせてもらっています。
Shift_JIS 文字コード表
キャプチャ(http://seiai.ed.jp/sys/text/java/shiftjis_table.html)
2バイトの上位
sjisの1バイト目と2バイト目なのかは、単純に区別できない場合があります。
例)キャプチャ(http://seiai.ed.jp/sys/text/java/shiftjis_table.html)
ですので、一度、切り出したバイト列を変換して、最後の文字が化けていないかチェックします。
化けていたら、その文字を除いて返却するという処理を行います。
実装方式その3
高速版です。なんとなく、できる方法あるなーとは気がついてはいたのですが、自力ではないですが、がんばってコードをシンプルにしました。
実は、発想は、実装方式その1と変わらないですが、javaのもっている文字コード処理を直接呼び出すイメージでしょうか。
使っているので重要なのは、CharsetEncoderです。Stringのbyte[] getBytes(String charsetName)で使われているのもこれですね。
参考
文字列を指定の文字エンコーディングでのバイト数で切る - kameidの備忘録 - Sharpen the Saw!
以下、ソースコード
package a; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; public class TestMidb { public static void main(String[] args) throws UnsupportedEncodingException { a(); } static void a() throws UnsupportedEncodingException { final String[] テスト文字列 = { "", "a", "aa", "aaaa", "aムム", "ムムa", "ムaa", "ムムムム", "ムムム", "ムム", "ム" }; final int len = 3; for (String s : テスト文字列) { final String s01 = new String(midb01(s, len), "sjis"); final String s02 = new String(midb02(s, len), "sjis"); final String s03 = new String(midb03(s, len), "sjis"); final boolean judge = s01.equals(s02) && s02.equals(s03); System.out.printf("%s [%s] [%s] [%s] %s %n", s, s01, s02, s03, judge); } } /** * 実装方式1 * * @param s * @param byteLen * @return */ public static byte[] midb01(String s, int byteLen) { final String characterSet = "sjis"; final ByteArrayOutputStream baos = new ByteArrayOutputStream(); int size = 0; try { for (int i = 0; (i < s.length() && size < byteLen); i++) { final byte[] bs = s.substring(i, i + 1).getBytes(characterSet); final int blen = bs.length; if ((size + blen) > byteLen) break; baos.write(bs); size += bs.length; } } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } return baos.toByteArray(); } /** * 実装方式2 * * @param s * @param byteLen * @return */ public static byte[] midb02(String s, int byteLen) { final String charset = "sjis"; try { final byte[] srcbs = s.getBytes(charset); if (srcbs.length <= byteLen) { return srcbs; } final byte[] bs = new byte[byteLen]; System.arraycopy(srcbs, 0, bs, 0, byteLen); final int b = bs[byteLen - 1] & 0xff; // sjis 1バイト目の範囲か検査する if ((0x80 <= b && b < 0xA0) || (0xE0 <= b && b <= 0xFF)) { // 検査のため全体を文字列にする final String s2 = new String(bs, charset); // 最後の文字を取得 final char ch = s2.charAt(s2.length() - 1); // 変換できていない文字か検査する if (Character.OTHER_SYMBOL == Character.getType(ch)) { // 1バイト少ないバイト配列を作成 byte[] bs2 = new byte[byteLen - 1]; System.arraycopy(bs, 0, bs2, 0, byteLen - 1); return bs2; } } return bs; } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } /** * 実装方式3 * @param s * @param byteLen * @return */ public static byte[] midb03(String s, int byteLen) { final String charsetName = "sjis"; final Charset charset = Charset.forName(charsetName); final byte[] bs = new byte[byteLen]; final ByteBuffer outByteBuffer = ByteBuffer.wrap(bs); final char[] array = s.toCharArray(); final CharBuffer inCharBuffer = CharBuffer.wrap(array); final CharsetEncoder charsetEncoder = charset.newEncoder(); charsetEncoder.encode(inCharBuffer, outByteBuffer, true /* endOfInput */); // 余ったぶんは切り捨てる outByteBuffer.flip(); // 切り捨てたあとのサイズで返却する final byte[] bs2 = new byte[outByteBuffer.limit()]; outByteBuffer.get(bs2); return bs2; } }
: