1. 序章

多くの場合、JavaではString配列とbyte配列の間で変換する必要があります。 このチュートリアルでは、これらの操作について詳しく説明します。

まず、Stringbyte配列に変換するさまざまな方法を見ていきます。 次に、同様の操作を逆に見ていきます。

2. 文字列バイト配列に変換する

String は、JavaではUnicode文字の配列として格納されます。 これをbyte配列に変換するために、文字のシーケンスをバイトのシーケンスに変換します。 この翻訳では、 Charsetのインスタンスを使用します。 このクラスは、文字のシーケンスとバイトのシーケンスの間のマッピングを指定します

上記のプロセスをencodingと呼びます。

Javaでは、Stringbyte配列に複数の方法でエンコードできます。 それぞれを例を挙げて詳しく見ていきましょう。

2.1. String.getBytes()を使用する

Stringクラスは、文字列をバイト配列にエンコードするための3つのオーバーロードされたgetBytesメソッドを提供します

まず、プラットフォームのデフォルトの文字セットを使用して文字列をエンコードしましょう:

String inputString = "Hello World!";
byte[] byteArrray = inputString.getBytes();

上記のメソッドは、プラットフォームのデフォルトの文字セットを使用するため、プラットフォームに依存します。 この文字セットを取得するには、 Charset.defaultCharset()を呼び出します。

次に名前付き文字セットを使用して文字列をエンコードしましょう:

@Test
public void whenGetBytesWithNamedCharset_thenOK() 
  throws UnsupportedEncodingException {
    String inputString = "Hello World!";
    String charsetName = "IBM01140";

    byte[] byteArrray = inputString.getBytes("IBM01140");
    
    assertArrayEquals(
      new byte[] { -56, -123, -109, -109, -106, 64, -26,
        -106, -103, -109, -124, 90 },
      byteArrray);
}

名前付き文字セットがサポートされていない場合、このメソッドはUnsupportedEncodingExceptionをスローします。

入力に文字セットでサポートされていない文字が含まれている場合、上記の2つのバージョンの動作は定義されていません。 対照的に、3番目のバージョンは、文字セットのデフォルトの置換バイト配列を使用して、サポートされていない入力をエンコードします。

次に、 getBytes()メソッドの3番目のバージョンを呼び出して、Charset:のインスタンスを渡します。

@Test
public void whenGetBytesWithCharset_thenOK() {
    String inputString = "Hello ਸੰਸਾਰ!";
    Charset charset = Charset.forName("ASCII");

    byte[] byteArrray = inputString.getBytes(charset);

    assertArrayEquals(
      new byte[] { 72, 101, 108, 108, 111, 32, 63, 63, 63,
        63, 63, 33 },
      byteArrray);
}

ここでは、ファクトリメソッド Charset.forName を使用して、Charsetのインスタンスを取得しています。 要求された文字セットの名前が無効な場合、このメソッドは実行時例外をスローします。 また、文字セットが現在のJVMでサポートされている場合は、ランタイム例外がスローされます。

ただし、一部の文字セットは、すべてのJavaプラットフォームで使用できることが保証されています。 StandardCharsets クラスは、これらの文字セットの定数を定義します。

最後に、標準の文字セットの1つを使用してエンコードしましょう:

@Test
public void whenGetBytesWithStandardCharset_thenOK() {
    String inputString = "Hello World!";
    Charset charset = StandardCharsets.UTF_16;

    byte[] byteArrray = inputString.getBytes(charset);

    assertArrayEquals(
      new byte[] { -2, -1, 0, 72, 0, 101, 0, 108, 0, 108, 0,
        111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100, 0, 33 },
      byteArrray);
}

これで、さまざまなgetBytesバージョンのレビューが完了しました。 次に、Charset自体が提供するメソッドを調べてみましょう。

2.2. Charset.encode()を使用する

Charsetクラスはencode()を提供します。これは、Unicode文字をバイトにエンコードする便利なメソッドです。このメソッドは、charsetのデフォルトの置換バイト配列を使用して、無効な入力文字とマップ不可能な文字を常に置換します。

encode メソッドを使用して、Stringbyte配列に変換してみましょう。

@Test
public void whenEncodeWithCharset_thenOK() {
    String inputString = "Hello ਸੰਸਾਰ!";
    Charset charset = StandardCharsets.US_ASCII;

    byte[] byteArrray = charset.encode(inputString).array();

    assertArrayEquals(
      new byte[] { 72, 101, 108, 108, 111, 32, 63, 63, 63, 63, 63, 33 },
      byteArrray);
}

上記のように、サポートされていない文字は、文字セットのデフォルトの置換 byte63に置き換えられています。

これまで使用してきたアプローチでは、CharsetEncoderクラスを内部的に使用してエンコードを実行します。 次のセクションでこのクラスを調べてみましょう。

2.3. CharsetEncoder

CharsetEncoderは、Unicode文字を特定のcharsetのバイトシーケンスに変換します。 さらに、エンコードプロセスをきめ細かく制御できます

このクラスを使用して、Stringbyte配列に変換してみましょう。

@Test
public void whenUsingCharsetEncoder_thenOK()
  throws CharacterCodingException {
    String inputString = "Hello ਸੰਸਾਰ!";
    CharsetEncoder encoder = StandardCharsets.US_ASCII.newEncoder();
    encoder.onMalformedInput(CodingErrorAction.IGNORE)
      .onUnmappableCharacter(CodingErrorAction.REPLACE)
      .replaceWith(new byte[] { 0 });

    byte[] byteArrray = encoder.encode(CharBuffer.wrap(inputString))
                          .array();

    assertArrayEquals(
      new byte[] { 72, 101, 108, 108, 111, 32, 0, 0, 0, 0, 0, 33 },
      byteArrray);
}

