Java I/O（入出力）操作

Java 11 Gold 取得に向けた学習記録

# IO（入出力）操作

# `java.io`パッケージ

## `File`

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/io/File.html

```java:File（ファイル）
File file = new File("a.txt");
```

```java:File（ディレクトリ）
File dir = new File("a");

// カレントディレクトリ
File dir = new File(".");
```

### `exists()`

```java:exists()
File file = new File("a.txt");
System.out.println(file.exists());
// >> false
```

### `createNewFile()`

```java:createNewFile()
try {
    File file = new File("a.txt");
    file.createNewFile();
} catch (IOException e) {
}    
```

### `mkdirs()`

```java:mkdirs()
File dir = new File("a");
dir.mkdirs();
```

### `isDirectory()`

```java:isDirectory()
File dir = new File("a");

System.out.println(dir.isDirectory());
// >> false

dir.mkdirs();

System.out.println(dir.isDirectory());
// >> true
```

### `getAbsoluteFile()`

`File`オブジェクトは本質的には「パス」を扱うクラスなので、実際のファイルの有無は関係ない

```java:getAbsoluteFile()
File file = new File("a.txt");

System.out.println(file.getAbsoluteFile());
// >> /Users/asahinakei/IdeaProjects/Qiita/a.txt
```

### `list()`

返される型が`String[]`。

```java:list()
File dir = new File(".");
String[] files = dir.list(); // String[]

for (String file : files) {
    System.out.println(file);
}
// >> a
// >> gradle
// >> gradlew
// >> build.gradle
// >> .gradle
// >> build
// >> gradlew.bat
// >> settings.gradle
// >> .idea
// >> src
```

### `listFiles()`

返される型が`File[]`。

```java:listFiles()
File dir = new File(".");
File[] files = dir.listFiles(); // File[]

for (File file : files) {
    System.out.println(file.getName());
}
// >> a
// >> gradle
// >> gradlew
// >> build.gradle
// >> .gradle
// >> build
// >> gradlew.bat
// >> settings.gradle
// >> .idea
// >> src
```

# `java.nio.file`パッケージ

nio = New IO

