。这下从零开始了。
流式 IO
编程语言的 IO 类库经常使用流这个抽象概念,它将所有数据源或者数据接收器表示为能够产生或者接收数据片的对象。
所有与输入有关的类都继承于 InputStream,因此从 InputStream 或 Reader 派生而来的类都含有名为 read() 的基本方法,用于读取单个字节或者字节数组。而所有与输出有关的类也类似,故不再赘述。
InputStream:读出 Byte 数组,是字节流,数据按八位一组。
Reader:读出 Char 数组或 String,是字符流,数据按相对应的编码分组,比如Unicode。
Reader 的存在主要是为了国际化。老的 IO 流仅支持 8 比特的字节流,并且不能很好地处理 16 比特的 Unicode 字符。新的设计也使得它更快。
而实际使用中很少使用单一的类来创建流对象,而是通过叠合多个 IO 类【装饰器设计模式】。
数据源
IO 可以连接到如下数据源:
- 字节数组;
- String 对象;
- 文件;
- 管道;
- 一个由其它种类的流组成的序列;
- 对象;
- 其它数据源,如 Internet 连接;
构造器传参有文件路径、 File 对象和 FileDescriptor 对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import java.io.*; import java.nio.charset.StandardCharsets;
public class main { public static void main(String[] args) throws IOException { InputStream inputStream = new FileInputStream("/tmp/out1.txt"); int data = inputStream.read(); while (data != -1) { System.out.print(data + " "); data = inputStream.read(); } inputStream.close();
OutputStream outputStream = new FileOutputStream("/tmp/out1.txt"); outputStream.write("南溟".getBytes(StandardCharsets.UTF_8)); outputStream.close(); } }
|
ByteArrayInputStream 和 ByteArrayOutputStream 是在内存中创建缓冲区,缓冲区会随着数据的不断写入而自动增长。
类中的方法在关闭此流后仍可被调用,而不会产生任何 IOException。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import java.io.*; import java.nio.charset.StandardCharsets;
public class main { public static void main(String[] args) throws IOException {
InputStream input = new ByteArrayInputStream("南溟".getBytes(StandardCharsets.UTF_8)); int data = input.read(); while (data != -1) { System.out.print(data + " "); data = input.read(); } input.close();
OutputStream output = new ByteArrayOutputStream(); output.write("南溟".getBytes()); output.close(); } }
|
这两个类使得程序可以通过管道进行线程间的通讯。
使用两个已连接的管道流时,要为每个流操作创建一个线程。
因为 read() 和 write() 都是阻塞方法,如果一个线程同时读写就会造成死锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import java.io.*;
public class main { public static void main(String[] args) throws IOException { PipedOutputStream pipedOutputStream = new PipedOutputStream(); PipedInputStream pipedInputStream = new PipedInputStream(pipedOutputStream, 1); Thread thread1 = new Thread(new Runnable() { @Override public void run() { try { String data = "1234567890"; while (true) { pipedOutputStream.write(data.getBytes()); pipedOutputStream.flush(); System.out.println("pipedOutputStream.write " + data); Thread.sleep(10000); } } catch (Exception e) { System.out.println(e); } } }); thread1.start();
Thread thread2 = new Thread(new Runnable() { @Override public void run() { try { while (true) { int data = pipedInputStream.read(); System.out.println("pipedInputStream.read " + (char) data); Thread.sleep(100); } } catch (Exception e) { System.out.println(e); } } }); thread2.start(); } }
|
被序列化的对象的类要实现接口 Serializable。
静态属性无法被序列化和反序列化。
可以使用关键字 transient 标记不想被序列化的成员变量。
序列化到文件后不用 flush() 操作,同字节流一样也不存在缓冲区。
反序列化后的对象是 Object 类型,如果需要使用原对象的属性或方法,需要进行强制类型转化。
对象在序列化和反序列化的过程中,抛出的不只有 IOException 异常。
serialVersionUID 机制
当实现 Serializable 接口的类没有显式地定义一个 serialVersionUID 变量时,Java 的序列化机制会自动生成一个 serialVersionUID,写入到序列化文件中,用来验证版本一致性。
如果两个版本一致,在没有发生成员变量类型变更的情况下都可以正常转化。
如果两个版本不一致,则会直接报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import java.io.*;
public class main { public static void main(String[] args) throws IOException, ClassNotFoundException {
Student stu = new Student("南溟", 18, 1); File file = new File("/tmp/out.txt");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)); oos.writeObject(stu);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); Student newStu = (Student) ois.readObject(); System.out.println(newStu.toString()); } }
class Student implements Serializable {
private String Name; public int Age; public transient int Sex; public static String ClassName; private final static long serialVersionUID = -123123612836L;
public Student(String name, int age, int sex) { this.Name = name; this.Age = age; this.Sex = sex; }
@Override public String toString() { return "Student{" + "Name='" + Name + '\'' + ", Age=" + Age + ", Sex=" + Sex + '}'; } }
|
构造参数可以是两个 InputStream,或者是一个迭代器。
读取完成之前,不可以关闭被组合的流,否则会抛出异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import java.io.*;
public class main { public static void main(String[] args) throws IOException, ClassNotFoundException { InputStream input1 = new FileInputStream("/tmp/out1.txt"); InputStream input2 = new FileInputStream("/tmp/out2.txt");
SequenceInputStream sequenceInputStream = new SequenceInputStream(input1, input2); int data = sequenceInputStream.read(); while (data != -1) { System.out.print(data + " "); data = sequenceInputStream.read(); } } }
|
FilterInputStream/FilterOutputStream 也属于一种 InputStream/OutputStream,它的作用是为装饰器类提供基类。其中,装饰器类可以把属性或有用的接口与输入流连接在一起。
一旦要使用 readLine(),就不应该用 DataInputStream 【否则,编译时会得到使用了过时方法的警告】,而应该使用 BufferedReader。除了这种情况之外的情形中,DataInputStream 仍是 IO 类库的首选成员。是对 InputStream 的拓展。
为输入流提供缓冲,可以加快 IO 的速度。 BufferedInputStream 不是一次从网络或磁盘读取一个字节,而是一次将更大的块读入内部缓冲区。当从 BufferedInputStream 读取一个字节时,从其内部缓冲区中读取一个字节。当缓冲区被完全读取时,将另一个更大的数据块读入缓冲区。比从 InputStream 一次读取单个字节快得多,特别是对于磁盘访问和更大的数据量。
最好使用 1024 字节倍数的缓冲区大小,最适合硬盘中的大多数内置缓冲等。
1 2 3 4
| InputStream input1 = new BufferedInputStream(new FileInputStream("/tmp/out.txt"));
int bufferSize = 8 * 1024; InputStream input2 = new BufferedInputStream(new FileInputStream("/tmp/out.txt"), bufferSize);
|
BufferedOutputStream 在绝对确定数据写出后,才可调用 flush() 清空缓冲区。
允许先读取几个字节查看内容,来决定做什么,然后再重新从头读取。
流本身不支持回退功能,PushBackInputStream 内部维护了一个 byte 数组来实现推回操作的。
通常作为编译器的扫描器,一般情况下不会用到。
1 2
| int pushbackLimit = 8; PushbackInputStream input = new PushbackInputStream(new FileInputStream("/tmp/out.txt"), pushbackLimit);
|
PrintStream
目的就是为了以可视化格式打印所有基本数据类型。
它捕获了所有的 IOException,因此,我们必须使用 checkError() 自行测试错误状态。
可以用 boolean 值指示是否每次换行时清空缓冲区。
跟踪输入流中的行号,可调用 getLineNumber() 和 setLineNumber(int)。
Refer
《On Java 8》中文版
系统学习 Java IO ---- 目录,概览