【Java発展学習6日目】テキストファイルとバイナリファイルについて

# ファイルへのアクセス方式
`ファイル`への`アクセス`方式は以下の2通り。

|方式|メリット|デメリット|
|:--|:--|:--|
|`ランダムアクセス(random access)`|・高速アクセス|・読み書き速度が低速<br>・大容量ファイルの操作に不向き|
|`シーケンシャルアクセス(sequential access)`|・読み書き速度が高速<br>・大容量のファイル操作も可能|・低速アクセス|

なお、`ランダムアクセス`方式を用いる場合は`RandomAccessFile`クラスを用いて`バイトデータ`を読み書きしたり、
`MappedByteBuffer`クラスを用いて`バイト配列`を読み書きすることができる。

# ファイルの分類
`ファイル`は`テキストファイル(text file)`と`バイナリファイル(binary file)`に分類され、それぞれ以下の特徴をもつ。

|ファイル|内容|アクセス用API|
|:--|:--|:--|
|`テキストファイル`|文字解釈が**可能**な`文字列`データ|`FileReader`/`FileWriter`|
|`バイナリファイル`|文字解釈が**不可能**な`バイト列`データ|`FileInputStream`/`FileOutputStream`|

# テキストファイルの操作
`テキストファイル`は`文字ストリーム`を経由し、`FileReader`を用いた`読み込み`、`FileWriter`を用いた`書き込み`が可能。
なお、それぞれ`オブジェクト`の利用後に**必ず**`オブジェクト`の`解放(close)`処理を実行する必要がある。

## FileReader

### 定義

```java
// FileReaderオブジェクトの生成
FileReader(String fileName)
// パラメータ
// file: 読み込む「テキストファイル」のファイルパス

// 単一文字(char型)の読み込み
// -> 読み込み可能な文字が存在しない場合は"-1"を返却
// => 出力する場合はint型 -> char型 への変換が必要
// <- FileReaderはInputStreamReaderを継承
int InputStreamReader.read()

// 単一文字のスキップ
// <- FileReaderはReaderを間接的に継承
long Reader.skip(long n)
// パラメータ
// long: スキップする文字数

// FileReaderオブジェクトの解放
void InputStreamReader.close()
```

## FileWriter

### 定義

```java
// FileWriterオブジェクトの生成
FileWriter(String fileName, boolean append)
// パラメータ
// fileName: 書き込む「テキストファイル」のファイルパス(存在しない場合は新規作成)
// append: 末尾への追記フラグ
// -> trueの場合は末尾に追記、false(,省略)の場合は先頭に挿入

// 単一文字(char型)の書き込み
// <- FileWriterはOutputStreamReaderを継承
void OutputStreamWriter.write(int c)
// パラメータ
// c: 文字リテラルまたは「文字」を表すバイトコード

// 文字列の書き込み
// <- FileWriterはWriterを間接的に継承
void Writer.write(String str)
// パラメータ
// str: 書き込む文字列

// バッファリング文字列の強制書き出し
void OutputStreamWriter.flush()

// FileWriterオブジェクトの解放
void OutputStreamWriter.close()
```

# バイナリファイルの操作
`バイナリファイル`は`バイトストリーム`を経由し、`FileInputStream`を用いた`読み込み`、`FileOutputStream`を用いた`書き込み`が可能。
なお、それぞれ`オブジェクト`の利用後に**必ず**`オブジェクト`の`解放(close)`処理を実行する必要がある。

## FileInputStream

### 定義

```java
// FileInputStreamオブジェクトの生成
FileInputStream(String name)
// パラメータ
// name: 読み込む「バイナリファイル」のファイルパス

// バイトデータの読み込み
int FileInputStream.read()

// FileInputStreamオブジェクトの解放
void FileInputStream.close()
```

## FileOutputStream

```java
// FileOutputStreamオブジェクトの生成
FileOutputStream(String name, boolean append)
// パラメータ
// name: 書き込む「バイナリファイル」のファイルパス(存在しない場合は新規作成)
// append: 末尾への追記フラグ
// -> trueの場合は末尾に追記、false(,省略)の場合は先頭に挿入

// バイト列データの書き込み
void FileOutputStream.write(byte[] b)
// パラメータ
// b: 書き込むバイト列データ

// バイトデータの書き込み
void FileOutputStream.write(int b)
// パラメータ
// b: 書き込むバイトデータを表す10進数整数
```

***

# ストリーム(stream)
細分化した`データ`の送受信を可能にする`ストリーム`には、以下の種類が存在する。

|クラス|接続先|
|:--|:--|
|`System.out`|標準出力(standard out)|
|`System.err`|標準エラー出力(standard error)|
|`System.in`|標準入力(standard in)|
|`StringReader`|`String型`文字列|
|`ByteArrayOutputStream`|`byte[]型`バイト配列|

***

# バッファリングフィルタ(buffering filter)
`ストリーム`を`バッファリング`しながら入出力を行う`バッファリングフィルタ`は、以下の4種類。

|フィルタ|対応するファイル形式|継承元|
|:--|:--:|:--|
|`BufferedReader`|文字データ|`Reader`|
|`BufferedWriter`|文字データ|`Writer`|
|`BufferedInputStream`|バイトデータ|`FilterInputStream`|
|`BufferedOutputStream`|バイトデータ|`FilterOutputStream`|

## 定義

```java
// BufferedReaderオブジェクトの生成
BufferedReader(Reader in)
// パラメータ
// in: 「文字ストリーム」を読み込むReaderオブジェクト

// BufferedWriterオブジェクトの生成
BufferedWriter(Writer out)
// パラメータ
// in: 「文字ストリーム」に書き込むWriterオブジェクト

// BufferedInputStreamオブジェクトの生成
BufferedInputStream(InputStream in)
// パラメータ
// in: 「バイトストリーム」を読み込むInputStreamオブジェクト

// BufferedOutputStreamオブジェクトの生成
BufferedOutputStream(OutputStream out)
// パラメータ
// in: 「バイトストリーム」に書き込むOutputStreamオブジェクト
```

***

# ファイルの操作
`File`クラスのメソッドを用いることで、`ファイル`を操作することができる。

また、`Path`インタフェースの実装クラスである`Paths`クラスのメソッドを用いてファイルの`格納パス`(=`Path`オブジェクト)を特定し、
`Files`クラスの`クラスメソッド(静的メソッド)`を用いて、柔軟かつ多機能な`ファイル操作`を行うことができる。

さらに、`Class`クラスや`ClassLoader`クラスの`getResourceAsStream()`メソッドを用いることで、
`クラスパス`を基準とした`相対パス`でファイルを操作することができる。

## 定義(java.io.File)

```java
// Fileオブジェクトの生成
File(String pathname)
// パラメータ
// pathname: ファイルパス

// ファイルの削除
boolean File.delete()

// ファイル名の変更
boolean File.renameTo(File dest)
// パラメータ
// dest: 変更後のファイルパスを保持するFileオブジェクト

// ファイルが存在する場合はtrueを返却
boolean File.exists()

// Fileオブジェクトが「ファイル」であればtrueを返却
boolean File.isFile()

// Fileオブジェクトが「フォルダ」であればtrueを返却
boolean File.isDirectory()

// ファイルサイズの取得
long File.length()

// Fileオブジェクトが保持する「フォルダ」のファイル一覧の取得
File[] File.listFiles()
```

## 定義(java.nio.file.Paths)

```java
Path Path.get(
```

## 定義(java.nio.file.Files)

```java
// ファイルのコピー
Path Files.copy(Path source, Path target, CopyOption... options)
// パラメータ
// source: 「コピー先ファイルパス」を表すPathオブジェクト
// target:　「コピー元ファイルパス」を表すPathオブジェクト
// options: 「コピー方法」を表すCopyOptionオブジェクト
// -> CopyOptionインタフェースを実装したLinkOptionまたはStandardCopyOptionのクラス定数を利用

// ファイルの移動または名前の変更
Path Files.move(Path source, Path target, CopyOption... options)
// パラメータ
// source: 「移動(変更)先ファイルパス」を表すPathオブジェクト
// target:　「移動(変更)元ファイルパス」を表すPathオブジェクト
// options: 「移動(変更)方法」を表すCopyOptionオブジェクト

// ファイルの削除
void Files.delete(Path path)
// パラメータ
// path: 「ファイルパス」を表すPathオブジェクト

// ファイルが存在する場合はtrueを返却
boolean Files.exists(Path path, LinkOption... options)
// パラメータ
// path: 「ファイルパス」を表すPathオブジェクト
// options: 「シンボリックリンクの不探査」を表すLinkOptionのクラス定数
// -> 既定ではシンボリックリンクを辿ってファイルの実体まで走査

// フォルダであればtrueを返却
boolean Files.isDirectory(Path path, LinkOption... options)
// パラメータ
// path: 「ファイルパス」を表すPathオブジェクト
// options: 「シンボリックリンクの不探査」を表すLinkOptionのクラス定数

// ファイルサイズの取得
long Files.size(Path path)
// パラメータ
// path: 「ファイルパス」を表すPathオブジェクト

// ファイルの「バイトデータ」を全て読み込む
byte[] Files.readAllBytes(Path path)
// パラメータ
// path: 「ファイルパス」を表すPathオブジェクト

// ファイルの「全行(文字列データ)」をList型で読み込む
List<String> Files.readAllLines(Path path)
// パラメータ
// path: 「ファイルパス」を表すPathオブジェクト

// ファイルの「全行(文字列データ)」をString型で読み込む
String Files.readString(Path path)
// パラメータ
// path: 「ファイルパス」を表すPathオブジェクト

// BufferedReaderオブジェクトの取得
BufferedReader Files.newBufferedReader(Path path)
// パラメータ
// path: 「ファイルパス」を表すPathオブジェクト

// BufferedWriterオブジェクトの取得
BufferedWriter Files.newBufferedWriter(Path path, OpenOption... options)
// パラメータ
// path: 「ファイルパス」を表すPathオブジェクト
// options: 「ファイルの展開方法」を表すOpenOptionオブジェクト
// -> OpenOptionインタフェースを実装したLinkOptionまたはStandardOpenOptionのクラス定数を利用
```

