Javaのファイル入出力関係のクラス/インタフェースについて整理する

More than 1 year has passed since last update.

はじめに

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というクラスが使われていた。
PathFileのAPIを使いやすく整理したり機能面での改良を施したりしたものである。
そのため、現在ではファイルパスを表すのにはFileではなくPathを使うことが推奨されている。

古いAPIにはFileしか引数にとらないものもある。
その場合に備え、PathFileは相互変換が可能になっている。
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.outSystem.err以外はもう使われない

OutputStreamを継承しているくせに何故か文字列も書き込めるクラス。
一応バイト列も書き込める。
おそらく入出力関係のAPIがきちんと設計される前に作られたクラスなのだと思われる。
そのような古いクラスであるため、このクラスのインスタンスを開発者が新たに作って使うことはもう無い。
しかし、System.outSystem.errはこのクラスのインスタンスであるため、それらを扱うときだけはこのクラスを使うことになる。
System.outSystem.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

Writerprintlnprintfなどの便利なメソッドを追加して使いやすくしたクラス。
通常は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) {
    // 例外処理
}

ただ、これらのコードの内部で使われている多くのクラス、そしてこれらのコードに至るまでに作られてきた多くの古いクラスについて知っておいた方が少し凝ったことをしようとしたときに応用が利くはずである。
この記事がそうした知識の整理に少しでも役に立てば幸いである。