lenb,midb? 2017/01/21
2021/03/14

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;
 }
}


: