文字列の文字コード(文字セット)を変換する方法

Scala 3 (Dotty 0.26.0-RC1) 2.13.3 2.12.12
最終更新:2020年7月6日

[AD] scalapediaでは記事作成ボランティアを募集しています

この記事では、文字列の文字コード(文字セット)を変換する方法を解説します。

Scalaには独自の変換方法が用意されていないので、JavaのAPIを利用して変換します。

Javaにおいては、String#getBytesメソッドを使用して簡便に変換する方法と、java.nio.charset.CharsetEncoderクラスを使用して厳密に変換する方法があります。

厳密に処理するならCharsetEncoder、そうでなければString#getBytesを使用する

まず、String#getBytesメソッドとCharsetEncoderクラスの使い分けについて説明します。

String#getBytesメソッドを使用する場合、もし指定された文字セットに含まれず変換できない文字が含まれているときには、適切に変換することができません。

したがって、以下の2つの条件を満たす場合には、CharsetEncoderを使用します。

  • 変換先の文字セットに含まれない無効な文字が文字列中に存在する可能性がある
  • そのような文字が存在したことを検知して独自に処理する必要がある

参考となるリンクを掲載しておきますので、必要に応じて参照してください。

逆に、以下のいずれかの場合にはString#getBytesを使用します。

  • 変換先の文字セットに含まれないような無効な文字が文字列中に存在する可能性はない
  • もしそのような文字が存在したとしても、メソッドに定められた標準の挙動で十分である

この記事では、String#getBytesメソッドを使用する方法を解説します。

getBytes メソッドを使用して文字セットを変換する

String#getBytes メソッドを利用すると、文字列の文字セットを変更してバイト列に変換することができます。

Java
public byte[] getBytes​(String charsetName) public byte[] getBytes​(Charset charset)

Scala風に書くと以下のようになります。

Scala
def getBytes​(charsetName: String): Array[Byte] def getBytes​(charset: Charset): Array[Byte]

文字セットの変換に使えるgetBytesメソッドは2つありますが、これらの違い、使い分けについては後述します。

getBytes(charsetName: String) を使用して変換する

まずは getBytes(charsetName: String) を使用して文字セットを変換してみましょう。

こちらの文字列に対して変換処理を行います。

val s = s"Scala逆引き解説 scalapedia"

サンプルコードはこちらです。

val utf8Bytes = s.getBytes println(utf8Bytes.mkString(",")) val sjisBytes = s.getBytes("Shift-JIS") println(sjisBytes.mkString(","))

まずは標準の文字セットでバイト列を出力しています。
この環境では特に指定していないので、標準の文字セットはUTF-8となります。
中身の確認のため、コンマ区切りにして出力しています。

次に、文字列をShift-JISのバイト列に変換します。
そしてこちらも中身の確認のため、コンマ区切りにして出力します。

出力結果は以下のようになります。

83,99,97,108,97,-23,-128,-122,-27,-68,-107,-29,-127,-115,-24,-89,-93,-24,-86,-84,32,115,99,97,108,97,112,101,100,105,97 83,99,97,108,97,-117,116,-120,-8,-126,-85,-119,-16,-112,-32,32,115,99,97,108,97,112,101,100,105,97

一行目と二行目でそれぞれ出力が異なることがわかりますね。

今度はそれぞれ文字列に戻してみましょう。
Stringのコンストラクタにバイト列を渡すと、文字列のインスタンスを生成できます。

println(new String(utf8Bytes)) println(new String(sjisBytes, "SHIFT_JIS"))

出力結果は以下のようになります。

Scala逆引き解説 scalapedia Scala逆引き解説 scalapedia

きちんと元に戻っていることがわかります。

getBytes(charset: Charset) を使用して変換する

今度は getBytes(charset: Charset) を使用して文字セットを変換してみましょう。

Charsetを取得する

このメソッドは引数としてCharsetを受け取ります。
つまりあらかじめ変換先のCharsetを取得して用意しておく必要があります。

使用するCharsetCharset#forName メソッドを使用することで取得できます。

import java.nio.charset.Charset val sjis: Charset = Charset.forName("sjis")

取得したCharsetの中に何が入っているのかみてみましょう。

println(sjis.name)
Shift_JIS

「正準名」が返ってきました。
取得した際の文字列とは異なります。

ここまでの例では、Shift-JISの表記をいろいろ変えて試してきました。
正準名以外に別名もカバーされているから、多少の表記ゆれが許容されるということがわかります。便利ですね。