[`java.io.File`](#javaiofile)クラスには、UNIX系OSにおけるシンボリックリンクやファイル属性が適切に扱えないなどの課題があったことから、Java 7では`java.nio.file`パッケージが導入された。

`java.nio.file`パッケージに含まれる`Path`インターフェースと`Files`クラスを利用することで、`java.io.File`クラスが適切に扱うことのできなかったファイル属性を簡単に扱ったり、ファイル削除、移動などの高度な操作を簡単に行うことができる。

- `Path` : インターフェース
- `Paths` : `Path`インターフェースの実装クラスを生成する factory メソッドが定義されたクラス
- `Files` : `java.io.File`クラスの改良版

https://qiita.com/AsahinaKei/items/3a1f17b728769bab95b7

## `Path` / `Paths`

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/nio/file/Path.html

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/nio/file/Paths.html

```java:Pathインターフェースの実装を取得する
Path path = Paths.get("a.txt");
```

パスを可変長引数によって表現することができる

```java:パスの表現
Path path = Paths.get("aDir", "bDir", "c.txt");
```

`Path`は`File`へ変換することができる。

```java:Path ▶️ File
Path path = Paths.get("a.txt");

File file = path.toFile();
```

`Path`インターフェースの実装は、`java.io.File`クラスからも取得することができる。

```java:File ▶️ Path
File file = new File("a.txt");

Path path = file.toPath();
```

## `Files`

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/nio/file/Files.html

`Files`クラスは多くの場合、`Files`クラスの`static`メソッドに対して`Path`インターフェースの実装を渡すと言う形で利用する。

### `exists()`

```java:exists()
Path path = Paths.get("a.txt");
System.out.println(Files.exists(path));
// >> false
```

### `createFile()`

作成しようとしているファイルがすでに存在する場合、`FileAlreadyExistsException`が発生する。

```java:createFile()
try {
    Path path = Paths.get("a.txt");
    Files.createFile(path);
} catch (IOException e) {
}
```

### `delete()`

削除しようとしているファイルが存在しない場合、`NoSuchFileException`が発生する。

```java:delete()
try {
    Path path = Paths.get("a.txt");
    Files.delete(path);
} catch (IOException e) {
}
```

### `getPosixFilePermissions()`

POSIX = Portable Operation System Interface for UNIX

```java:getPosixFilePermissions()
try {
    Path path = Paths.get("a.txt");
    System.out.println(Files.getPosixFilePermissions(path));
} catch (IOException e) {
}

// >> [OWNER_READ, OTHERS_READ, OWNER_WRITE, GROUP_READ]
```

### `createDirectory()`

親ディレクトリの存在を前提にしているため、ディレクトリ`a`が存在しない場合、`NoSuchFileException`が発生する。

また作成対象のディレクトリがまだ存在していないことを前提としているため、`c`ディレクトリがすでに存在した場合にも、`FileAlreadyExistsException`が発生する。

```java:createDirectory()
try {
    Path path = Paths.get("a", "b", "c");
    Files.createDirectory(path);
} catch (IOException e) {
}
```

### `createDirectories()`

親ディレクトリ、作成対象ディレクトリの存在を前提としていないため、指定パスの中で存在しないディレクトリがあったとしても、親ディレクトリも含めて作成することができる。

```java:createDirectories()
try {
    Path path = Paths.get("a", "b", "c");
    Files.createDirectories(path);
} catch (IOException e) {
}
```

### `resolve()`

`Path`を連結することで、複数のファイルが同じディレクトリにある場合などに共通パス部分とファイル固有のパス部分を分割することができる。

```java:resolve()
Path dir = Paths.get("a", "b", "c");

Path file1 = dir.resolve(Paths.get("d.txt"));
Path file2 = dir.resolve(Paths.get("e.txt"));
```

### `copy()`

```java:copy()
try {
    Path original = Paths.get("a.txt");
    Path copy = Paths.get("a_copy.txt");
    Files.copy(original, copy);
} catch (IOException e) {
}
```

### `move()`

```java:move()
try {
    Path source = Paths.get("a" + "b" + "c.txt");
    Path destination = Paths.get("a" + "c.txt");
    Files.move(source, destination);
} catch (IOException e) {
}
```

### `walk()`

ファイルツリーを再帰的にトラバース（traverse=横切る）するための便利メソッド。

```java:walk()
Path dir = Paths.get("a");
try (Stream<Path> stream = Files.walk(dir)) {
    stream.forEach((path) -> {
        System.out.println(path.getFileName());
    });
} catch (IOException e) {
}
```

### `walkFileTree()`

ファイルツリーを再帰的にトラバースする際に、トラバースを細かく制御することができる高機能なメソッド。

`FileVisitor`インターフェースの実装を渡す必要がある。

#### `FileVisitor`

`FileVisitor`インターフェースは、`walkFileTree()`への引数として利用され、以下の4つの抽象メソッドが定義されている。それぞれのメソッドは各々決められたタイミングで`Files`によって実行される。

事前にコールバック関数を描くメソッドに仕込んでおき、コールバックのセットを`walkFileTree()`へ渡すイメージ。

```java:FileVisitor
// 一部省略しています
public interface FileVisitor<T> {
    // ディレクトリに入るとき
    FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs);

    // ファイルに到達したとき
    FileVisitResult visitFile(T file, BasicFileAttributes attrs);

    // ディレクトリから出るとき
    FileVisitResult postVisitDirectory(T dir, IOException exc);

    // 処理に失敗したとき
    FileVisitResult visitFileFailed(T file, IOException exc);
}
```

#### `FileVisitResult`

処理終了後の動作はenumの`FileVisitResult`を返すことで制御ができる。

```java:FileVisitResult
public enum FileVisitResult {
    // トラバースを続行する
    CONTINUE,

    // トラバースを終了する
    TERMINATE,
    
    // このファイルまたはディレクトリの兄弟を visit せずにトラバースを続行する
    SKIP_SIBLINGS,
    
    // このディレクトリ内を visit せずにトラバースを続行する
    SKIP_SUBTREE,
}
```

#### `SimpleFileVisitor`

`FileVisitor`インターフェースには4つのメソッド定義があるため、本来は4つとも実装しなければならないが`SimpleFileVisitor`クラスを使用することで、その手間を省くことができる。

`SimpleFileVisitor`クラスはすでに`FileVisitor`インターフェースを実装しているため、自分がカスタマイズしたいメソッドのみオーバーライドすることができる。

```java
try {
    Path dir = Paths.get("a");
    Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
        // visitFile()だけをオーバーライドする
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
            System.out.println("hello world");
            return FileVisitResult.CONTINUE;
        }
    });
} catch (IOException e) {
}
```



# ストリーム

`java.io`パッケージから提供されるクラスは、入出力を「文字」として扱うのか「バイナリデータ（`010111011`みたいな2進数表現）」として扱うのかによって使い分ける。

- 文字データを扱うストリーム
    - **input** : `Reader` ▶️ [`FileReader`](#filereader) / [`BufferedReader`](#bufferedreader)
    - **output** : `Writer` ▶️ [`FileWriter`](#filewriter) / [`BufferedWriter`](#bufferedwriter)
- バイナリデータを扱うストリーム
    - **input** : `InputStream` ▶️ [`FileInputStream`](#fileinputstream--bufferedinputstream) / [`BufferedInputStream`](##fileinputstream--bufferedinputstream)
    - **output** : `OutputStream` ▶️ [`FileOutputStream`](#fileoutputstream--bufferedoutputstream) / [`BufferedOutputStream`](##fileoutputstream--bufferedoutputstream)

ここでの`InputStream`と`OutputStream`はJava 1（JDK 1.0）のもので、Java 8 から導入されたストリームAPIとは別のものであることに注意する。

https://qiita.com/AsahinaKei/items/7ba294a97d2da67ddf2a

また`AutoCloseable`インターフェースや`Closeable`インターフェースを実装したクラスをインスタンス化する場合、`try-finally`構文で明示的に`close()`するか、もしくは`try-with-resource`構文で自動的に`close()`する必要があるため注意する。

:::note info
`AutoCloseable`、`Closeable`インターフェースの実装クラスをインスタンス化する場合、クローズ処理が必要
:::

# 文字データを扱うストリーム

## `FileReader`

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/io/FileReader.html

```java:FileReader
// close が必要
try (FileReader reader = new FileReader("a.txt")) {

} catch (IOException e) {
}
```

### `read()`

`read()` はファイルの終端に到達すると `-1` を返す

```java:read()
try (FileReader reader = new FileReader("a.txt")) {
    int i = 0;
    // read() はファイルの終端に到達すると -1 を返す
    while ((i = reader.read()) != -1) {
        char c = (char) i;
        System.out.print(c);
    }
} catch (IOException e) {
}

// >> hello world
// >> hello world
// >> hello world
// >> hello world
// >> hello world
```

## `BufferedReader`

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/io/BufferedReader.html

前述の[`read()`](#read)では、1文字づつファイルへのアクセスを行っている。一般的に、IO（Input/Output）処理はプログラムの実行と比較して大変重たい処理になるため、バッファを使用してファイルシステムへのアクセスを最小限に抑える方が望ましい。

バッファを使用することで入出力内容を一時的にバッファに貯めておくことができるため、ファイルシステムへのアクセス回数が大幅に減りパフォーマンスが向上する。

### `java.io`パッケージの場合

```java:BufferedReader (java.io.File)
// close が必要
try (FileReader fileReader = new FileReader("a.txt");
     // 生成時に FileReader を渡す必要がある
     BufferedReader bufferReader = new BufferedReader(fileReader)) {
    
} catch (IOException e) {
}
```

`BufferedReader`クラスは、コンストラクが`Reader`クラスを受け取るようになっている。また、`BufferedReader`クラスは`Reader`クラスを継承しているため、どちらも`Reader`型として扱うことができる。コンストラクタが受け取った`Reader`クラスは内部で使用されていて、一部のメソッドがオーバーライドされることで、`Reader`クラスの機能が拡張されている。このようにオブジェクトを別のオブジェクトでラップすることによって機能を拡張するデザインパターンをDecoratorパターンと言う。

https://qiita.com/AsahinaKei/items/6fb3e76099514fb1757a

### `java.nio.file`パッケージの場合

```java:BufferedReader (java.nio.file.Files)
Path path = Paths.get("a.txt");
try (BufferedReader bufferReader = Files.newBufferedReader(path)) {
    
} catch (IOException e) {
}
```

### `readLine()` 

`readLine()` はファイルの終端に到達すると `null` を返す

```java:readLine()
try (FileReader fileReader = new FileReader("a.txt");
     BufferedReader bufferReader = new BufferedReader(fileReader)) {
    String line = null;
    // readLine() はファイルの終端に到達すると null を返す
    while ((line = bufferReader.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
}

// >> hello world
// >> hello world
// >> hello world
// >> hello world
// >> hello world
```


## `FileWriter`

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/io/FileWriter.html

`FileWriter`をインスタンス化する際、指定ファイルが存在しなければその時点でファイルが作成される。

```java:FileWriter
// ファイルが存在しない場合、この時点で新規作成される（close が必要）
try (FileWriter writer = new FileWriter("a.txt")) {
    
} catch (IOException e) {
}
```

### `write()`

`write()`はデフォルトでは上書きモードで書き込みを行う。

```java:write() 上書きモード
// デフォルトでは上書きモード
try (FileWriter writer = new FileWriter("a.txt")) {
    writer.write("hello world");
} catch (IOException e) {
}
```

追記モードで書き込みを行いたい場合は、`FileWriter`のインスタンス化の際に設定を行う。

```java:write() 追記モード
// 追記モード
try (FileWriter writer = new FileWriter("a.txt", true)) {
    writer.write("hello world");
} catch (IOException e) {
}
```

## `BufferedWriter`

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/io/BufferedWriter.html

### `java.io`パッケージの場合

```java:BufferedWriter (java.io)
try (FileWriter fileWriter = new FileWriter("a.txt");
     // 生成時に FileWriter を渡す必要がある（close が必要）
     BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) {
    
} catch (IOException e) {
}
```

### `java.nio.file`パッケージの場合

```java:BufferedWriter (java.nio.file) 上書きモード
Path path = Paths.get("a.txt");
try (BufferedWriter bufferWriter = Files.newBufferedWriter(path)) {
    
} catch (IOException e) {
}
```

```java:BufferedWriter (java.nio.file) 追記モード
Path path = Paths.get("a.txt");
try (BufferedWriter bufferWriter = Files.newBufferedWriter(path, StandardOpenOption.APPEND)) {

} catch (IOException e) {
}
```

### `write()`

```java:write()
try (FileWriter fileWriter = new FileWriter("a.txt");
     BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) {
    for (int i = 0; i < 5; i++) {
        bufferedWriter.write("hello world");
        bufferedWriter.newLine(); // 改行
    }
    // メモリ（上のバッファ）とファイルが同期される
    bufferedWriter.flush();
} catch (IOException e) {
}
```


# バイナリデータを扱うストリーム

## `FileInputStream` / `BufferedInputStream`

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/io/FileInputStream.html

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/io/BufferedInputStream.html

```java:FileInputStream / BufferedInputStream
// close が必要
try (FileInputStream fileInputStream = new FileInputStream("a.png");
     // 生成時に fileInputStream を渡す必要がある
     BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream)) {
    
} catch (IOException e) {
}
```

## `FileOutputStream` / `BufferedOutputStream`

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/io/FileOutputStream.html

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/io/BufferedOutputStream.html

```java:FileOutputStream / BufferedOutputStream
// close が必要
try (FileOutputStream fileOutputStream = new FileOutputStream("a.png");
     // 生成時に fileOutputStream を渡す必要がある
     BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream)) {
    
} catch (IOException e) {
}
```

# コンソール

コンソールからの入力を受け付けたい場合、`java.lang.System`クラスを使用する。ただし、この方法は後述する[`java.io.Console`](#javaioconsole)クラスによってさらに簡略化することができる。


## `System`を使う方法

`System`クラスを利用する場合、バイナリデータを扱うストリームを文字を扱うストリームに変換する処理が必要になる。

この変換は**ブリッジ**と呼ばれ、`java.io.InputStreamReader`クラスによって行うことができる。`InputStreamReader`クラスはバイナリデータ（`InputStream`）を文字エンコーディングを使用して文字データ（`Reader`）にデコードすることができる。

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/io/InputStreamReader.html

この辺りが非常にややこしいので再掲。
> - 文字データを扱うストリーム
    - **input** : `Reader` ▶️ [`FileReader`](#filereader) / [`BufferedReader`](#bufferedreader)
    - **output** : `Writer` ▶️ [`FileWriter`](#filewriter) / [`BufferedWriter`](#bufferedwriter)
> - バイナリデータを扱うストリーム
    - **input** : `InputStream` ▶️ [`FileInputStream`](#fileinputstream--bufferedinputstream) / [`BufferedInputStream`](##fileinputstream--bufferedinputstream)
    - **output** : `OutputStream` ▶️ [`FileOutputStream`](#fileoutputstream--bufferedoutputstream) / [`BufferedOutputStream`](##fileoutputstream--bufferedoutputstream)


```java:InputStreamReader
// 通常　 InputStream ▶️ BufferedInputStream
// 変換時 InputStream ▶️ 【InputStreamReader】 ▶️ BufferedReader
try (InputStream inputStream = System.in;
     InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
     BufferedReader bufferReader = new BufferedReader(inputStreamReader)) {
    String input = bufferReader.readLine();
    System.out.println(input);
} catch (IOException e) {
}

// コンソールが入力を待つ
// 「hello world」とコンソールへ入力してEnter
// >> hello world
```

## `Console`を使う方法

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/io/Console.html

`Console`はコンソールからの入出力をサポートするクラスなので、IDEのツールからプログラムを実行した場合には`System.console()`が`null`を返すことがあるため注意。

```java:Console
Console console = System.console();
if (console != null) {
    String input = console.readLine();
    System.out.println(input);
}

// コンソールが入力を待つ
// 「hello world」とコンソールへ入力してEnter
// >> hello world
```

## `Scanner`を使う場合

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/Scanner.html

標準入力（`System.in`）からのデータの読み取りをサポートするクラス。

```java:Scanner
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
System.out.println(input);

// コンソールが入力を待つ
// 「hello world」とコンソールへ入力してEnter
// >> hello world
```

# `FileReader` vs `InputStream`

テキストファイルを読み込む場合、`FileReader` を使用すべきなのか、`InputStream` を使用すべきなのか。

**`FileReader`**
記述が簡単な一方で、プラットフォームのデフォルトエンコーディングを使用するため、特定のエンコーディングを指定する場面に不向き。

**`InputStream`**
エンコーディングを指定して読み取りを行うことができる。



# 参考
・<a href="https://amzn.asia/d/gkx4D5q" target="_blank" rel="noopener noreferrer">徹底攻略Java SE 11 Gold問題集[1Z0-816]対応</a>