デトフィア

プログラミング

コードリーディング FileUtils.readFileToString

コードを読んで勉強する目的

今回のターゲットはFileUtils.readFileToStringです

FileUtils.readFileToStringメソッドの引数を見ると

public static String readFileToString(final File file, final Charset charsetName) throws IOException {
    try (InputStream inputStream = openInputStream(file)) {
        return IOUtils.toString(inputStream, Charsets.toCharset(charsetName));
    }
}

Fileオブジェクトを受け取る必要がありますので、以下のように利用します

try {
    String result = FileUtils.readFileToString(Paths.get("D:\\sumomo\\sumomon.txt").toFile(), Charset.forName("UTF-8"));
    System.out.println(result);
} catch (IOException e) {
    e.printStackTrace();
}

または以下のようなFileクラスを使う方法です

try {
    String result = FileUtils.readFileToString(new File("D:\\sumomo\\sumomon.txt"), Charset.forName("UTF-8"));
    System.out.println(result);
} catch (IOException e) {
    e.printStackTrace();
}

おそらくこのFileクラスのオブジェクトを受け取るのが想定された方法なのでしょうが、java.io.Fileは使われることが少なくなりました。 しかし古くからあるためjava.nioとの相互変換も可能です。

このコードはテキストファイルの中身をStirngで受け取り、コンソールに出力しています。

ソースコード

実装は以下のようになっている

public static String readFileToString(final File file, final Charset charsetName) throws IOException {
    try (InputStream inputStream = openInputStream(file)) {
        return IOUtils.toString(inputStream, Charsets.toCharset(charsetName));
    }
}
  • try-with-resourcesを使って安全にcloseされます
  • 更に別のIOUtilsというクラスを利用しています

渡したFileオブジェクトはopenInputStreamメソッドに渡っています。

public static FileInputStream openInputStream(final File file) throws IOException {
    Objects.requireNonNull(file, "file");
    return new FileInputStream(file);
}
  • Fileのnullチェックを行う
  • FileInputStreamオブジェクトを返す

ちなみに

  InputStreamで文字列を出力する時は、readメソッドの引数に渡したバイト配列をSystem.out.writeなどで出力します。

  文字列ストリームを扱う場合はInputStreamをInputStreamReaderのコンストラクタに渡します

IOUtils.toStringメソッド

public static String toString(final InputStream input, final Charset charset) throws IOException {
    try (final StringBuilderWriter sw = new StringBuilderWriter()) {
        copy(input, sw, charset);
        return sw.toString();
    }
}

StringBuilderWriterというWriterのサブクラスを利用しています。 これはorg.apache.commons.io.outputパッケージにあります

先ほども書いたようにInputStreamReaderで文字IOストリームを扱えます。このクラスはReaderクラスのサブクラスで、Writerというのも同じ立ち位置です。 なのでWriterのサブクラスということは文字IOストリームを扱うためのサブクラスだと認識できます。

copyメソッド

public static void copy(final InputStream input, final Writer writer, final Charset inputCharset)
    throws IOException {
        final InputStreamReader reader = new InputStreamReader(input, Charsets.toCharset(inputCharset));
        copy(reader, writer);
    }

文字IOストリームを扱うためのInputStreamReaderを生成しています。 ここまでほぼ以下のコードと変わりません

FileInputStream fileInputStream = new FileInputStream("file.txt");
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "UTF8");

このメソッドは戻り値の無いvoid型であることに注意します。 IOUtils.toStringメソッドを見て推測すると、Writerオブジェクトに対してファイルの内容を付与していると思われます。

そして引数の違う次のcopyメソッドです

public static int copy(final Reader reader, final Writer writer) throws IOException {
    final long count = copyLarge(reader, writer);
    if (count > Integer.MAX_VALUE) {
        return EOF;
    }
    return (int) count;
}

このメソッドのjavadocを見ると

Copies chars from a Reader to a Writer. This method buffers the input internally, so there is no need to use a BufferedReader. Large streams (over 2GB) will return a chars copied value of -1 after the copy has completed since the correct number of chars cannot be returned as an int. For large streams use the copyLarge(Reader, Writer) method.

とあります。 やはりReader→Writerへと文字列のコピーを行うようです。 大きな文字を扱う場合はcopyLargeメソッドを使ってくださいと注意書きがありますが、内部的にはcopyLargeメソッドを使っているようです??

public static long copyLarge(final Reader reader, final Writer writer, final char[] buffer) throws IOException {
    long count = 0;
    int n;
    while (EOF != (n = reader.read(buffer))) {
        writer.write(buffer, 0, n);
        count += n;
    }
    return count;
}

ここでreader.readメソッドを利用します。 これは配列に文字を読み込みます。そしてストリームの終端に達すると-1を返します。 読み込んだ配列をWriter.writeに渡して書き込んでいます

このwriteメソッドはStringBuilderWriterで実装が以下のようになっています

@Override
public void write(final char[] value, final int offset, final int length) {
    if (value != null) {
        builder.append(value, offset, length);
    }
}

StringBuilderに対して文字列を加えていっているというシンプルな構造です。 そして読み込んだテキストを最終的に文字列として返しているのがtoStringメソッドです

@Override
public String toString() {
    return builder.toString();
}

StringBuilderのtoStringを呼び出しているだけです。

ソースを読むと、上手に面倒な処理が隠蔽されていることがわかります。 もちろんこのクラスが利用しているのはjava.ioパッケージなので、nioを使ってもっとシンプルに扱うこともできます。