サポートされている文字セットはこちらのページにまとめられています。

StandardCharsetsを使用する

また、頻出の文字セットはStandardCharsetsとして定数があらかじめ用意されています。

使用できるのは以下の6種類です。

Charset通称
ISO_8859_1ISO-LATIN-1
US_ASCIIASCII文字
UTF_8UTF-8
UTF_16UTF-16
UTF_16BEUTF-16 ビッグエンディアン・バイト順
UTF_16LEUTF-16 リトルエンディアン・バイト順

これらの文字コードを使用する場合は表記ミスによる不具合を防ぐため、できる限りStandardCharsetsを使用していきましょう。

文字セットを変換する

それでは、また先ほどの文字列に対して変換処理を行いましょう。

val s = s"Scala逆引き解説 scalapedia"

サンプルコードはこちらです。

val sjisBytes = s.getBytes(sjis) println(sjisBytes.mkString(","))

文字列をShift-JISのバイト列に変換します。
中身の確認のため、コンマ区切りにして出力します。

出力結果は以下のようになります。

83,99,97,108,97,-117,116,-120,-8,-126,-85,-119,-16,-112,-32,32,115,99,97,108,97,112,101,100,105,97

先ほどの例の出力の二行目と同じであることがわかります。

2つの getBytes の違いは異常系の処理

上述のように、文字セットの変換に使えるgetBytesメソッドは2つあります。
これらの違いは2つあります。

  • 文字コードが存在しない場合に投げる例外が異なる
  • 無効な文字が含まれる場合の挙動が異なる

結論としてはこの相違点はそれほど重大な問題とはなりにくいのですが、念のため、どう違うのかについて確認していきましょう。

文字コードが存在しない場合の例外は気にする必要なし

まず、両者のメソッドは文字コードが存在しない場合に投げる例外が異なります。

getBytes(charsetName: String)では、引数に渡したcharsetNameに対応する文字コードが存在しない場合にjava.lang.UnsupportedEncodingExceptionを投げます。

UnsupportedEncodingExceptionはチェック例外なので、Javaのコードにおいてはエラーハンドリングを強制されます。
煩わしいですね。

他方、getBytes(charset: Charset)自体が例外を投げるわけではないのですが、文字セットの取得のために使用するCharset#forNameが例外を投げます。
もし対応する文字コードが存在しない場合にはjava.nio.charset.UnsupportedCharsetExceptionを投げます。

UnsupportedCharsetExceptionは非チェック例外なので、エラーハンドリングは強制されません。
つまり、JavaにおいてはgetBytes(charset: Charset)の方が取り回しがいいのです。 ただし、Scalaにはチェック例外がないので、いずれの場合にも関係ありません。

また、実際に変換するタイミングではなく、それよりも前の文字コードを検索する段階で例外が発生するようになります。 例外発生のタイミングが異なることと、投げられる例外のクラスが異なることに注意しましょう。 変換の都度検索するより多少効率がよくなります。

無効な文字が含まれる場合の挙動は注意が必要

また、両者のメソッドは無効な文字が含まれる場合の挙動が異なります。

getBytes(charsetName: String)の無効な文字が含まれる場合の挙動は指定されておらず、JVMの実装依存となります。
気になる場合には検証するか、getBytes(charset: Charset)を使用するか、CharsetEncoderを使用しましょう。

getBytes(charset: Charset)|デフォルトの置換バイト列で置き換えられます。
「デフォルトの置換バイト列」が何なのかは確認しておきましょう。

Charset#forName を使って文字セットを事前検索すると良い

さて、文字セットを指定する方法は、直接文字列で指定する方法(getBytes(charsetName: String))と、 Charset#forName を使って取得したCharset型で指定する方法(getBytes(charset: Charset))の2つがありますね。
どちらを使えば良いでしょうか。

チェック例外のないScalaにおいては、両者それぞれそれなりに効率的なので、結論としては「どちらでもいい」ということになりますが、強いて言えば以下の点を参考にすると良いでしょう。

  • 文字コードをあちこちで引き回す場合には、getBytes(charset: Charset)を使った方が型安全で良い。
    • 文字コードを引き回す予定がないなら、Charset#forNameせず直接getBytes(charsetName: String)を呼ぶだけで十分。
  • getBytes(charsetName: String)も実装上はCharset#forNameを使用している(ので、Charset#forNameを他で呼ばないような書き方をすると効率的で良い)。
  • 使用する文字セットが限られる場合には、getBytes(charset: Charset)を使った方が検索回数が節約できるので効率的で良い。

