web-dev-qa-db-ja.com

BOMを使用してUTF-16LEバイト配列をエンコード/デコードするにはどうすればよいですか?

Java.lang.Stringとの間でUTF-16バイト配列をエンコード/デコードする必要があります。バイト配列は バイトオーダーマーカー(BOM) で与えられ、BOMでエンコードされたバイト配列が必要です。

また、私はMicrosoftクライアント/サーバーを扱っているので、誤解を避けるために、エンコードをリトルエンディアンで(LE BOMとともに)出力したいと思います。 BOMを使用すると、ビッグエンディアンで機能するはずですが、Windowsの世界で上流に泳ぎたくありません。

例として、BOMを使用してリトルエンディアンでJava.lang.StringUTF-16としてエンコードするメソッドを次に示します。

public static byte[] encodeString(String message) {

    byte[] tmp = null;
    try {
        tmp = message.getBytes("UTF-16LE");
    } catch(UnsupportedEncodingException e) {
        // should not possible
        AssertionError ae =
        new AssertionError("Could not encode UTF-16LE");
        ae.initCause(e);
        throw ae;
    }

    // use brute force method to add BOM
    byte[] utf16lemessage = new byte[2 + tmp.length];
    utf16lemessage[0] = (byte)0xFF;
    utf16lemessage[1] = (byte)0xFE;
    System.arraycopy(tmp, 0,
                     utf16lemessage, 2,
                     tmp.length);
    return utf16lemessage;
}

Javaでこれを行うための最良の方法は何ですか?理想的には、バイト配列全体を、最初に2つの余分なバイトが割り当てられている新しいバイト配列にコピーしないようにします。

このような文字列のデコードについても同じことが言えますが、 Java.lang.Stringコンストラクター を使用する方がはるかに簡単です。

public String(byte[] bytes,
              int offset,
              int length,
              String charsetName)
23
Jared Oberhaus

「UTF-16」文字セット名は常にBOMでエンコードし、ビッグ/リトルエンディアンを使用してデータをデコードしますが、「UnicodeBig」と「UnicodeLittle」は特定のバイトオーダーでエンコードする場合に便利です。 BOMがない場合はUTF-16LEまたはUTF-16BEを使用します--- この投稿を参照 「\ uFEFF」を使用してBOMを手動で処理する方法について。文字セット文字列名の正規の命名については here を、または(できれば) Charset クラスを参照してください。また、サポートする必要があるのは エンコーディングの限定されたサブセット のみであることに注意してください。

29
McDowell

これがnioでのやり方です:

    return Charset.forName("UTF-16LE").encode(message)
            .put(0, (byte) 0xFF)
            .put(1, (byte) 0xFE)
            .array();

確かに高速になるはずですが、内部でいくつの配列を作成するかはわかりませんが、APIのポイントについての私の理解は、それを最小限に抑えることになっているということです。

7
Yishai

まず、デコードには文字セット「UTF-16」を使用できます。初期BOMを自動的に検出します。 UTF-16BEのエンコードには、「UTF-16」文字セットを使用することもできます。これにより、適切なBOMが書き込まれ、ビッグエンディアンのものが出力されます。

BOMを使用してリトルエンディアンにエンコードする場合、(文字列が本当に巨大でない限り)二重割り当てを使用しても、現在のコードはそれほど悪くないと思います。それらがバイト配列ではなく、Java.nio ByteBufferを処理し、Java.nio.charset.CharsetEncoderクラスを使用する場合に実行したいことがあります。 (これはCharset.forName( "UTF-16LE")。newEncoder()から取得できます)。

6
Daniel Martin
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(string.length() * 2 + 2);
    byteArrayOutputStream.write(new byte[]{(byte)0xFF,(byte)0xFE});
    byteArrayOutputStream.write(string.getBytes("UTF-16LE"));
    return byteArrayOutputStream.toByteArray();

編集:あなたの質問を読み直すと、二重配列の割り当てを完全に避けたいと思います。残念ながら、私が知る限り、APIはそれを提供しません。 (メソッドがありましたが、非推奨であり、エンコードを指定することはできません)。

あなたのコメントを見る前に上記を書きましたが、nioクラスを使用するための答えは正しい方向に進んでいると思います。私はそれを見ていましたが、APIに精通していないため、どのようにそれを実行するかをすぐに知ることができません。

2
Yishai

これは古い質問ですが、それでも、自分の状況に受け入れられる答えを見つけることができませんでした。基本的に、Javaには、BOMを備えたUTF-16LE用の組み込みエンコーダーがありません。したがって、独自の実装を展開する必要があります。

これが私が最終的に得たものです:

private byte[] encodeUTF16LEWithBOM(final String s) {
    ByteBuffer content = Charset.forName("UTF-16LE").encode(s);
    byte[] bom = { (byte) 0xff, (byte) 0xfe };
    return ByteBuffer.allocate(content.capacity() + bom.length).put(bom).put(content).array();
}
0
hopia