ここでは、CharsetオブジェクトでnewEncoderメソッドを呼び出して、CharsetEncoderのインスタンスを作成しています。

次に、 onMalformedInput()および onUnmappableCharacter()methodsを呼び出してエラー状態のアクションを指定します。次のアクションを指定できます。

  • 無視–誤った入力を削除します
  • REPLACE –誤った入力を置き換えます
  • レポート– CoderResult オブジェクトを返すか、 CharacterCodingException をスローして、エラーを報告します

さらに、 replaceWith()メソッドを使用して、置換byte配列を指定しています。

このようにして、文字列をバイト配列に変換するためのさまざまなアプローチのレビューを完了しました。 次に、逆の操作を見てみましょう。

3. バイト配列を文字列に変換する

バイト配列を文字列に変換するプロセスをdecodingと呼びます。 エンコーディングと同様に、このプロセスにはCharsetが必要です。

ただし、バイト配列のデコードに文字セットを使用することはできません。 特に、文字列をバイト配列にエンコードした文字セットを使用する必要があります。

また、さまざまな方法でバイト配列を文字列に変換できます。 それぞれを詳しく調べてみましょう。

3.1. Stringコンストラクターの使用

Stringクラスには、入力としてバイト配列を受け取るコンストラクターがいくつかあります。 これらはすべてgetBytesメソッドに似ていますが、逆に機能します。

そこでプラットフォームのデフォルトの文字セットを使用してバイト配列を文字列に変換しましょう:

@Test
public void whenStringConstructorWithDefaultCharset_thenOK() {
    byte[] byteArrray = { 72, 101, 108, 108, 111, 32, 87, 111, 114,
      108, 100, 33 };
    
    String string = new String(byteArrray);
    
    assertNotNull(string);
}

ここでは、デコードされた文字列の内容について何も主張していないことに注意してください。 これは、プラットフォームのデフォルトの文字セットによっては、別の文字にデコードされる可能性があるためです。

このため、通常、この方法は避けてください。

次に、デコードに名前付き文字セットを使用しましょう:

@Test
public void whenStringConstructorWithNamedCharset_thenOK()
    throws UnsupportedEncodingException {
    String charsetName = "IBM01140";
    byte[] byteArrray = { -56, -123, -109, -109, -106, 64, -26, -106,
      -103, -109, -124, 90 };

    String string = new String(byteArrray, charsetName);
        
    assertEquals("Hello World!", string);
}

名前付き文字セットがJVMで使用できない場合、このメソッドは例外をスローします。

次に、 Charsetオブジェクトを使用してデコードを実行しましょう:

@Test
public void whenStringConstructorWithCharSet_thenOK() {
    Charset charset = Charset.forName("UTF-8");
    byte[] byteArrray = { 72, 101, 108, 108, 111, 32, 87, 111, 114,
      108, 100, 33 };

    String string = new String(byteArrray, charset);

    assertEquals("Hello World!", string);
}

最後に、同じものに標準の文字セットを使用しましょう:

@Test
public void whenStringConstructorWithStandardCharSet_thenOK() {
    Charset charset = StandardCharsets.UTF_16;
        
    byte[] byteArrray = { -2, -1, 0, 72, 0, 101, 0, 108, 0, 108, 0,
      111, 0, 32, 0, 87, 0, 111, 0, 114, 0, 108, 0, 100, 0, 33 };

    String string = new String(byteArrray, charset);

    assertEquals("Hello World!", string);
}

これまで、コンストラクターを使用してbyte配列をStringに変換してきましたが、次に他のアプローチを検討します。

3.2. Charset.decode()を使用する

Charset クラスは、 ByteBufferStringに変換するdecode()メソッドを提供します。

@Test
public void whenDecodeWithCharset_thenOK() {
    byte[] byteArrray = { 72, 101, 108, 108, 111, 32, -10, 111,
      114, 108, -63, 33 };
    Charset charset = StandardCharsets.US_ASCII;
    String string = charset.decode(ByteBuffer.wrap(byteArrray))
                      .toString();

    assertEquals("Hello �orl�!", string);
}

ここで、無効な入力は、文字セットのデフォルトの置換文字に置き換えられます。

3.3.  CharsetDecoder

内部でデコードするための以前のアプローチはすべて、CharsetDecoderクラスを使用することに注意してください。 このクラスを直接使用して、デコードプロセスのきめ細かい制御を行うことができます

@Test
public void whenUsingCharsetDecoder_thenOK()
  throws CharacterCodingException {
    byte[] byteArrray = { 72, 101, 108, 108, 111, 32, -10, 111, 114,
      108, -63, 33 };
    CharsetDecoder decoder = StandardCharsets.US_ASCII.newDecoder();

    decoder.onMalformedInput(CodingErrorAction.REPLACE)
      .onUnmappableCharacter(CodingErrorAction.REPLACE)
      .replaceWith("?");

    String string = decoder.decode(ByteBuffer.wrap(byteArrray))
                      .toString();

    assertEquals("Hello ?orl?!", string);
}

ここでは、無効な入力とサポートされていない文字を「?」に置き換えています。

入力が無効な場合に通知を受け取りたい場合は、デコーダーを変更できます。

decoder.onMalformedInput(CodingErrorAction.REPORT)
  .onUnmappableCharacter(CodingErrorAction.REPORT)

4. 結論

この記事では、 String をバイト配列に、またはその逆に変換する複数の方法を調査しました。 入力データと、無効な入力に必要な制御レベルに基づいて、適切な方法を選択する必要があります。

いつものように、完全なソースコードはGitHubにあります。