javaのIOおさらい

# IOの分類

IOを幾つかの観点で分類してみる

<dl>
<dt>IO方向で分類</dt>
<dd>input stream：読み取りできる</dd>
<dd>output stream：書き込みできる</dd>
<br>
<dt>処理単位で分類</dt>
<dd>byte stream：処理単位が１バイト(８ビット)</dd>
<dd>char stream：処理単位が２バイト(１６ビット)</dd>
<br>
<dt>レベルで分類</dt>
<dd>low level stream：特定IO設備(HDD,net等)からin/outする</dd>
<dd>high level stream：既存のstreamをラップして、in/outする</dd>
</dl>
![low and high.png](https://qiita-image-store.s3.amazonaws.com/0/104977/f007f2b0-dab4-1f6e-a058-aa3665fa95a6.png)

__high level streamを使うメリット__
1. 操作を抽象化して、データソースへの操作相違を吸収できる
2. IO操作が簡単になる
3. IO効率がよくなる


# IO常用クラス
javaのIO関連クラスは40個以上存在するが、
全てInputStream/Reader,OutputStream/Writerを基底クラスとする。

## InputStream/Reader, OutputStream/Writer仕様
<dl>
<dt>InputStream/Reader</dt>
<dd>int read() : 一単位読込む</dd>
<dd>int read(byte[] | char[] b)：b単位読込む</dd>
<dd>int read(byte[] | char[] b, int off, int len)：len単位読込む、bのoff位置から格納</dd>
<br>
<dt>OutputStream/Writer</dt>
<dd>write(int c):cを書込む</dd>
<dd>write(byte[] | char[] buf):bufを書込む</dd>
<dd>write(byte[] | char[] buf, int off, int len):offの位置から、len分書込む</dd>

</dl>

## 常用IOクラス
| 分類             | byte input stream    | byte output stream    | char input stream | char output stream |
|-----------------|----------------------|-----------------------|-------------------|--------------------|
|抽象基底   | InputStream          | OutputStream          | Reader            | Writer             |
|ファイルIO | FileInputStream      | FileOutputStream      | FileReader        | FileWriter         |
|配列IO     | ByteArrayInputStream | ByteArrayOutputStream | CharArrayReader   | CharArrayWriter    |
|スレッド間IO | PipedInputStream     | PipedOutputStream     | PipedReader       | PipedWriter        |
|文字列IO   |                      |                       | StringReader      | StringWriter       |
|バッファー機能       | BufferdInputStream   | BufferdOutputStream   | BufferdReader     | BufferdWriter      |
|byte⇒char操作変換  |                      |                       | InputStreamReader | OutputStreamWriter |
|オブジェクトIO     | ObjectInputStream    | ObjectOutputStream    |                   |                    |
|抽象基底？   | FilterInputStream    | FilterOutputStream    | FilterReader      | FilterWriter       |
|プリント         |                      | PrintStream           |                   | PrintWriter        |
|pushback!           | PushbackInputStream  |                       | PushbackReader    |                    |
|特殊なやつ  | DataInputStream      | DataOutputStream      |                   |                    |
※ タイプミスあるかも！

# 試してみる

## FileInputStream/FileOutputStream, FileReader/FileWriter
直接ディスクアクセスなので、low level streamである。

```java:file-input.txt
abcd1\r\n
あいう
```
### FileInputStream (byte操作)

```java:FileInputStream
try (FileInputStream fi = new FileInputStream("./FileIOSample/file-input.txt")) {
    // 4 byte のバッファー
    byte[] buff = new byte[4];

    // 読み取ったbyte数
    int hasRead = 0;

    // 最大buff分読み取る
    while ((hasRead = fi.read(buff)) > 0) {
        
        // 読み取ったbyteを文字列に変換して出力
        System.out.println(new String(buff, 0, hasRead));
    }
} catch (IOException ex) {
    System.out.println(ex.getMessage());
}
```
```java:出力
abcd
1
�
���
�う
```

abcd : 最初4byte出力
1\r\n� :「あ」の前半(1byte)を取ったので、文字化け！
��� :「あ」の後半(1byte) + 3バイトなので、文字化け！

__結論__：指定したbyteずつ取得し、マルチバイト文字の場合、文字化けする可能性がある。

### FileReader (char操作)
```java:FileReader
try (FileReader fr = new FileReader("./FileIOSample/file-input.txt")) {
    // 4 char のバッファー
    char[] buff = new char[4];

    // 読み取った文字数
    int hasRead = 0;

    // 最大buff分読み取る
    while ((hasRead = fr.read(buff)) > 0) {
        
        // 読み取ったbyteを文字列に変換して出力
        System.out.println(new String(buff, 0, hasRead));
    }
} catch (IOException ex) {
    System.out.println(ex.getMessage());
}
```
```java:出力
abcd
1
あ
いう
```
abcd : 4文字出力
1\r\nあ：4文字出力
いう：最後の2文字出力

__結論__：
1. 指定したcharは文字数になる。
2. abcdは、4バイトだが、4文字なので、abcdが出力されるね！

## FileOutputStream/FileWriter

```java:FileInputStream
try (
    FileInputStream fi = new FileInputStream("./FileIOSample/file-input.txt")) {
    FileOutputStream fo = new FileOutputStream("./FileIOSample/file-output-1.txt");

    // 4 byte のバッファー
    byte[] buff = new byte[4];

    // 読み取った単位数(byte数)
    int hasRead = 0;

    while ((hasRead = fi.read(buff)) > 0) {
        // 読み取った分書き込む
        fo.write(buff, 0, hasRead);
    }
} catch (IOException ex) {
    System.out.println(ex.getMessage());
}

try (
    FileReader fr = new FileReader("./FileIOSample/file-input.txt");
    FileWriter fo = new FileWriter("./FileIOSample/file-output-2.txt")) {
    
    // 4 char のバッファー
    char[] buff = new char[4];
    
    // 読み取った単位数(byte数)
    int hasRead = 0;

    while ((hasRead = fr.read(buff)) > 0) {
        // 読み取った分書き込む
        fo.write(buff, 0, hasRead);
    }
} catch (IOException ex) {
    System.out.println(ex.getMessage());
}
```
```java:file-output-1.txt
abcd1
あいう
```
```java:file-output-2.txt
abcd1
あいう
```
__結論__：byte,char操作両方正常に出力できる。

```java:文字列出力した場合
FileWriter fo = new FileWriter("./FileIOSample/file-output-3.txt")) {
fo.write("行１");
fo.write(System.lineSeparator());
```

## PrintStream/PrintWrite

```java:
try (
    FileOutputStream fo = new FileOutputStream("./FileIOSample/file-output-4.txt");
    PrintStream ps = new PrintStream(fo)){

    ps.println(1);
    ps.println("aa");
    ps.println("あいうえお");
    ps.println(new String("aあ"));
} catch (IOException ex) {
    System.out.println(ex.getMessage());
}

try (
    FileOutputStream fo = new FileOutputStream("./FileIOSample/file-output-5.txt");
    PrintWriter ps = new PrintWriter(fo)){

    ps.println(1);
    ps.println("aa");
    ps.println("あいうえお");
    ps.println(new String("aあ"));
} catch (IOException ex) {
    System.out.println(ex.getMessage());
}
```
```java:file-output-4,5.txt
1
aa
あいうえお
aaa
```

まとめ：
1. PrintStream／PrintWrier強力な手段であり、よく使うSystou.outもPrintStream
2. テキスト出力はこれを使うべき？


## StringReader,StringWriter
<dl>
<dt>StringReader</dt>
<dd>文字列をデータソースとするinput stream</dd>

<dt>StringWriter</dt>
<dd>StringBufferをoutputとするoutput stream</dd>
</dl>

```java:StringReader,StringWriter
String src = "あいうえお" + System.lineSeparator()
    + "abcde" + System.lineSeparator()
    + "３行目";
    + 
try (StringReader sr = new StringReader(src)) {
    char[] buff = new char[4];
    int hasRead = 0;
    while ((hasRead = sr.read(buff)) > 0) {
        System.out.print(new String(buff, 0, hasRead));
    }
    System.out.println();
} catch (IOException e) {
    e.printStackTrace();
}

try (StringWriter sr = new StringWriter()) {
    sr.write("123");
    sr.write("あいう");
    System.out.println(sr.toString());
} catch (IOException e) {
    e.printStackTrace();
}
```
```java:出力
あいうえお
abcde
３行目
123あいう
```
## InputStreamReader/OutputStreamWriter
InputStreamReader/OutputStreamWriterは、byte streamをchar streamへ変換する。

```java:InputStreamReader,BufferedReader
try (InputStreamReader reader = new InputStreamReader(System.in);
     BufferedReader bufferedReader = new BufferedReader(reader)) {
    String line;
    System.out.print("input:");
    while ((line = bufferedReader.readLine()) != null) {
        if (line.equals("exit")) {
            System.exit(0);
        }
        System.out.println("output:" + line);
        System.out.print("input:");
    }
} catch (IOException e) {
    e.printStackTrace();
}
```

1. 標準入力(System.ioはInputStream)をInputStreamReaderに変換
2. InputStreamReaderをバッファー機能付のBufferedReaderに変換
3. BufferedReader#readLineは改行を読込むまで__スレッドをブロック__する

## PushbackInputStream/PushbackReader

戻すためのバッファが用意され、下記メソッドでバッファに戻すことができる。
unread(int b)
unread(byte[] | char[] buff)
unread(byte[] | char[] buff, int off, int len)

1. read時に、まずpush-back-bufから取得する。
2. push-back-bufからreadする最大長さを満たさない場合、buffから取得する。

```push-back.txt
123456789
```
```java:push-back-stream
int pushbackBuff = 8;
try (
   PushbackReader pr = new PushbackReader(new FileReader("push-back.txt"), pushbackBuff)) {
    int hasRead = 0;
    int size = 1;
    char[] buff = new char[size];

    while ( (hasRead = pr.read(buff)) != -1) {
        System.out.println(new String(buff, 0, hasRead));
        // push back
        pr.unread(buff, 0, hasRead);
        buff = new char[++size];
    }
} catch (IOException ex) {
    System.out.println();
}
```
```java:出力
1
12
123
1234
12345
123456
1234567
12345678
123456789
```

処理イメージ
![push-back-init.png](https://qiita-image-store.s3.amazonaws.com/0/104977/171ad15a-ec28-7227-d302-88a418061c37.png)
1. buff, push-back-buff, 初期位置がイメージ通りに生成される


![push-back-1.png](https://qiita-image-store.s3.amazonaws.com/0/104977/b8246772-e14f-de6b-d6c6-2291d4986b98.png)
1. read()でファイルから`1`がbuffに入る
2. 次に読むのは２
3. この状態で出力すると'1'が出力される

![push-back-2.png](https://qiita-image-store.s3.amazonaws.com/0/104977/531d6f5a-f754-e865-7490-5f75d2ba6838.png)
1. buffの`1`をpush-back-buffへ入れる

![push-back-3.png](https://qiita-image-store.s3.amazonaws.com/0/104977/3c4510af-837f-5c0e-0fd1-a6c4d69bf414.png)
1. read()で、まずpush-back-buffから`1`がbuffに入る
2. ファイルから`2`がbuffに入る
3. この状態で出力すると'12'が出力される
4. 次に読むのは3

![push-back-4.png](https://qiita-image-store.s3.amazonaws.com/0/104977/bbefb405-e14e-1530-9284-d471ac8f2b87.png)
1. buffの`12`をpush-back-buffへ入れる

![push-back-5.png](https://qiita-image-store.s3.amazonaws.com/0/104977/7775e84d-e8e6-2d62-47fa-0f4e925f95fe.png)
1. read()で、まずpush-back-buffから`12`がbuffに入る
2. ファイルから`3`がbuffに入る
3. この状態で出力すると'123'が出力される
4. 次に読むのは4

これの繰り返し！！

## ObjectInputStream/ObjectOutputStream
オブジェクトをシリアライズ、デシリアライズするためストリーム。
シリアライズするためには、__Serializable/Externalizable__を実装する必要がある。

```java:使うモデル
import java.io.Serializable;

public class Person implements Serializable{
    public String name;
    public int age;

    public Person(String name, int age) {
        System.out.println("Create person.");
        this.name = name;
        this.age = age;
    }
}

public class Student implements Serializable{
    public String name;
    public int age;

    public Student(String name, int age) {
        System.out.println("Create student.");
        this.name = name;
        this.age = age;
    }
}

public class Teacher implements Serializable{
    public String name;
    public int age;

    public Student student;

    public Teacher(String name, int age, Student student) {
        System.out.println("Create teacher.");
        this.name = name;
        this.age = age;
        this.student = student;
    }
}
```



### 単一オブジェクトin/out
```java:単一オブジェクトin/out
System.out.println("------シリアライズ------");
try (
    // 出力先(low level stream)
    FileOutputStream fo = new FileOutputStream("object-out-1.data");
    // Object出力ストーリー(high level stream)
    ObjectOutputStream out = new ObjectOutputStream(fo)) {

    // Object出力
    out.writeObject(new Person("alex", 30));
} catch (IOException ex) {
    System.out.println(ex.getMessage());
}

System.out.println("------デシリアライズ------");
try (
    // (low level stream)
    FileInputStream fi = new FileInputStream("object-out-1.data");
    // Object出力ストーリー(high level stream)
    ObjectInputStream in = new ObjectInputStream(fi)) {

    Person p = (Person) in.readObject();

    // Object出力
    System.out.println(p.name);
    System.out.println(p.age);
} catch (IOException | ClassNotFoundException ex) {
    System.out.println(ex.getMessage());
}
```
```java:出力
#------シリアライズ------
Create person.
#------デシリアライズ------
alex
30
```

まとめ：
1. シリアライズしたのは、オブジェクトのデータであり、クラスでないため、
デシリアライズする際にはPerson.classが必要
2. `Create person.`が出力されてないことから、デシリアライズする際にコンストラクタは不要である。


### 複数オブジェクトin/out

```java
// シリアザイズ
out.writeObject(new Person("alex", 30));
out.writeObject(new Student("king", 20));

// デシリアザイズ
Person p = (Person) in.readObject();
Student s = (Student) in.readObject();
```

まとめ：
1. 順序が存在し、シリアザイズした順にデシリアライズする必要がある。
2. 順序が違う場合、「Exception in thread "main" java.lang.ClassCastException: io.serializable.Person cannot be cast to io.serializable.Student」が発生する。

### オブジェクト参照を持つ

```java
// シリアザイズ
out.writeObject(new Teacher("alex", 30, new Student("king", 20)));

// writing aborted; java.io.NotSerializableException: io.serializable.Student
Teacher t = (Teacher) in.readObject();
```

まとめ：
1. 参照されるオブジェクトもシリアザイズされる。
2. 参照されるオブジェクトがSerializableを実装しない場合、「writing aborted; java.io.NotSerializableException: io.serializable.Student」が発生する。


### 複数オブジェクトが同じ参照を持つ

```java:
Student s = new Student("king", 20);
// シリアザイズ
out.writeObject(new Teacher("alex", 30, s));
out.writeObject(new Teacher("bill", 40, s));

// デシリアザイズ
Teacher alex = (Teacher) in.readObject();
Teacher bill = (Teacher) in.readObject();
System.out.println(alex.student == bill.student); // true
```

まとめ： 同じ参照をデシリアライズした場合でも、シリアライズ前と同じく参照されるオブジェクトは一つである。
シリアライズイメージ：
![object ref.png](https://qiita-image-store.s3.amazonaws.com/0/104977/67c9e525-080a-6a33-b803-910dd4c1269c.png)

### serialVersionUID 
シリアライズとデシリアライズ時のクラスが同じであることを確認するためのもの。
前後が異なると、「Exception in thread "main" java.io.InvalidClassException」が発生する。


* 記載方法
    * 明示的に書く  
private static final long serialVersionUID = 512L;
    * jvmがクラス情報を元に計算してくれる 
        * メソッド変更は影響を与えない 
        * staticメンバ変更は影響を与えない 
        * 非staticメソッドの変更は影響を与える 
* bin/serialverで手動計算できる


# おまけ
## 標準IOのリダイレクト
javaはSystem.inとSystem.outで標準入出を表していて、デフォルトはキーボードとディスプレイである。

* 標準IOのリダイレクトメソッド
    * System#setErr(PrintStream err)
    * System#setIn(InputStream in)
    * System#setOut(PrintStream out)

```java:標準出力リダイレクト
try (FileOutputStream fileOutputStream = new FileOutputStream("strandard-output.txt");
     PrintStream print = new PrintStream(fileOutputStream)) {
    System.setOut(print);
    System.out.println("aaaaabbbbb");
} catch (IOException ex) {
    System.out.println(ex.getMessage());
}
```

# 次はNIOとNIO.2
NIO  : https://qiita.com/liguofeng29/items/c827af3d62f219e17755
NIO2 : https://qiita.com/liguofeng29/items/3d0ba350a1547630727b