倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

これからの「Java I/O」の話をしようwww (5) : Files クラスのメソッド 〜ファイル内容の読み書き〜

Java nio の API を見ていくシリーズ(目次)。 今回はファイルに対しての読み書きを行うメソッドを見ていきます。 Files クラスのメソッドで一番使うものかも知れませんね。

Files クラスには、いろいろな方法でファイルに読み書きするメソッドが定義されてます。 大別すれば「バイト」と「文字」の読み書きですが、これは java.io パッケージのクラスで言うと InputStream/OutputStream と Reader/Writer に対応するものです*1。 それぞれの読み書きにも更に幾つかの方法があります:

以下でそれぞれを見ていきます。 更に上記の幾つかのメソッドで指定できる OpenOption についても見ていきます:

参考

Files クラスの宣言

まずは java.nio.file.Files クラスに定義されている、ファイルの読み書きに関する static メソッドには以下のようなものがあります:

package java.nio.file;

public final Class Files{
    ...

    // 読み書き可能?
    public static boolean isReadable(Path path)
    public static boolean isWritable(Path path)

    // ***** Byte IO *****
    // バイト配列
    public static byte[] readAllBytes(Path path)
    public static Path write(Path path, byte[] bytes, OpenOption... options)

    // バイト・ストリーム
    public static InputStream newInputStream(Path path, OpenOption... options)
    public static OutputStream newOutputStream(Path path, OpenOption... options)

    // バイト・チャネル
    public static SeekableByteChannel newByteChannel(Path path, OpenOption... options)
    public static SeekableByteChannel newByteChannel(
            Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)

    // ***** 文字列 *****
    // 文字列
    public static List<String> readAllLines(Path path, Charset cs)
    public static Path write(
            Path path, Iterable<? extends CharSequence> lines, Charset cs, OpenOption... options)

    // 文字ストリーム
    public static BufferedReader newBufferedReader(Path path, Charset cs)
    public static BufferedWriter newBufferedWriter(Path path, Charset cs, OpenOption... options)

    // 【New】Java8 Stream API
    static Stream<String> lines(Path path)
    static Stream<String> lines(Path path, Charset cs)
}

以下のサンプルでは使ってませんが、引数のファイルが読み書き可能かどうかをテストするメソッドがあります:

メソッド 返り値 説明
isReadable(Path) boolean 指定したファイルが読み込みできるかどうかをテスト
isWritable(Path) boolean 指定したファイルが書き込みできるかどうかをテスト

バイトの読み書き

バイトベースの読み書きを行うメソッドを見ていきましょう。 これには3種類あります:

  • バイト配列
  • バイト・ストリーム
  • バイト・チャネル

バイト配列
読み書きを行うメソッドは以下の通り:

メソッド 返り値 説明
readAllBytes(Path) byte[] ファイルの内容をバイト配列として全て読み込む
write(Path, byte[], OpenOption...) Path 引数のバイト配列をファイルへ書き出す

これらは小さいファイルの読み書きに使ってください。 「source.txt」というファイルから内容を読み取って「dest1.txt」へ書き出すサンプル:

import java.io.IOException;
import java.nio.file.*;

public class ByteIO {

    public static void main(String... args)throws IOException{
        Path src = Paths.get("source.txt");
        byte[] bytes = Files.readAllBytes(src);    // バイト配列へ読み込む

        System.out.println(new String(bytes));

        Path dest = Paths.get("dest1.txt");
        Files.write(dest, bytes);    // バイト配列をファイルへ書き出す
    }
}
  • write() メソッドは、OpenOption が指定されていなければ CREATE, TRUNCATE_EXISTING, WRITE が指定されているのと同じになります

バイト・ストリーム
次はバイト・ストリームに対して読み書きするメソッド:

メソッド 返り値 説明
newInputStream(Path, OpenOption...) InputStream 指定したファイルの内容を読み込む InputStream を返す
newOutputStream(Path, OpenOption...) OutputStream 指定したファイルへ書き出す OutputStream を返す

返り値の InputStream/OutputStream は、どちらも java.io パッケージのクラスです。

「source.txt」というファイルから内容を読み取って「dest2.txt」へ書き出すサンプル:

import java.io.*;
import java.nio.file.*;

public class ByteStreamIO {

