Java 入出力 memo (Oracle Java8 Gold 試験対策)


Goldの出題範囲がとても広い（なんとしても2018/9中に取りたい）。。
今回のメモはこれ。

* Fileクラス
* ストリーム
* シリアライズ
* コンソール

## Fileクラス

サンプル実行のために事前にファイル作成しておく。

```
mkdir /tmp/dir
touch /tmp/dir/hoge.txt
touch /tmp/dir/fuga.txt
mkdir /tmp/dir/subdir
touch /tmp/a.txt
touch /tmp/dir/x.txt
```

```java
import java.io.File;

public class InOutFileSample {
    public static void main(String[] args) {
        
        // Fileオブジェクトの生成
        File f1 = new File("/tmp/dir");
        File f2 = new File("tmp", "a.txt");
        File f3 = new File(f1, "x.txt");

        for (File f : f1.listFiles()) { // f1ディレクトリ内のファイル群を取得

            if (f.isFile()) { // ファイルだった場合
                System.out.println("File: " + f.getName());
            } else if (f.isDirectory()) { // ディレクトリだった場合
                System.out.println("Directory: " + f.getName());
            }
        }

        // 絶対パス確認
        System.out.println("f1 absolute path: " + f1.getAbsolutePath());
        System.out.println("f2 absolute path: " + f2.getAbsolutePath());
        System.out.println("f3 absolute path: " + f2.getAbsolutePath());

    }
}
```

## ストリーム

### 4つの基本的なストリーム

* 入力ストリーム(InputStream)
    * データの読み込み
* 出力ストリーム(OutputStream)
    * データの書き込み
* バイトストリーム
    * **byte単位**で読み書きするストリーム
* キャラクタストリーム
    * **char単位**で読み書きするストリーム

| | バイトストリーム | キャラクタストリーム |
|:---|:---|:----|
| 出力ストリーム | OutputStream | Writer |
| 入力ストリーム | InputStream | Reader |

XXStreamのStreamがついてなければキャラクタストリーム。

#### 主なバイトストリーム

| クラス名|  説明|
|:---|:----|
|FileInputStream |ファイルからbyte単位の読み出しのためのストリーム|
|FileOutputStream |ファイルに対してbyte単位の書き出しするためのストリーム|
|DataInputStream |基本データ型のデータを読み出せるストリーム|
|DataOutputStream | 基本データ型のデータを書き出せるストリーム|

#### 主なキャラクタストリーム

| クラス名|  説明|
|:---|:----|
| FileReader | ファイルからchar単位の読み込みをするためのストリーム|
| FileWriter |ファイルに対してchar単位の書き込みをするためのストリーム |
| BufferedReader |基本データ型のデータを読み込むためのストリーム|
| BufferedWriter | 基本データ型のデータを書き出すためのストリーム|

### FileInputStream/FileOutputStream

バイトストリームの`FileInputStream`と`FileOutputStream`を使った実装。

```java
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class FileInputOutputStreamSsample {
    public static void main(String[] args) {

        try (FileOutputStream fos = new FileOutputStream("/tmp/lesson.txt");
             FileInputStream fis = new FileInputStream("/tmp/lesson.txt")) {

            // ファイルへの書き込み
            fos.write(0);
            fos.write("makio".getBytes());
            fos.write(99);

            // ファイルから読み込み
            int data = 0;
            while ((data = fis.read()) != -1) { // -1を返すまでreadメソッドでバイト単位の読み込み
                System.out.print(data + " ");
            }

        } catch (FileNotFoundException e) {
            System.out.println("ファイルがありません。");
        } catch (IOException e) {
            System.out.println("ファイルの読み書きで失敗");
        }
    }
}
```

```
0 109 97 107 105 111 99 
```

なお、`FileOutputStream`でファイルに対して追記したい場合は、以下のようにする。

```java
// これだと新規作成になる。
FileOutputStream("/tmp/lesson.txt")

// 第二引数にtrueを与えると追記になる。
FileOutputStream("/tmp/lesson.txt", true)
```

### DataInputStream/DataOutputStream

バイトストリームの`DataInputStream`と`DataOutputStream`を使った実装。

**このストリームは単体では利用できない**ので、他のストリームと組み合わせて利用する。

```java
import java.io.*;

public class DataInputOutputStreamSample {

    public static void main(String[] args) {

        try(DataOutputStream dos =
                    new DataOutputStream(new FileOutputStream("/tmp/lesson.txt"));
            DataInputStream dis =
                    new DataInputStream(new FileInputStream("/tmp/lesson.txt"))) {

            // ファイルへの書き込み
            dos.writeInt(100);
            dos.writeUTF("makio");
            dos.writeUTF("ほげほげほげ");
            
            // ファイルから読み込み
            System.out.println(dis.readInt());
            System.out.println(dis.readUTF());
            System.out.println(dis.readUTF());

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
```

