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する
high level streamを使うメリット
- 操作を抽象化して、データソースへの操作相違を吸収できる
- IO操作が簡単になる
- 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である。
abcd1\r\n
あいう
FileInputStream (byte操作)
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操作)
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文字出力
結論:
- 指定したcharは文字数になる。
- abcdは、4バイトだが、4文字なので、abcdが出力されるね!
FileOutputStream/FileWriter
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());
}
abcd1
あいう
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());
}
1
aa
あいうえお
aaa
まとめ:
- PrintStream/PrintWrier強力な手段であり、よく使うSystou.outもPrintStream
- テキスト出力はこれを使うべき?
StringReader,StringWriter
- StringReader
- 文字列をデータソースとするinput stream
- StringWriter
- StringBufferをoutputとするoutput stream
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
3行目
123あいう
InputStreamReader/OutputStreamWriter
InputStreamReader/OutputStreamWriterは、byte streamをchar streamへ変換する。
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();
}
- 標準入力(System.ioはInputStream)をInputStreamReaderに変換
- InputStreamReaderをバッファー機能付のBufferedReaderに変換
- BufferedReader#readLineは改行を読込むまで__スレッドをブロック__する
PushbackInputStream/PushbackReader
戻すためのバッファが用意され、下記メソッドでバッファに戻すことができる。
unread(int b)
unread(byte[] | char[] buff)
unread(byte[] | char[] buff, int off, int len)
- read時に、まずpush-back-bufから取得する。
- push-back-bufからreadする最大長さを満たさない場合、buffから取得する。
123456789
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
- buff, push-back-buff, 初期位置がイメージ通りに生成される
- read()でファイルから
1
がbuffに入る - 次に読むのは2
- この状態で出力すると'1'が出力される
- buffの
1
をpush-back-buffへ入れる
- read()で、まずpush-back-buffから
1
がbuffに入る - ファイルから
2
がbuffに入る - この状態で出力すると'12'が出力される
- 次に読むのは3
- buffの
12
をpush-back-buffへ入れる
- read()で、まずpush-back-buffから
12
がbuffに入る - ファイルから
3
がbuffに入る - この状態で出力すると'123'が出力される
- 次に読むのは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
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
まとめ:
- シリアライズしたのは、オブジェクトのデータであり、クラスでないため、
デシリアライズする際にはPerson.classが必要 -
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();
まとめ:
- 順序が存在し、シリアザイズした順にデシリアライズする必要がある。
- 順序が違う場合、「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();
まとめ:
- 参照されるオブジェクトもシリアザイズされる。
- 参照されるオブジェクトが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
まとめ: 同じ参照をデシリアライズした場合でも、シリアライズ前と同じく参照されるオブジェクトは一つである。
シリアライズイメージ:
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