はじめに
Javaのファイル入出力に関係するクラス/インタフェースはやたらと数が多い。
そこで、この記事ではファイル入出力に関係するクラス/インタフェースについて整理する。
今よく使われているものだけでなく、検索でよく引っかかるが古過ぎて今となってはもう使わないものについても説明する。
Javaはなまじ歴史の長い言語であるだけに古いクラス/インタフェースを使ったコードも検索でよく引っかかる。
そのとき古い書き方を古い書き方と認識できないと混乱するので、古いクラス/インタフェースについても知っておいた方が良いのである。
大分類
まずは以下の4+2クラス/インタフェースとそれぞれの役割を知ろう。
この4+2クラス/インタフェースさえ押さえておけば、大筋で理解を誤ることは無い。
基本的な4クラス
| バイト列 | 文字列 | |
|---|---|---|
| 読み込み | java.io.InputStream |
java.io.Reader |
| 書き込み | java.io.OutputStream |
java.io.Writer |
読み込みのためのクラスと書き込みのためのクラスとがあり、また、バイト列を読み書きするためのクラスと文字列を読み書きするためのクラスとがあるので、全部で2×2で4クラスである。
バイト列を読み書きするためのクラスでは、0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84, 0xE3, 0x81, 0x86のようなバイト列を読み書きすることができる。
文字列を読み書きするためのクラスでは、"あいう"のような文字列を読み書きすることができる。
補助的な2クラス/インタフェース
| ファイルパス | 文字コード |
|---|---|
java.nio.file.Path |
java.nio.charset.Charset |
Pathはファイルパスを表すインタフェースである。
あくまでファイルパスを表すだけであって、その場所にファイルが本当に存在するかどうかという情報は含まない。
Charsetは文字コードを表すクラスである。
文字列をファイルに保存する際、ファイル上には文字列をそのまま文字列として保存することはできず、バイト列に変換して保存しなければならない。
この変換の方式を定めたものが文字コードである。
UTF-8という文字コードでは"あいう"という文字列は0xE3, 0x81, 0x82, 0xE3, 0x81, 0x84, 0xE3, 0x81, 0x86というバイト列と相互変換できる。
また、MS932という文字コードでは、"あいう"という文字列は0x82, 0xA0, 0x82, 0xA2, 0x82, 0xA4というバイト列と相互変換できる。
ファイルにバイト列を読み書きする場合は、Pathの情報が必要になる。
ファイルに文字列を読み書きする場合は、Pathの情報とCharsetの情報が必要になる。
Path vs File
昔はjava.nio.file.Pathの代わりにjava.io.Fileというクラスが使われていた。
PathはFileのAPIを使いやすく整理したり機能面での改良を施したりしたものである。
そのため、現在ではファイルパスを表すのにはFileではなくPathを使うことが推奨されている。
古いAPIにはFileしか引数にとらないものもある。
その場合に備え、PathとFileは相互変換が可能になっている。
path.toFile()でPathからFileへの変換が、file.toPath()でFileからPathへの変換ができるようになっている。
小分類
多くのクラスが基本4クラスを継承することによって作られている。
今はもう基本的に使われていないものには(古い)と説明に書いておいた。
| 継承関係 | 種別 | 説明 | ||
|---|---|---|---|---|
Path |
インタフェース | ファイルパス | ||
File |
具象クラス | ファイルパス(古い) | ||
Charset |
抽象クラス | 文字コード | ||
InputStream |
抽象クラス | バイト列を読み込む | ||
FileInputStream |
具象クラス | ファイルからバイト列を読み込む(古い) | ||
Reader |
抽象クラス | 文字列を読み込む | ||
InputStreamReader |
具象クラス | バイト列から変換して文字列を読み込む | ||
FileReader |
具象クラス | ファイルから文字列を読み込む(古い) | ||
BufferedReader |
具象クラス | バッファリングによって文字列を効率よく読み込む | ||
LineNumberReader |
具象クラス |
BufferedReaderに行番号をカウントする機能を追加したもの |
||
OutputStream |
抽象クラス | バイト列を書き込む | ||
FileOutputStream |
具象クラス | ファイルにバイト列を書き込む(古い) | ||
FilterOutputStream |
具象クラス(ただしコンストラクタはprotected) |
バイト列を書き込む際何らかのフィルタ処理をする | ||
PrintStream |
具象クラス |
OutputStreamを継承しているくせに何故か文字列も書き込める(古い) |
||
Writer |
抽象クラス | 文字列を書き込む | ||
OutputStreamWriter |
具象クラス | バイト列に変換して文字列を書き込む | ||
FileWriter |
具象クラス | ファイルに文字列を書き込む(古い) | ||
BufferedWriter |
具象クラス | バッファリングによって文字列を効率的に書き込む | ||
PrintWriter |
具象クラス |
Writerに様々な機能を追加して使いやすくしたもの |
||
Path
- 完全修飾インタフェース名:
java.nio.file.Path - 継承関係:
Path
ファイルパスを表すインタフェース。
Paths.get("path/to/file")のようにして使う。
Paths.get("path/to/file").toFile()のようにすることでFileクラスへ変換可能。
File
- 完全修飾クラス名:
java.io.File - 継承関係:
File - 古いAPIに対応するためにときどき使われる
ファイルパスを表すクラス。
new File("path/to/file")のようにして使う。
Fileしか引数にとらないような古いAPIに対応するために、未だに使われることがある。
new File("path/to/file").toPath()のようにすることでPathインタフェースへ変換可能。
Charset
- 完全修飾クラス名:
java.nio.charset.Charset - 継承関係:
Charset
文字コードを表すクラス。
Charset.defaultCharset()でデフォルトの文字コードを取得できる。
これは、Unix系OSでは基本的にUTF-8になり、Windowsでは基本的にMS932(Shift_JISのWindows方言)になる。
UTF-8を決め打ちで取得するにはStandardCharsets.UTF_8とする。
MS932を決め打ちで取得するにはCharset.forName("MS932")とする。
InputStream
- 完全修飾クラス名:
java.io.InputStream - 継承関係:
InputStream
バイト列を読み込むためのクラス。
ここから派生したクラスをいくつか持つ。
FileInputStream
- 完全修飾クラス名:
java.io.FileInputStream - 継承関係:
InputStream>FileInputStream - 古いので今となってはもう使われない
ファイルからバイト列を読み込むためのクラス。
以下のようにして使う。
try (final InputStream is = new FileInputStream(new File("path/to/file"))) {
}
以下のようにすれば同じことができるため、今となってはもう使われない。
try (final InputStream is = Files.newInputStream(Paths.get("path/to/file"))) {
}
Reader
- 完全修飾クラス名:
java.io.Reader - 継承関係:
Reader
文字列を読み込むためのクラス。
ここから派生したクラスをいくつか持つ。
InputStreamReader
- 完全修飾クラス名:
java.io.InputStreamReader - 継承関係:
Reader>InputStreamReader
バイト列から変換して文字列を読み込むためのクラス。
以下のようにして使う。
try (final InputStream is = Files.newInputStream(Paths.get("path/to/file"));
final InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8)) {
}
FileReader
- 完全修飾クラス名:
java.io.FileReader - 継承関係:
Reader>InputStreamReader>FileReader - 古いので今となってはもう使われない
ファイルから文字列を読み込むためのクラス。
ファイルから文字列を読み込むための以下の記述が長いので省略できるように作られた。
try (final InputStream is = new FileInputStream(new File("path/to/file"));
final InputStreamReader isr = new InputStreamReader(is, Charset.defaultCharset())) {
}
以下のようにして使う。(デフォルト文字コードしか指定できない点に注意)
try (final InputStreamReader isr = new FileReader(new File("path/to/file"))) {
}
以下のようにすればほぼ同じことができるため、今となってはもう使われない。
try (final BufferedReader br = Files.newBufferedReader(Paths.get("path/to/file"), Charset.defaultCharset())) {
}
BufferedReader
- 完全修飾クラス名:
java.io.BufferedReader - 継承関係:
Reader>BufferedReader
バッファリングと呼ばれる技術を用いてReaderから効率的に文字列を読み込むためのクラス。
以下のようにして使う。(下は上の省略表記)
try (final InputStream is = Files.newInputStream(Paths.get("path/to/file"));
final InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
final BufferedReader br = new BufferedReader(isr)) {
}
try (final BufferedReader br = Files.newBufferedReader(Paths.get("path/to/file"), StandardCharsets.UTF_8)) {
}
LineNumberReader
- 完全修飾クラス名:
java.io.LineNumberReader - 継承関係:
Reader>BufferedReader>LineNumberReader
BufferedReaderに行番号をカウントする機能を追加したクラス。
以下のようにして使う。
try (final InputStream is = Files.newInputStream(Paths.get("path/to/file"));
final InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
final LineNumberReader lnr = new LineNumberReader(isr)) {
}
行番号を表示する例は以下。
/*
1: 1行目
2: 2行目
3: 3行目
…
のような出力がされる
*/
try (final InputStream is = Files.newInputStream(Paths.get("path/to/file"));
final InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
final LineNumberReader lnr = new LineNumberReader(isr)) {
String line;
while ((line = lnr.readLine()) != null) {
System.out.printf("%d: %s%n", lnr.getLineNumber(), line);
}
}
OutputStream
- 完全修飾クラス名:
java.io.OutputStream - 継承関係:
OutputStream
バイト列を書き込むためのクラス。
ここから派生したクラスをいくつか持つ。
FileOutputStream
- 完全修飾クラス名:
java.io.FileOutputStream - 継承関係:
OutputStream>FileOutputStream - 古いので今となってはもう使われない
ファイルにバイト列を書き込むためのクラス。
以下のようにして使う。
try (final OutputStream os = new FileOutputStream(new File("path/to/file"))) {
}
以下のようにすれば同じことができるため、今となってはもう使われない。
try (final OutputStream os = Files.newOutputStream(Paths.get("path/to/file"))) {
}
FilterOutputStream
- 完全修飾クラス名:
java.io.FilterOutputStream - 継承関係:
OutputStream>FilterOutputStream
バイト列を書き込む際何らかのフィルタ処理をするクラスはこのクラスを継承する。
普段はほぼ使わない。
後述のPrintStreamの親クラスなので紹介した。
PrintStream
- 完全修飾クラス名:
java.io.PrintStream - 継承関係:
OutputStream>FilterOutputStream>PrintStream - 古いので
System.outとSystem.err以外はもう使われない
OutputStreamを継承しているくせに何故か文字列も書き込めるクラス。
一応バイト列も書き込める。
おそらく入出力関係のAPIがきちんと設計される前に作られたクラスなのだと思われる。
そのような古いクラスであるため、このクラスのインスタンスを開発者が新たに作って使うことはもう無い。
しかし、System.outとSystem.errはこのクラスのインスタンスであるため、それらを扱うときだけはこのクラスを使うことになる。
System.outとSystem.errに文字列を書き込むと、デフォルト文字コードで変換されたバイト列がストリームに書き込まれる。
Writer
- 完全修飾クラス名:
java.io.Writer - 継承関係:
Writer
文字列を書き込むためのクラス。
ここから派生したクラスをいくつか持つ。
OutputStreamWriter
- 完全修飾クラス名:
java.io.OutputStreamWriter - 継承関係:
Writer>OutputStreamWriter
バイト列に変換して文字列を書き込むためのクラス。
以下のようにして使う。
try (final OutputStream os = Files.newOutputStream(Paths.get("path/to/file"));
final OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8)) {
}
FileWriter
- 完全修飾クラス名:
java.io.FileWriter - 継承関係:
Writer>OutputStreamWriter>FileWriter - 古いので今となってはもう使われない
ファイルに文字列を書き込むためのクラス。
ファイルに文字列を書き込むための以下の記述が長いので省略できるように作られた。
try (final OutputStream os = new FileOutputStream(new File("path/to/file"));
final OutputStreamWriter osw = new OutputStreamWriter(osw, Charset.defaultCharset())) {
}
以下のようにして使う。(デフォルト文字コードしか指定できない点に注意)
try (final OutputStreamWriter osw = new FileWriter(new File("path/to/file"))) {
}
以下のようにすればほぼ同じことができるため、今となってはもう使われない。
try (final BufferedWriter bw = Files.newBufferedWriter(Paths.get("path/to/file"), Charset.defaultCharset())) {
}
BufferedWriter
- 完全修飾クラス名:
java.io.BufferedWriter - 継承関係:
Writer>BufferedWriter
バッファリングと呼ばれる技術を用いてWriterに効率的に文字列を書き込むためのクラス。
以下のようにして使う。(下は上の省略表記)
try (final OutputStream os = Files.newOutputStream(Paths.get("path/to/file"));
final OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8);
final BufferedWriter bw = new BufferedWriter(osw)) {
}
try (final BufferedWriter bw = Files.newBufferedWriter(Paths.get("path/to/file"), StandardCharsets.UTF_8)) {
}
PrintWriter
- 完全修飾クラス名:
java.io.PrintWriter - 継承関係:
Writer>PrintWriter
Writerにprintlnやprintfなどの便利なメソッドを追加して使いやすくしたクラス。
通常はBufferedWriterと組み合わせて以下のようにして使う。
PrintWriterの第二引数はprintlnメソッド、printfメソッド、formatメソッドの実行後に第一引数のWriterをflushするか否か。
try (final BufferedWriter bw = Files.newBufferedWriter(Paths.get("path/to/file"), StandardCharsets.UTF_8);
final PrintWriter pw = new PrintWriter(bw, false)) {
}
上の表記は以下のような省略表記が可能。
try (final PrintWriter pw = new PrintWriter(new File("path/to/file"), StandardCharsets.UTF_8)) {
}
コンストラクタ/ファクトリメソッドのまとめ
上で説明を省いたコンストラクタ/ファクトリメソッドも一部ある。
似たコンストラクタ/ファクトリメソッドを一まとめにするため、他言語におけるデフォルト引数のような書き方をしているものが一部ある。
| 継承関係 | コンストラクタ/ファクトリメソッド | 備考 | ||
|---|---|---|---|---|
Path |
Paths.get(String filePath) |
|||
File |
new File(String filePath) |
|||
Charset |
Charset.defaultCharset() |
デフォルト文字コードを取得する | ||
StandardCharsets.UTF_8 |
UTF-8 | |||
Charset.forName("MS932") |
MS932 | |||
InputStream |
Files.newInputStream(Path filePath, OpenOption... options = new OpenOption[]{StandardOpenOption.READ}) |
|||
FileInputStream |
new FileInputStream(String filePath) |
|||
new FileInputStream(File filePath) | ||||
Reader |
(特になし) | |||
InputStreamReader |
new InputStreamReader(InputStream stream, String charsetName) |
|||
new InputStreamReader(InputStream stream, Charset charset = Charset.defaultCharset()) | ||||
FileReader |
new FileReader(String filePath) |
new InputStreamReader(new FileInputStream(filePath, Charset.defaultCharset()))と等価 |
||
new FileReader(File filePath) | ||||
BufferedReader |
new BufferedReader(Reader reader, int bufferSize = 8192) |
|||
Files.newBufferedReader(Path filePath, Charset charset = StandardCharsets.UTF_8) |
new BufferedReader(new InputStreamReader(Files.newInputStream(filePath), charset), 8192)と等価 |
|||
LineNumberReader |
new LineNumberReader(Reader reader, int bufferSize = 8192) |
|||
OutputStream |
Files.newOutputStream(Path filePath, OpenOption... options = new OpenOption[]{StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE}) |
|||
FileOutputStream |
new FileOutputStream(String filePath, boolean append = false) |
|||
new FileOutputStream(File filePath, boolean append = false) | ||||
FilterOutputStream |
new FilterOutputStream(OutputStream stream) |
|||
PrintStream |
new PrintStream(OutputStream stream, boolean autoFlush = false, String charsetName = Charset.defaultCharset().name()) |
|||
new PrintStream(String filePath, String charsetName = Charset.defaultCharset().name()) |
new PrintStream(new FileOutputStream(filePath, false), false, charsetName);と等価 |
|||
new PrintStream(File filePath, String charsetName = Charset.defaultCharset().name()) | ||||
Writer |
(特になし) | |||
OutputStreamWriter |
new OutputStreamWriter(OutputStream stream, String charsetName) |
|||
new OutputStreamWriter(OutputStream stream, Charset charset = Charset.defaultCharset()) | ||||
FileWriter |
new FileWriter(String filePath, boolean append = false) |
new OutputStreamWriter(new FileOutputStream(filePath, append), Charset.defaultCharset());と等価 |
||
new FileWriter(File filePath, boolean append = false) | ||||
BufferedWriter |
new BufferedWriter(Writer writer, int bufferSize = 8192) |
|||
Files.newBufferedWriter(Path filePath, Charset charset = StandardCharsets.UTF_8, OpenOption... options = new OpenOption[]{StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE}) |
new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(filePath, options), charset), 8192)と等価 |
|||
PrintWriter |
new PrintWriter(Writer writer, boolean autoFlush = false) |
|||
new PrintWriter(OutputStream stream, boolean autoFlush = false) |
new PrintWriter(new BufferedWriter(new OutputStreamWriter(stream, Charset.defaultCharset()), 8192), autoFlush)と等価 |
|||
new PrintWriter(String filePath, String charsetName = Charset.standardCharset().name()) |
new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath, false), charsetName), 8192), false)と等価 |
|||
new PrintWriter(File filePath, String charsetName = Charset.standardCharset().name()) | ||||
結局
結局のところ、今は下の3つの書き方だけ覚えていれば基本的なケースには対応できるんじゃないかと思われる。
try (final Stream<String> lines = Files.lines(Paths.get("path/to/file"), StandardCharsets.UTF_8)) {
lines.forEachOrdered(line -> {
// 入力処理
});
} catch (final IOException e) {
// 例外処理
}
try (final BufferedWriter bw = Files.newBufferedWriter(Paths.get("path/to/file"), StandardCharsets.UTF_8);
final PrintWriter pw = new PrintWriter(bw, true)) {
pw.println("出力処理(新規書き込み)");
} catch (final IOException e) {
// 例外処理
}
try (final BufferedWriter bw = Files.newBufferedWriter(Paths.get("path/to/file"), StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
final PrintWriter pw = new PrintWriter(bw, true)) {
pw.println("出力処理(追加書き込み)");
} catch (final IOException e) {
// 例外処理
}
ただ、これらのコードの内部で使われている多くのクラス、そしてこれらのコードに至るまでに作られてきた多くの古いクラスについて知っておいた方が少し凝ったことをしようとしたときに応用が利くはずである。
この記事がそうした知識の整理に少しでも役に立てば幸いである。