    public static void main(String... args)throws IOException{
        Path src = Paths.get("source.txt");
        Path dest = Paths.get("dest2.txt");

        try(InputStream in = Files.newInputStream(src);
              OutputStream out = Files.newOutputStream(dest)){

            for(int c = in.read(); c != -1; c = in.read()){    // InputStream からの読み込み
                System.out.print((char)c);
                out.write((char)c);    // OutputStream への書き出し
            }
        }
    }
}
  • 返される InputStream/OutputStream は通常の java.io パッケージのクラスで、使い方も同じです
  • InputStream/OutputStream は使用後に close() メソッドで閉じる必要があります。 Java7 では try-with-resources 文で暗黙に閉じるのが普通でしょうけど
  • newInputStream() メソッドは、OptionOption が指定されていない場合は、READ が指定されているのと同じです
  • newOutputStream() メソッドは、OpenOption が指定されていなければ CREATE, TRUNCATE_EXISTING, WRITE が指定されているのと同じになります

バイト・チャネル

メソッド 返り値 説明
newByteChannel(Path, OpenOption...)
newByteChannel(Path, Set, FileAttribute...)
SeekableByteChannel 指定したファイルに対するバイト・チャネルを返す

返り値の SeekableByteChanneljava.nio.channels パッケージのクラスです。 このクラスは通常 FileChannel にキャストできるそうです。 なぜ返り値がそうなってないのかは分かりませんが・・・ また、バイト・チャネルには読み書きを分けて返すメソッドがないので、バイト・チャネルに書き出したい場合は OpenOption を指定する必要があります。 下記のサンプル参照。

「source.txt」というファイルから内容を読み取って「dest3.txt」へ書き出すサンプル:

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.*;
import static java.nio.file.StandardOpenOption.*;

public class ByteChannelIO {

    public static void main(String... args)throws IOException{
        Path src = Paths.get("source.txt");
        Path dest = Paths.get("dest3.txt");

        try(FileChannel inCh = (FileChannel)Files.newByteChannel(src);  // 返り値は FileChannel。 別にキャストしなくても OK
              FileChannel outCh = (FileChannel)Files.newByteChannel(dest, CREATE, TRUNCATE_EXISTING, WRITE)){
                    // バイト・チャネルに書き出すためには、OpenOption に少なくとも StandardOpenOption.WRITE を指定する必要あり

            ByteBuffer buf = ByteBuffer.allocate((int)inCh.size());
            inCh.read(buf);    // バイト・チャネル inCh から buf へ読み込む

            String s = new String(buf.array());
            System.out.print(s);

            outCh.write(ByteBuffer.wrap(s.getBytes()));    // String → byte[] → ByteBuffer と変換して outCh へ書き出し
        }
    }
}
  • ByteChannel, ByteBuffer などの使い方はそれぞれの JavaDoc などを参照してください
  • バイト・チャネルに書き出したい場合は、newByteChannel() メソッドの OpenOption に StandardOpenOption.WRITE を指定する必要があります

文字列の読み書き

次は文字列の読み書きをするメソッド。 読み書き対象のファイルを指定する Path オブジェクトに加えて、エンコーディングを行っているキャラクターセット (java.nio.charset.Charset オブジェクト)を指定する必要があります。

文字列
まずはファイル内の各行を String オブジェクトとして読み書きするメソッド:

メソッド 返り値 説明
readAllLines(Path, Charset) List ファイル内の各行を String オブジェクトとして読み取る
write(Path, Iterable, Charset, OpenOption...) Path 各 CharSequence を行としてファイルへ書き出す

write() メソッドの引数にある CharSequence クラスは、とりあえず String オブジェクトと思って構いません。 「Iterable<? extends CharSequence>」は例えば「List<String>」などが型としてマッチします。 また、バイト配列の読み書きに使った readAllBytes()/write() メソッド同様、これらのメソッドは小さいファイルに対して使うようにしてください。

「source.txt」というファイルから内容を読み取って「dest4.txt」へ書き出すサンプル:

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.*;
import java.util.List;

public class StringIO {

    public static void main(String... args)throws IOException{
        Path src = Paths.get("source.txt");
        List<String> lines = Files.readAllLines(src, Charset.defaultCharset());    // ファイル内容の読み込み

        for(String line : lines){
            System.out.println(line);
        }

        Path dest = Paths.get("dest4.txt");
        Files.write(dest, lines, Charset.forName("UTF-8"));    // ファイル内容の書き出し
    }
}
  • ファイルシステムのデフォルト Charset を取得するには Charset#defaultCharset() メソッドを用います
  • 名前を指定して Charaset オブジェクトを取得したい場合は Charset#forName(String) メソッドを用います
  • write() メソッドは、OpenOption が指定されていなければ CREATE, TRUNCATE_EXISTING, WRITE が指定されているのと同じになります

文字ストリーム

