入門Javaのファイル操作
環境
JDK 25
Java 8以降であれば、多少バージョンが違っていても同じだと思います。
主要なクラス・インタフェース
ファイル操作に関するクラス・インタフェースの多くはjava.nio.file
パッケージに属しています。
これらのクラス・インタフェースを総称して「NIO.2」と呼ばれます。
Path
インタフェース
Path
インタフェースは、名前の通りファイルやディレクトリのパスを表します。Path.of("ファイルやディレクトリのパス")
でPath
オブジェクトを生成できます。
Path targetFile = Path.of("/tmp/test/sample.txt");
Files
クラス
ファイルまたはディレクトリに関する操作(読み込みや書き込みなど)はFiles
クラスのstaticメソッドとしてまとまっています。
いろんなメソッドが用意されているので、ぜひJavadocを一通り眺めてみてください!
文字コード
文字コードはCharset
抽象クラスが表します。Charset.forName("文字コード名")
で、指定した文字コードを表すCharset
オブジェクトを生成できます。
Charset cs = Charset.forName("MS932");
Charset cs = Charset.forName("EUC-JP");
文字コード名が正しくない場合は
UnsupportedCharsetException
がスローされます。
UTF-8やUTF-16などを指定する場合はStandardCharsets
クラスの定数を利用できます。
Charset cs = StandardCharsets.UTF_8;
Charset cs = StandardCharsets.UTF_16;
テキストファイルの読み込み
あいうえお
かきくけこ
さしすせそ
1行ずつ読み込んで加工する
Files.lines()
メソッドを利用すると、各行を要素に持つStream<String>
を取得できます。
今回は各行を[]
で囲うように変換します。
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
Path targetFile = Path.of("./test/sample.txt");
// 処理終了後に自動的にStreamがclose()されるようにtry-with-resourcesを利用
try (Stream<String> stream = Files.lines(targetFile, StandardCharsets.UTF_8)) {
// 各行を[]で囲って、標準出力に出力
stream.map(line -> "[" + line + "]")
.forEach(line -> System.out.println(line));
} catch (IOException e) {
// 業務コードでe.printStackTrace()を使うことはめったにありません。
// 業務では各プロジェクトのルールに合わせて、例外の再スローやログの出力などを行ってください。
e.printStackTrace();
}
}
}
[あいうえお]
[かきくけこ]
[さしすせそ]
Stream<String>
に対してtoList()
すると、ファイルの内容を全て保持するList<String>
を作成してしまい、それによってメモリを圧迫してOutOfMemoryError
を引き起こす可能性があります。
上記のコードのようにStream<String>
のまま扱うと、ファイルの1行ずつがメモリに乗る+必要に応じてガベージコレクションが実行されるため、メモリを圧迫しません。
ガベージコレクションについては、👇️の記事を読んでみてください。
引数が
Path
のみで文字コードを指定しないlines()
メソッドもあります(文字コードはUTF-8になります)。実際にコードを書く場合は、明示的に文字コードを指定するlines()
を使ったほうがいいでしょう。
何行か飛ばす、指定した行数だけ読み込む
サイズが大きいファイルを読み込んでList<String>
を作りたい場合は、何回かに分けてファイルを読むことをおすすめします。
Stream
のskip()
メソッドで飛ばす行数を、limit()
メソッドで読み込む行数を指定できます。
try (Stream<String> stream = Files.lines(targetFile, StandardCharsets.UTF_8)) {
// 1000行を持つリストになる
List<String> list = stream.map(line -> "[" + line + "]")
.skip(3000) // 3000行飛ばす
.limit(1000) // 1000行読み込む
.toList();
全行を保持するListを生成する
テキストファイルのサイズが実行環境のメモリ量より十分に小さい場合、および将来もそのテキストファイルが大きくならないことが分かっている場合は、Files.readAllLines()
を利用してファイルの内容を全て保持するList<String>
に変換できます。
今は小さなファイルであっても、時が経つにつれてデータ量が多くなり、ファイルが大きくなることがあります。しっかりと検討してください。
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class Main {
public static void main(String[] args) {
Path targetFile = Path.of("/tmp/test/sample.txt");
try {
// 1要素=1行であるListを生成する
List<String> lines = Files.readAllLines(targetFile, StandardCharsets.UTF_8);
for (String line : lines) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
あいうえお
かきくけこ
さしすせそ
ファイルの全内容を1つの文字列として取得する
Files.readString()
を利用します。
このメソッドも、ファイルの全内容を一気に読み込むので注意してください。
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class Main {
public static void main(String[] args) {
Path targetFile = Path.of("/tmp/test/sample.txt");
try {
String content = Files.readString(targetFile, StandardCharsets.UTF_8);
System.out.println(content);
} catch (IOException e) {
e.printStackTrace();
}
}
}
あいうえお
かきくけこ
さしすせそ
テキストファイルの書き込み
あいうえお
かきくけこ
さしすせそ
StandardOpenOption
StandardOpenOption
列挙型は、ファイルの開き方および作成方法を表します。
後述するFiles
クラスの各メソッドでは、最後の引数にStandardOpenOption
を指定します(可変長引数なので複数指定可能)。
-
CREATE
- ファイルが存在しない場合は新規作成します。
- ファイルが存在する場合は何もしません。
-
CREATE_NEW
- ファイルが存在しない場合は新規作成します。
- ファイルが存在する場合は例外がスローされます。
-
APPEND
- 既存の内容の最後に追記します。
-
TRUNCATE_EXISTING
- 既存の内容を上書きします。
書き込みの実行
書き込みにはBufferedWriter
を利用します。
Files.write()
でも書き込みを行えます。しかしこのメソッドは改行コードがOSに依存します(Mac/LinuxではLF、WindowsではCRLF)。改行コードは明示的に指定したほうが良いので、BufferedWriter
をおすすめします。
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.List;
public class Main {
public static void main(String[] args) {
Path targetFile = Path.of("/tmp/test/sample.txt");
try (BufferedWriter bufferedWriter = Files.newBufferedWriter(targetFile,
StandardCharsets.UTF_8,
// ファイルが存在しない場合は新規作成
StandardOpenOption.CREATE,
// ファイルが存在する場合は追記
//(上書きの場合はTRUNCATE_EXISTINGに変更する)
StandardOpenOption.APPEND
)) {
List<String> lines = List.of("たちつてと", "なにぬねの", "はひふへほ");
for (String line : lines) {
bufferedWriter.write(line);
bufferedWriter.write("\r\n"); // 改行コードにCRLFを指定(LFの場合は"\n")
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
あいうえお
かきくけこ
さしすせそ
たちつてと
なにぬねの
はひふへほ
文字コードAで読んで、文字コードBで書き出す
public class Main {
public static void main() {
// 読み込み対象(Windows-31J)
Path sourceFile = Path.of("./w31j.txt");
// 書き込み対象(EUC-JP)
Path destFile = Path.of("./euc.txt")
try (Stream<String> inStream = Files.lines(
sourceFile,
// 読み込む文字コードを指定
Charset.forName("Windows-31J"));
BufferedWriter bufferedWriter = Files.newBufferedWriter(
destFile,
// 書き込む文字コードを指定
Charset.forName("EUC-JP"),
StandardOpenOption.CREATE,
StandardOpenOption.APPEND)
) {
inStream.forEach(line -> {
try {
bufferedWriter.write(line);
bufferedWriter.write("\n");
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
Discussion