java-io

Java 的 I/O 大概可以分成以下几类:

  1. 磁盘操作:File
  2. 字节操作:InputStream 和 OutputStream
  3. 字符操作:Reader 和 Writer
  4. 对象操作:Serializable
  5. 网络操作:Socket
  6. 新的输入/输出:NIO

字节操作

java i/o 使用了装饰着模式实现,以 InputStream 为例,InputStream 是抽象组件,FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作。FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能,例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。

实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。

1
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));

字符操作

不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符,所以 I/O 操作的都是字节而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。

InputStreamReader 实现从文本文件的字节流解码成字符流;OutputStreamWriter 实现字符流编码成为文本文件的字节流。它们继承自 Reader 和 Writer。

编码就是把字符转换为字节,而解码是把字节重新组合成字符。

1
2
byte[] bytes = str.getBytes(encoding);     // 编码
String str = new String(bytes, encoding); // 解码

GBK 编码中,中文占 2 个字节,英文占 1 个字节;UTF-8 编码中,中文占 3 个字节,英文占 1 个字节;Java 使用双字节编码 UTF-16be,中文和英文都占 2 个字节。

如果编码和解码过程使用不同的编码方式那么就出现了乱码。

五、对象操作

序列化就是将一个对象转换成字节序列,方便存储和传输。

序列化:ObjectOutputStream.writeObject()

反序列化:ObjectInputStream.readObject()

序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现。

transient 关键字可以使一些属性不会被序列化。

ArrayList 序列化和反序列化的实现 :ArrayList 中存储数据的数组是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。

1
private transient Object[] elementData;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) throws Exception {
Pers p = new Pers("aa", "bb");
Ser(p);
Pers ps = Deser();
System.out.println(ps);

}

public static void Ser(Pers p) throws IOException {
try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./pers.bin"))) {
outputStream.writeObject(p);
System.out.println("ser done");
}
}

public static Pers Deser() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./pers.bin")));
Pers p = (Pers) ois.readObject();
System.out.println("Person对象反序列化成功!");
return p;
}

网络操作

NIO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
它用的是事件机制。它可以用一个线程把Accept,读写操作,请求处理的逻辑全干了。如果什么事都没得做,它也不会死循环,它会将线程休眠起来,直到下一个事件来了再继续干活,这样的一个线程称之为NIO线程。用伪代码表示:
while true {
events = takeEvents(fds) // 获取事件,如果没有事件,线程就休眠
for event in events {
if event.isAcceptable {
doAccept() // 新链接来了
} elif event.isReadable {
request = doRead() // 读消息
if request.isComplete() {
doProcess()
}
} elif event.isWriteable {
doWrite() // 写消息
}
}
}