01 Java 标准IO
Java 标准IO
1 概览

本篇文章的范围应该是涵盖java.io包中的所有类。
IO定义
该理解方式非常有价值。为什么IO的同步和异步如此重要?因为IO操作是整个调用链路上的性能瓶颈。普通的A函数调用B函数,为什么不采用异步操作呢,因为函数都是计算任务,都在内存完成。所以所有的操作都可以分为两种:计算操作(非IO操作,内存中即可完成)和IO操作(从其他设备中读取、写入数据)。计算操作是使用CPU的,IO操作过程中CPU线程是挂起的,等待中。函数、调用可以分为两种,正常调用和IO调用。
缓冲区以及如何处理缓冲区是所有I / O的基础。 术语“输入/输出”仅意味着将数据移入和移出缓冲区。 只要时刻牢记这一点即可。 通常,进程通过请求操作系统从缓冲区中清空数据( write operation )或向缓冲区中填充数据( read operation )来执行I / O。 以上是I / O概念的全部摘要。

上图显示了块数据如何从外部源(例如硬盘)移动到正在运行的进程(例如RAM)内部的存储区的简化“逻辑”图。
- 首先,该进程通过进行read()系统调用来请求填充其缓冲区。
- 此调用导致内核向磁盘控制器硬件发出命令以从磁盘获取数据。 磁盘控制器通过DMA将数据直接写入内核内存缓冲区,而无需主CPU的进一步协助。
- 磁盘控制器完成缓冲区填充后,当它请求read()操作时。内核将数据从内核空间中的临时缓冲区复制到进程指定的缓冲区中;
- 需要注意的一件事是内核尝试缓存和/或预取数据,因此进程请求的数据可能已经在内核空间中可用。 如果是这样,则将过程所请求的数据复制出来。 如果数据不可用,则该过程将在内核将数据带入内存时挂起。
虚拟内存
虚拟内存具有两个重要优点:
1)多个虚拟地址可以引用相同的物理内存位置。
2)虚拟内存空间可以大于可用的实际硬件内存。
对于第一个特性,通过地址映射mmap,将内核空间逻辑地址与用户空间中虚拟地址映射到相同物理空间。DMA硬件(只能访问物理内存地址)可以填充一个缓冲区,该缓冲区同时对内核和用户空间进程可见。消除了内核空间和用户空间之间的副本,

对于第二个特性,进行虚拟内存分页(通常称为交换)。将虚拟内存空间的页面持久保存到外部磁盘存储中,从而在物理内存中为其他虚拟页面腾出空间。物理内存充当页面调度区域的缓存,当虚拟内存的不在物理内存中时,由物理内存从磁盘空间交换。
IO的分类
Java 的 I/O可以根据如下方式进行分类。


根据内容的不同可以分为:
- 字节操作:InputStream 和 OutputStream
- 字符操作:Reader 和 Writer
- 输入流 InputStream、Reader
- 输出流 OutputStream、Writer
- 字节转字符:InputStreamReader/OutputStreamWriter
根据对象的不同可以分为:
- 文件操作:File
- 管道操作:Piped
- 数组操作:ByteArray&CharArray
- 对象操作:Object
- 过滤操作:Filter添加额外特性
- Bufferd
- Data
- PushBack
- LineNumber
- 网络操作:Socket
根据IO模型的不同可以分为:
- 同步阻塞IO:BIO
- 异步IO:NIO
- 异步IO:AIO
根据IO的原理可以分为:
- Block IO 块IO
- Stream IO 流IO
装饰者模式
Java I/O 使用了装饰者模式来实现。以 InputStream 为例,
- InputStream 是抽象组件;
- FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作;
- FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能。例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。
实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。
1 | FileInputStream fileInputStream = new FileInputStream(filePath); |
DataInputStream 装饰者提供了对更多数据类型进行输入的操作,比如 int、double 等基本类型。
2 字节操作

InputStream
- 基本的InputStream
1 | int read() |
- FileInputStream
1 | FileInputStream(File file) |
- PipedInputStream
1 | PipedInputStream(int pipeSize) |
- ByteArrayInputStream
1 | ByteArrayInputStream(byte[] buf) |
- ObjectInputStream 持久化对象。用ObjectOutputStream写出,就用这个读入
1 | ObjectInputStream(InputStream in) |
- FilterInputStream->BufferedInputStream
1 |
OutputStream
- 基本的OutputStream
1 | close() |
- FileOutputStream
1 | FileOutputStream(File file) |
- PipedOutputStream
1 | PipedOutputStream(PipedInputStream snk) |
- ByteArrayOutputStream,自带一个字节缓冲区
1 | ByteArrayOutputStream(int size) |
- ObjectOutputStream
1 | ObjectOutputStream(OutputStream out) |
FilterOutputStream->BufferedOutputStream
PrintStream一个特殊的包装器类
1 | print(基础类型和对象) |
3 字符操作

编码与解码
编码就是把字符转换为字节,而解码是把字节重新组合成字符。
如果编码和解码过程使用不同的编码方式那么就出现了乱码。
- GBK 编码中,中文字符占 2 个字节,英文字符占 1 个字节;
- UTF-8 编码中,中文字符占 3 个字节,英文字符占 1 个字节;
- UTF-16 be编码中,中文字符和英文字符都占 2 个字节。
UTF-16be 中的 be 指的是 Big Endian,也就是大端。相应地也有 UTF-16le,le 指的是 Little Endian,也就是小端。
Java 的内存编码使用双字节编码 UTF-16be,这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位,也就是两个字节,Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。
String 的编码方式
String 可以看成一个字符序列,可以指定一个编码方式将它编码为字节序列,也可以指定一个编码方式将一个字节序列解码为 String。
1 | String str1 = "中文"; |
在调用无参数 getBytes() 方法时,默认的编码方式不是 UTF-16be。双字节编码的好处是可以使用一个 char 存储中文和英文,而将 String 转为 bytes[] 字节数组就不再需要这个好处,因此也就不再需要双字节编码。getBytes() 的默认编码方式与平台有关,一般为 UTF-8。
1 | byte[] bytes = str1.getBytes(); |
字节字符流转换Reader 与 Writer
不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。
- InputStreamReader 实现从字节流解码成字符流;
1 | InputStreamReader(InputStream in) |
- OutputStreamWriter 实现字符流编码成为字节流。
1 | OutputStreamWriter(OutputStream out) |
实现逐行输出文本文件的内容
1 | public static void readFileContent(String filePath) throws IOException { |
Reader
- 基本的Reader
1 | close() |
- FileReader
1 | FileReader(File file) |
- PipedReader
1 | PipedReader(int pipeSize) |
- CharArrayReader
1 | CharArrayReader(char[] buf) |
- BufferedReader
1 | String readLine() |
Writer
- 基本的Writer
1 | append(char c) |
- FileWriter
1 | OutputStreamWriter(OutputStream out) |
- PipedWriter
1 | PipedWriter(PipedReader snk) |
- CharArrayWriter
1 | size() |
- BufferedWriter
1 | BufferedWriter(Writer out) |
- PrintWriter一个特殊的装饰器类,在write的基础上添加了许多print函数
1 | print(基础类型和对象) |
参考文献
- Eckel B, 埃克尔, 昊鹏, 等. Java 编程思想 [M]. 机械工业出版社, 2002.
- IBM: NIO 入门
- Java NIO Tutorial
- Java NIO 浅析
- IBM: 深入分析 Java I/O 的工作机制
- IBM: 深入分析 Java 中的中文编码问题
- IBM: Java 序列化的高级认识
- NIO 与传统 IO 的区别
- Decorator Design Pattern
- Socket Multicast










