「Javaにおける文字コードまわりの話(3) - あしのあしあと」の続き。今回は、ストリームを用いて、文字をエンコードおよびデコードする。
Javaの開発において、文字のエンコード、デコードが必要な箇所は、だいたいストリームを用いているのではないだろうか(String#getBytesかもしれないが)。この場合、バイトストリームであるInputStream、OutputStream、および、文字ストリームであるReader、Writerを用いる。
今回は、検証のためなので、次のストリームを用いることにした。
- 典型的な文字ストリーム
- InputStreamReader
- OutputStreamWriter
- メモリ上に保持するだけの、バイトストリーム
- ByteArrayInputStream
- ByteArrayOutputStream
バイト配列のデコード、文字配列のエンコードのプログラムは、次のようになる。
検証用ということもあって、バイト配列、文字配列は全てメモリ上に展開している。配列が大きいと、相応のメモリを使うので注意。
/** * 指定されたバイト配列をデコードし、文字配列を取得する。 * @param bytes デコード対象のバイト配列 * @param encordingsetName バイト列をデコードするエンコーディングセット名 * @return 指定されたバイト配列をデコードした文字配列 */ public static char[] getChars(byte[] bytes, String encordingsetName) { ArrayList<Character> resultChars = new ArrayList<Character>(); Reader reader = null; try { reader = new InputStreamReader( new ByteArrayInputStream(bytes), encordingsetName); char[] cbuf = new char[2048]; for (int len = 0; len != -1; len = reader.read(cbuf, 0, cbuf.length)) { for (int i = 0; i < len; i++) { resultChars.add(Character.valueOf(cbuf[i])); } } } catch (IOException ioe) { handleIOException(ioe); } finally { closeStreams(reader); } return toCharArray(resultChars); } private static char[] toCharArray(List<Character> charList) { char[] charArray = new char[charList.size()]; int i = 0; for (Character ch : charList) { charArray[i++] = ch.charValue(); } return charArray; } /** * 指定された文字配列をエンコードし、バイト配列を取得する。 * @param chars エンコード対象の文字配列 * @param encordingsetName 文字をエンコードするエンコーディングセット名 * @return 指定された文字配列をエンコードしたバイト配列 */ public static byte[] getBytes(char[] chars, String encordingsetName) { ByteArrayOutputStream os = new ByteArrayOutputStream(); Writer writer = null; try { writer = new OutputStreamWriter(os, encordingsetName); writer.write(chars); writer.flush(); } catch (IOException ioe) { handleIOException(ioe); } finally { closeStreams(writer); } return os.toByteArray(); }
ここで、本質的ではないコードを少なくするために、次の便利メソッドを用いた。
/** * @param streams クローズするストリーム */ private static void closeStreams(Closeable... streams) { for (Closeable stream : streams) { try { if (stream != null) stream.close(); } catch (IOException e) { handleIOException(e); } } } /** * @param ioe 処理対象のI/O例外 */ private static void handleIOException(IOException ioe) { // FIXME ioe.printStackTrace(); }
ここでは、(あえて)エンコーディングセットを指定するようにしている。実際には、エンコーディングセット名を指定しないで用いる場合が多いかもしれない。その場合には、デフォルトのエンコーディングセットが用いられる。
デフォルトのエンコーディングセットは、システムプロパティのfile.encordingに設定されている。デフォルトのエンコーディングセットを指定するには、次の2つの方法がある(次は、Windows-31Jを指定している例)。
(1) Javaの起動オプションに指定する
-Dfile.encording=windows-31j
(2) プログラム中で指定する
System.setProperty("file.encording", "windows-31j");
このようにしておくことで、文字ストリームは(コンストラクタの第2引数を指定しなかった場合に)、デフォルトとして指定されているエンコーディングセットを用いる。デフォルトとして指定されているエンコーディングセットを表示させる方法は、「Javaにおける文字コードまわりの話(2) - あしのあしあと」を参照のこと。
今回はこれだけ*1にしたい。最後に、指定したエンコーディングセットにより、ファイルに書きこんだり、ファイルから読み込んだりする場合のコードをメモしておく。FileReaderを用いると、デフォルトのエンコーディングセットが用いられる(エンコーディングセットを指定できない)ため、ここではあえて使わない。
/** * @param fileIn 読み込むファイル * @param fileOut 書き出すファイル * @param encordingsetNameIn 読み込み時のエンコーディングセット名 * @param encordingsetNameOut 書き出し時のエンコーディングセット名 */ public static void readAndWriteFileInGivenEncording( File fileIn, File fileOut, String encordingsetNameIn, String encordingsetNameOut) { Reader reader = null; Writer writer = null; try { reader = new InputStreamReader(new FileInputStream(fileIn), encordingsetNameIn); writer = new OutputStreamWriter(new FileOutputStream(fileOut), encordingsetNameOut); char[] cbuf = new char[2048]; for (int len = 0; len != -1; len = reader.read(cbuf)) { // ここで、読み込んだ文字に対して処理を行う。 writer.write(cbuf); } } catch (FileNotFoundException fnfe) { handleIOException(fnfe); } catch (IOException ioe) { handleIOException(ioe); } finally { closeStreams(reader, writer); } }
実際の検証においては、読み込みだけ、書き出しだけの場合を使うことが多いと思う。
2010/07/24追記
「Javaにおける文字コードまわりの話(5) - あしのあしあと」に、(整理し切れてはいないのだが)簡単なまとめを書いた。