***

# サンプルコード

```java:ReadAndWrite.java
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class ReadAndWrite {
    public static void main(String[] args) {
        // try-with-resource文を利用したリソース生成
        try (
            // FileWriterオブジェクトの生成
            // -> ファイルの新規作成(末尾に追記)
            FileWriter fw = new FileWriter("src/test.txt", true);

            // BufferedWriterオブジェクト(バッファリングフィルタ)の生成
            BufferedWriter bw = new BufferedWriter(fw);
        ) {
            // ファイルへの書き込み
            // -> 改行する場合は改行文字(\n)を挿入
            bw.write("1st sentence is written by BufferedWriter.\n");
            bw.write("2nd sentence is appended.");

            // 強制書き出し
            bw.flush();
        }
        // 例外処理
        catch (IOException e) {
            System.out.println("Output Exception occurred.");
        }
        
        try (
            // FileReaderオブジェクトの生成
            FileReader fr = new FileReader("src/test.txt");

            // BufferedReaderオブジェクトの生成
            BufferedReader br = new BufferedReader(fr);
        ) {
            // 入力ストリームが利用可能であればファイルを読み込む
            if (br.ready()) {
                // 読み込んだ文字列をnullで初期化(null安全の保証)
                String line = null;

                System.out.println("-- Output by BufferedReader --");

                // 読み込んだ文字列がnullでない限り、コンソールに出力
                while ((line = br.readLine()) != null) {
                    System.out.println(line);
                }
            }
        }
        //例外処理
        catch (IOException e) {
            System.out.println("Input Exception occurred.");
        }

        // try節中で定義するとcatch, finally節からは"見えない"ため、
        // tryブロック外でnullで初期化しておく必要がある
        BufferedReader br2 = null;

        // Paths/Filesクラスを用いたテキストファイルの読み込み
        try {
            // Pathオブジェクトの取得
            Path p = Paths.get("src/test.txt");

            // ファイルのコピー
            Files.copy(p, Paths.get("src/test_cp.txt"));
            Files.copy(p, Paths.get("src/test_cp_delete.txt"));

            Path pd = Paths.get("src/test_cp_delete.txt");

            // ファイル名の変更
            Files.move(p, Paths.get("src/test_renamed.txt"));

            Path pr = Paths.get("src/test_renamed.txt");

            // ファイルが存在する場合はファイルを削除
            if (Files.exists(pd)) {
                Files.delete(pd);
            }
            
            System.out.println("-- Output by Paths and Files --");

            // ファイルの読み込み・出力(BufferedReaderを利用しない場合)
            String str = Files.readString(pr);
            System.out.println(str);

            System.out.println("-- Output by Files and BufferedReader --");

            // ファイルの読み込み・出力(BufferedReaderを利用する場合)
            br2 = Files.newBufferedReader(pr);
            if (br2.ready()) {
                String line = null;
                while ((line = br2.readLine()) != null ) {
                    System.out.println(line);
                }
            }

            // ファイルサイズの取得
            System.out.print("File size: ");
            System.out.printf("%7.2f[KB]\n", ((double) Files.size(pr)) / 1024.0);
        }
        // 例外処理
        catch (Exception e) {
            System.out.println("Some exceptions occurred.");
        }
        finally {
            try {
                // BufferedReaderオブジェクトの解放処理
                // <- close()メソッドも例外送出の可能性があるため、「finally節内」でtry節を用いて記述
                br2.close();
            }
            catch (Exception e) {
                System.out.println("BufferedReader br2 cannot be closed.");
            }
        }
    }
}
```

```java:実行結果
-- Output by BufferedReader --
1st sentence is written by BufferedWriter.
2nd sentence is appended.
-- Output by Paths and Files --
1st sentence is written by BufferedWriter.
2nd sentence is appended.
-- Output by Files and BufferedReader --
1st sentence is written by BufferedWriter.
2nd sentence is appended.
File size:    0.07[KB]
```

```text:src/test_renamed.txt(src/test_cp.txt)
1st sentence is written by BufferedWriter.
2nd sentence is appended.
```

***

# 用語集

|用語|内容|
|:--|:--|
|リダイレクト(redirect)|`プログラム`の標準出力結果を`ファイル`に転送する機能。|
|パイプ(pipe)|`プログラム`の標準出力結果を別プログラムの標準入力に転送する機能。|
|フィルタ(filter)|`ストリーム`内のデータに`変換処理`を行う機能。|
|シンボリックリンク(symbolic link)|`UNIX系OS`における`ショートカット`の呼称。|