LoginSignup
35
44

More than 5 years have passed since last update.

javaのIOおさらい

Last updated at Posted at 2017-11-08

IOの分類

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

IO方向で分類
input stream:読み取りできる
output stream:書き込みできる

処理単位で分類
byte stream:処理単位が1バイト(8ビット)
char stream:処理単位が2バイト(16ビット)

レベルで分類
low level stream:特定IO設備(HDD,net等)からin/outする
high level stream:既存のstreamをラップして、in/outする

low and high.png

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

IO常用クラス

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

InputStream/Reader, OutputStream/Writer仕様

InputStream/Reader
int read() : 一単位読込む
int read(byte[] | char[] b):b単位読込む
int read(byte[] | char[] b, int off, int len):len単位読込む、bのoff位置から格納

OutputStream/Writer
write(int c):cを書込む
write(byte[] | char[] buf):bufを書込む
write(byte[] | char[] buf, int off, int len):offの位置から、len分書込む

常用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である。

file-input.txt
abcd1\r\n
あいう

FileInputStream (byte操作)

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());
}
出力
abcd
1

���

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

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

FileReader (char操作)

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());
}
出力
abcd
1

いう

abcd : 4文字出力
1\r\nあ:4文字出力
いう:最後の2文字出力

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

FileOutputStream/FileWriter

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());
}
file-output-1.txt
abcd1
あいう
file-output-2.txt
abcd1
あいう

結論:byte,char操作両方正常に出力できる。

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

PrintStream/PrintWrite

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());
}
file-output-4,5.txt
1
aa
あいうえお
aaa

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

StringReader,StringWriter

StringReader
文字列をデータソースとするinput stream
StringWriter
StringBufferをoutputとするoutput stream
StringReader,StringWriter
String src = "あいうえお" + System.lineSeparator()
    + "abcde" + System.lineSeparator()
    + "3行目";
    + 
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();
}
出力
あいうえお
abcde
行目
123あいう

InputStreamReader/OutputStreamWriter

InputStreamReader/OutputStreamWriterは、byte streamをchar streamへ変換する。

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
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();
}
出力
1
12
123
1234
12345
123456
1234567
12345678
123456789

処理イメージ
push-back-init.png
1. buff, push-back-buff, 初期位置がイメージ通りに生成される

push-back-1.png
1. read()でファイルから1がbuffに入る
2. 次に読むのは2
3. この状態で出力すると'1'が出力される

push-back-2.png
1. buffの1をpush-back-buffへ入れる

push-back-3.png
1. read()で、まずpush-back-buffから1がbuffに入る
2. ファイルから2がbuffに入る
3. この状態で出力すると'12'が出力される
4. 次に読むのは3

push-back-4.png
1. buffの12をpush-back-buffへ入れる

push-back-5.png
1. read()で、まずpush-back-buffから12がbuffに入る
2. ファイルから3がbuffに入る
3. この状態で出力すると'123'が出力される
4. 次に読むのは4

これの繰り返し!!

ObjectInputStream/ObjectOutputStream

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

使うモデル
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

単一オブジェクト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());
}
出力
#------シリアライズ------
Create person.
#------デシリアライズ------
alex
30

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

複数オブジェクトin/out

// シリアザイズ
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」が発生する。

オブジェクト参照を持つ

// シリアザイズ
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」が発生する。

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

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

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)
標準出力リダイレクト
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

35
44
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
44