あるいは、もし無効な文字が含まれる場合の挙動が気になる場合は、getBytes(charset: Charset)を使うか、あるいはCharsetEncoderの使用を検討するべきかもしれません。

文字列の内容の編集は文字セットを変換する前に行っておく

ところで、getBytesメソッドの戻り値はバイト列Array[Byte]です。
言い換えると、文字セットの変換後にはStringとしての形を保つことはできません。

これは、文字列の内部表現がUTF-16に固定されているからです。
文字列の内部表現で使用する文字セットを変換することはできません。

つまり変換後の状態では、内容を編集するのが難しいということになります。
文字列の中身に対する操作は変換前に済ませておきましょう。

まとめ

文字列の文字セットを簡便に変換したい場合はString#getBytesメソッドを使用します。

また、厳密に変換したい場合は java.nio.charset.CharsetEncoder を使用します。

文字列の内容について必要な編集をおこなった後、文字セットを変換するようにしましょう。

サイト内検索


カテゴリ「文字列処理」の記事

文字列をエスケープしたり復元したりする方法(Apache Commons Text) JavaとScalaのString/StringBuilder/StringBuffer使い分け事情 文字列を分割する方法(split・splitAt・linesIterator・linesWithSeparatorsメソッド) trimメソッドで文字列の前後の空白を除去する 文字列が一致するか比較する方法/大文字・小文字を区別せずに比較する方法 特定の文字の文字コード(コード・ポイント)を取得する replaceメソッドなど、文字列を置換する方法を紹介 文字列の先頭や末尾を、取得したり切り落としたりする方法 containsメソッドでStringに特定の文字列が含まれるか調べる方法 数値を文字列に変換する方法 stripメソッドで文字列の前後の全角空白を除去する 文字列を数値に変換するには?to○○メソッドと注意点について 文字列を辞書的に比較する方法/大文字・小文字を区別せずに比較する方法 文字列が特定の文字列で始まるか・終わるかを調べる方法 【getBytes&size】文字列のバイト長を取得する方法 substringでStringを切り取り、部分文字列を抽出する方法 StringOpsとWrappedStringの違いは? 文字列を連結するには?+演算子やString interpolationの使い方 文字列の大文字へ・小文字へ変換する方法 文字列を逆順にする方法 文字列の文字コード(文字セット)を変換する方法 文字列の長さを取得する方法:lengthとcodePointCountの使い分け 文字列をURLエンコード・デコードする方法 文字列が正規表現に合致するか調べる方法 この文字の位置はどこ?文字列のインデックスを取得する方法 文字列をバイト列に、またはバイト列を文字列に変換する方法

カテゴリ「文字列処理」の記事

文字列をエスケープしたり復元したりする方法(Apache Commons Text) JavaとScalaのString/StringBuilder/StringBuffer使い分け事情 文字列を分割する方法(split・splitAt・linesIterator・linesWithSeparatorsメソッド) trimメソッドで文字列の前後の空白を除去する 文字列が一致するか比較する方法/大文字・小文字を区別せずに比較する方法 特定の文字の文字コード(コード・ポイント)を取得する replaceメソッドなど、文字列を置換する方法を紹介 文字列の先頭や末尾を、取得したり切り落としたりする方法 containsメソッドでStringに特定の文字列が含まれるか調べる方法 数値を文字列に変換する方法 stripメソッドで文字列の前後の全角空白を除去する 文字列を数値に変換するには?to○○メソッドと注意点について 文字列を辞書的に比較する方法/大文字・小文字を区別せずに比較する方法 文字列が特定の文字列で始まるか・終わるかを調べる方法 【getBytes&size】文字列のバイト長を取得する方法 substringでStringを切り取り、部分文字列を抽出する方法 StringOpsとWrappedStringの違いは? 文字列を連結するには?+演算子やString interpolationの使い方 文字列の大文字へ・小文字へ変換する方法 文字列を逆順にする方法 文字列の文字コード(文字セット)を変換する方法 文字列の長さを取得する方法:lengthとcodePointCountの使い分け 文字列をURLエンコード・デコードする方法 文字列が正規表現に合致するか調べる方法 この文字の位置はどこ?文字列のインデックスを取得する方法 文字列をバイト列に、またはバイト列を文字列に変換する方法