メソッド 返り値 説明
newBufferedReader(Path, Charset) java.io.BufferedReader 指定したファイルから内容を読み込む BufferedReader を返す
newBufferedWriter(Path, Charset, OpenOption...) java.io.BufferedWriter 指定したファイルへ書き出す BufferedWriter を返す

返り値の BufferedReader/BufferedWriter は、 java.io パッケージのいつものクラスです。

「source.txt」というファイルから内容を読み取って「dest5.txt」へ書き出すサンプル:

import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.*;

public class CharacterStreamIO {

    public static void main(String... args)throws IOException{
        Path src = Paths.get("source.txt");
        Path dest = Paths.get("dest5.txt");

        try(BufferedReader reader = Files.newBufferedReader(src, Charset.defaultCharset());
              BufferedWriter writer = Files.newBufferedWriter(dest, Charset.defaultCharset())){

            for(String line = reader.readLine(); line != null; line = reader.readLine()){    // ファイル内容の読み込み
                System.out.println(line);

                writer.write(line);    // ファイルへの書き出し
                writer.newLine();
            }
        }
    }
}
  • BufferedReader/BufferedWriter は普通に使ってください。 使用後は close() メソッドによって閉じる必要があります。 ここでは try-with-resources 文によって close 処理を行っています
  • newBufferedReaderWriter() メソッドは、OpenOption が指定されていなければ CREATE, TRUNCATE_EXISTING, WRITE が指定されているのと同じになります

OpenOption インターフェース

最後に OpenOption インターフェースについて。 OpenOption インターフェースを実装しているクラスは以下の2つ:

  • LinkOption
  • StandardOpenOption

これらはどちらも列挙型 (enum) です。 LinkOption 定数は前回やりました。 なので、ここでは StandardOpenOption についてのみ見ていきます。 StandardOpenOption の定義は以下の通り:

package java.nio.file;

public enum StandardOpenOption
        extends Enum<StandardOpenOption>
        implements OpenOption{

    READ,
    WRITE,
    APPEND,
    TRUNCATE_EXISTING,
    CREATE_NEW,
    CREATE,
    DELETE_ON_CLOSE,
    SPARSE,
    SYNC,
    DSYNC
}

それぞれの定数の意味は

定数 説明
READ 読み込み可能として開く
WRITE 書き込み可能として開く
APPEND ファイルの末尾に内容を追加する。 WRITE または CREATE とともに用いる
TRUNCATE_EXISTING ファイル内容を最初から書き込む。 WRITE とともに用いる
CREATE_NEW ファイルを新たに作って開く。 もしファイルがあれば例外を投げる
CREATE ファイルを新たに作って開く。 もしあればそのまま開く
DELETE_ON_CLOSE ストリームが閉じられれば、そのファイルを削除する
SPARSE
SYNC ファイル内容とメタデータを同期化
DSYNC ファイル内容を同期化

詳しくは以下を参照:

上記で見た Files メソッドのいろいろな書き込みメソッドで、OpenOption を指定しなかった場合のデフォルトであるCREATE, TRUNCATE_EXISTING, WRITE」というのは、「ファイルがなければ作成し、あれば内容を上書きする」と言う設定になります。

【追記】 Java8 で Files に追加されたファイル内容の読み書きメソッド lines

Java8 で Stream API が追加されたので、Files オブジェクトにも Stream 返すメソッドが追加定義されてます。 ただし、使う際には Stream を閉じるのを忘れずに。 通常 try-with-resources 文と一緒に使います。

import java.nio.file.*;
import java.nio.charset.StandardCharsets;

import static java.util.Arrays.asList;
import java.util.stream.Stream;

public class FilesStreamTest {

    public static void main(String... args)throws Exception {
        // 準備
        Path path = Files.createTempFile(null, null);
        Files.write(path, asList("first line.", "second line.", "third line."));

        // Files#lines(path)
        try(Stream<String> lines = Files.lines(path)){
            lines.forEach(System.out::println);
                // 「first line.
                //  second line.
                //  third line.」と表示
        }

        // Files#lines(Path, Charset)  // エンコーディングの指定
        try(Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8)){
            lines.forEach(System.out::println);    // 先ほどと同じ出力
        }
    }
}

文字エンコーディングを指定しなかった場合は UTF-8 が指定されたものとみなされます。

プログラミングGROOVY

プログラミングGROOVY

*1:大雑把に言うと「バイト + 文字エンコーディング = 文字」なので、Reader/Writer は InputStream/OutputStream に文字エンコーディングによるエンコード/デコード機能を追加したものです。 念のための復習。