```
100
makio
ほげほげほげ
```

### FileReader/FileWriter

キャラクタストリームの`FileReader`と`FileWriter`クラスの利用例。
`DataInputStream`/`DataOutputStream`と異なり、自動でエンコーディングしてくれる。

```java
import java.io.*;

public class FileReaderWriterSample {
    public static void main(String[] args) {
        try (FileWriter fw = new FileWriter(new File("/tmp/lesson.txt"));
             FileReader fr = new FileReader(new File("/tmp/lesson.txt"))) {

            // ファイルへの書き込み
            fw.write("ほげほげほほげ");
            fw.flush();

            // ファイルから読み込み
            int i = 0;
            while((i = fr.read()) != -1) {
                // キャラクタ型にキャストして出力
                System.out.print((char) i);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
```


```
ほげほげほほげ
```



### BufferedReader/BufferedWriter

キャラクタストリームの`BufferedReader`と`BufferedWriter`クラスの利用例。

文字列をブロック単位で読み書きするためのストリーム。
バッファリングしながら入出力するので、効率がいい。


```java
import java.io.*;

public class BufferedReaderWriterSample {
    public static void main(String[] args) {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter(new File("/tmp/lesson.txt")));
             BufferedReader br = new BufferedReader(new FileReader(new File("/tmp/lesson.txt")))) {

            // ファイルへの書き込み
            bw.write("Good morning!");
            bw.newLine();
            bw.write("Good afternoon!");
            bw.newLine();
            bw.write("Good evening!");
            bw.flush(); // flushで即時反映

            // ファイルから読み込み
            String data = null;
            while ((data = br.readLine()) != null) { // readLineメソッドはファイルの最後になると、nullを返す。
                System.out.println(data);
            }

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}
```

```
Good morning!
Good afternoon!
Good evening!
```

## シリアライズ

* シリアライズ
    * **オブジェクトを出力ストリームに書き出すこと**
    * 直列化ともいう。
* デシリアライズ
    * **シリアライズされたオブジェクトを読み込んでメモリ上の復元すること**
    * 直列化復元ともいう。


### シリアライズの方法

ここでは`User`クラスのオブジェクトを`ObjectInputStream`と`ObjectOutputStream`を使ってファイルに書き出して復元する。


```java
import java.io.*;

/**
 * シリアライズ対象のUserクラス
 * Serializableインタフェースはメソッドをもたない
 */
class User implements Serializable {
    private int id;
    private String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

public class SerializeDeseriazlieSample {

    public static void main(String[] args) {
        User writeUser = new User(1000, "Makio");

        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("/tmp/user_object.txt"));
             ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/tmp/user_object.txt"))) {

            // オブジェクトの書き込み　シリアライズ
            oos.writeObject(writeUser);
            oos.flush();

            // オブジェクトの読み込み デシリアライズ
            User readUser = (User)ois.readObject(); // Userにキャストしないといけない。
            System.out.format("id: %d, name: %s", readUser.getId(), readUser.getName());
            
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
}
```

```
id: 1000, name: Makio
```

### シリアライズの注意点　

考えて見れば当たり前ことばかりだけど制約事項が多い。

* 配列またはコレクションをシリアライズする場合は、その要素のそれぞれがシリアライズ可能でないといけない。
* シリアライズされたオブジェクトが参照しているオブジェクトもシリアライズ可能でないといけない.
* `static`変数および`tansient`指定された変数はシリアライズ対象外になる。
* スーパークラスをシリアライズ対象としていればサブクラスも暗黙的にシリアライズ対象になる。
* サブクラスがシリアライズ対象の場合、スーパークラスはデシリアライズの際にインスタンス化される。


## コンソール

コンソールから読み書きするためには`Console`オブジェクトを生成してコンソールのストリームに読み書きする。

```java
import java.io.Console;
import java.io.PrintWriter;

public class ConsoleSample {
    public static void main(String[] args) {
        // System.console()で一意のConsoleオブジェクトを取得
        // コンソールが使用できない場合はconsole()メソッドはnullになる。
        Console console = System.console();

        // キャラクタストリームのPrintWriterオブジェクトを生成
        PrintWriter pw = console.writer();

        while (true) {
            String str = console.readLine();
            if (str.equals("end")) {
                break;
            }
            pw.append("input data: " + str);
            pw.flush();
        }

    }
}
```