プロジェクト

全般

プロフィール

Javaプログラミング ファイル操作

Java SE 7で導入されたNIO.2ライブラリ、Java SE 8で導入されたStream APIを含めた、Javaにおけるファイル操作のメモです。

ディレクトリ(パス)操作

カレントディレクトリを取得する

システムプロパティuser.dirを取得

システムプロパティuser.dirにはカレントディレクトリが格納されるので、これを取得します。

String cwd = System.getProperty("user.dir");

NIO.2でカレントディレクトリを示す.を指定して取得

Path cwd = Paths.get(".");
  • 注記)Paths.get(...)は、内部でFileSystems.getDefault().getPath(...)を呼ぶ

指定したパスのファイルオブジェクト(java.io.File)を生成する

相対パスで指定

File theFile = Paths.get("work", "input", "alfa.txt").toFile();

Pathsクラスのgetメソッドは、引数の数(1個か2個以上か)で振る舞いが変わるようです。詳細はJavadoc参照。

パスからファイル名を抜き出す

Path path = Paths.get(url.getPath());
String fileName = path.getFileName().toString();

指定したディレクトリ直下のディレクトリまたはファイルの一覧を取得

指定したディレクトリの直下(サブディレクトリがある場合、その中は対象外)にあるディレクトリとファイルの一覧を取得します。

NIO.2のDirectoryStreamを使用

try (DirectoryStream<Path> entries = Files.newDirectoryStream(Paths.get("."))) {
    entries.forEach(System.out::println);
} catch (IOException ex) {
    // ...
}

対象ディレクトリをPathで作成し、Files#newDirectoryStreamに渡します。対象ディレクトリの直下に存在するディレクトリとファイルの一覧が、Iterable<Path>型で返却されるので、イテレータ処理をします。ここでは、Iterableに加わったforEachを呼び出しています。

Stream APIを使用

try (Stream<Path> entries = Files.list(Paths.get("."))) {
    entries.forEach(System.out::println);
} catch (IOException ex) {
    // ...
}

対象ディレクトリをPathで作成し、Files#listに渡します。対象ディレクトリの直下に存在するディレクトリとファイルの一覧が、Stream<Path>型で返却されるので、Stream処理をします。ここでは、終端操作のforEachを呼び出しています。

指定したディレクトリ直下のディレクトリまたはファイルから特定の名前の一覧を取得

ディレクトリとファイル一覧から特定の名前のもの、例えば拡張子が.javaのもの、を抜粋します。

NIO.2のDirectoryStreamを使用

try (DirectoryStream<Path> entries = Files.newDirectoryStream(Paths.get("."), "*.java")) {
    entries.forEach(System.out::println);
} catch (IOException ex) {
    // ...
}

Files#newDirectoryStreamの第2引数で指定するのはglobパターンです。FileSystem#getPathMatcherメソッドのJavadocにglobパターンに使える記号の説明があります。このページのglobのパターンマッチングにも簡単に書き方を記述しています。

Stream APIを使用

try (Stream<Path> entries = Files.list(Paths.get("."))) {
    PathMatcher matcher = FileSytems.getDefault().getPathMatcher("glob:**.java");
    entries.filter(matcher::matches)
           .forEach(System.out::println);
} catch (IOException ex) {
    // ...
}

FileSystems#getDefault() で使用しているFileSystemインスタンスを取得し、それのgetPathMatcherで指定したパターンに合致するPathMatcherインスタンスを取得します。パターンには、globパターンと正規表現パターンの2種類が指定可能で、文字列の最初に指定する種類を記述します。

globのパターンマッチング

PathMacherでglob構文によるパターン指定をする際のルールをメモします。

記号 内容 備考
* 0文字以上の任意の文字、ただしディレクトリ区切り子をまたがない
** 0文字以上の任意の文字でディレクトリ区切り子をまたぐ
? 任意の1つの文字
{} パターンをカンマ区切りで複数記述し、いずれかにマッチ *.{cpp,h}
[] 1つの文字の集合 [aeiou] は、a,e,i,o,uのいずれか1文字と一致

指定したディレクトリの下を再帰的にディレクトリまたはファイルから特定の名前の一覧を取得

Stream APIを使用

指定したディレクトリ直下のディレクトリまたはファイル一覧を取得する際に使用したFilesクラスのメソッドlistを、walkメソッドに変更するだけです。

try (Stream<Path> entries = Files.walk(Paths.get("."))) {
    PathMatcher matcher = FileSytems.getDefault().getPathMatcher("glob:**.java");
    entries.filter(matcher::matches)
           .forEach(System.out::println);
} catch (IOException ex) {
    // ...
}

ファイルの読み込み

Javaは、歴史的な経緯でファイル入出力処理のAPIが複数整備されています。

  • java.ioパッケージで提供されるライブラリ(API)
  • java.nioパッケージで提供されるライブラリ(API)

テキストファイルを読み込む

BufferedReaderを使って読み込む

java.nio.file.Filesユーティリティクラスで、Pathオブジェクトで示すファイルに対する BufferedReader を取得します。

以下の例は、テキストファイルを行単位で読み込み、ファイルの各行で、空白で区切った先頭の文字列を、重複なく(同じものは1回だけ)表示します。

