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!
以下、ソースコード
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;
}
}