try (BufferedReader reader = Files.newBufferedReader(Paths.get(fileName), Charset.forName("UTF-8"))) {
    reader.lines()
          .map(s -> s.split(" ")[0])
          .distinct()
          .forEach(System.out::println);
} catch (IOException ex) {
    // ファイルアクセスに関するエラー処理
}
  • Java SE 7で追加された try-with-resource構文を使って、tryスコープが終了するときにreaderがクローズされます。
  • Java SE 7で追加された NIO2パッケージのFilesクラスでファイルパスと文字コードを指定してBufferedReaderを生成します。
  • Java SE 8で追加された Stream APIを使って、読み込むファイルの各行について、空白で分割した最初の文字列を抜き出し(map)、重複する文字列は除外し(distinct)、残ったそれぞれの文字列について(forEach)コンソールに出力(System.out::println)します。
    lines()を読んだ時点ではファイルI/Oは発生しないので、大きなファイルを扱うことも問題なさそうです。一方、Files.readAllLinesを使うとヒープ領域に対して大きなファイルを扱う際にOutOfMemoryErrorが発生します。

FilesのreadAllLinesを使って一括読み込み

Filesクラスの静的メソッド List<String> readAllLines(Path path, Charset cs) throws IOException を使って、改行区切りのテキストファイルを各行をStringとしたListで一括取得します。
巨大なファイルを読み込むとメモリが破綻する可能性があるので簡易用途です。

try {
    List<String> lines = Files.readAllLines(Paths.get(fileName), Charset.forName("UTF-8")));
} catch (IOException ex) {
    // ファイルアクセスに関するエラー処理
}

readAllLinesは、メソッドの復帰時にファイルがクローズされるので明示的にクローズを呼ぶ必要はありません。

Flies.linesで遅延読み込み

Filesクラスの静的メソッド Stream<String> lines(Path path, Charset cs) throws IOException を使って、改行区切りのテキストファイルをStream APIで遅延処理して読み込みます。readAllLinesと違って一括読み込みはしないのでメモリに優しい処理となっています。

try (String stream = Files.lines(Paths.get(fileName), Charset.forName("UTF-8"))) {
    stream.map(s -> s.split(" ")[0])
          .distinct()
          .forEach(System.out::println);
} catch (IOException ex) {
    // ファイルアクセスに関するエラー処理
}

バイナリファイルを読み込む

バイナリファイルからデータを読み込む場合は、byte配列にバイナリデータを格納する、または ByteBufferにバイナリデータを格納するのが定番です。

その他には、Javaのプリミティブ型またはString型のデータをバイナリデータとして持つファイルを読み込む DataInputStream、JavaのSerializableなオブジェクト型のデータをバイナリデータとして持つファイルを読み込む ObjectInputStreamがありますが、Java同士でファイルを授受するとき以外は使わないでしょう。

BufferedInputStream

バイナリファイルからbyte配列にデータを読み込む際に使うAPIの一つです。InputStreamをバッファリングして読み込みます。読み込みには、
int read(byte[] buf) throws IOException を使用します。引数に指定したbyte配列に、そのサイズ以下のデータを読み込みます。

Path path = Paths.get("sample.dat");
byte[] buffer = new byte[1024];
try (var inStream = new BufferedInputStream(Files.newInputStream(path, StandardOpenOption.READ))) {
    int numRead = 0;
    while ((numRead = inStream.read(buffer)) != -1) {
        // TODO: process read data.
    }
} catch (IOException ex) {
    System.err.println("ERROR: read " + ex);
}
  • データ格納用のbyte配列を用意(処理の単位でサイズを定義)
  • FilesクラスのnewInputStreamメソッドにファイルパスと読み込み専用モードを指定してInputStreamを生成し、BufferedInputStreamでバッファリング処理を追加
  • InputStream#readメソッドで、ファイルからデータを読みbyte配列に格納(配列のサイズよりファイルが大きい時は、whileでファイル終了まで繰り返しデータを読み込み)
  • try-with-resource構文を使っているので、明示的にcloseしなくて良い

Files.readAllBytesで一気に読み込み

java.nio.file.Files クラスに用意された、メモリにファイルのデータを一気に読み込む便利なメソッドです。ファイルサイズに対してメモリに余裕があるときに利用します。
public static byte[] readAllBytes(Path path) throws IOException

ByteBufferに読み込む

ByteBufferは、チャネルからファイルを読み込むときに読み込みデータを格納する先として利用できます。
(byte配列から生成することも可能です)

ByteBuffer buffer = ByteBuffer.allocate(1024);
try (var channel = Files.newByteChannel(path, StandardOpenOption.READ)) {
    while (true) {
        buffer.clear();
        if (channel.read(buffer) == -1) {
            break;
        }
        buffer.flip();
        // TODO: process data
    }
} catch(IOException ex) {
    System.err.println("ERROR: read " + ex);
}
  • データ格納用のByteBufferを用意
    • allocateはヒープ内にByteBufferを確保(ここではサイズ1024バイトとした)
  • FilesクラスのnewByteChannelメソッドにファイルパスと読み込み専用モードを指定してSeekableByteChannelを生成
  • ByteBufferのサイズを超えるファイルを読み込めるよう、ループでファイル終端が来るまで処理繰り返し
  • readした後はByteBufferをflipして先頭から読み始める

ファイルへ書き出し


約2ヶ月前に更新