IO系统,是输入(Input)、输出(Output)系统的总称。对于程序语言设计者来说,设计一种好的IO系统是一项艰难的任务。因为IO系统真的很复杂:
1. 输入输出设备种类繁多,例如鼠标、键盘、文件、网络、控制台等
2. 输入输出方式繁多,例如:顺序输入输出、随机输入输出、带缓冲、按字符、按字节、按行等
java作为一种较高层次的面向对象语言,其在设计IO系统的时候,初衷是为了屏蔽底层细节、简单好用,为此设计者创建了大量的类。具有讽刺意味的是,java初学者,甚至一些java从业者,都对如此多的IO类不知所措,因此我觉得有必要对java的IO类库进行一次梳理。
输入和输出
java中使用“流”这个抽象概念,代表任何有能力产出数据的数据源或有能力接收数据的接收对象。“流”屏蔽了实际的I/O设备中处理数据的细节。Java I/O 类库可以分为输入和输出流两部分
输入流
输入流按照输入方式,可以分为两类:字节输入和字符方式。字节输入的基类是InputStream,这是一个抽象类,任何继承InputStream的类都含有名为read()的方法,用于读取单个字节或者字节数组;字符输入的基类的是Reader,任何继承Reader的类都含有名为read()的方法,用于读取单个字符或者字符数组。由于Java的字符编码是Unicode,因此Reader类兼容Unicode和其它类型的字符输入。
字节输入
继承InputStream的类如下所示
字符输入
继承Reader的类如下所示:
输出流
和输入流类似,字节输出的基类是OutputStream,字符输出的基类是Writer。任何自OutputStream或Writer派生的类都含有名为write()的方法,用于读取单个字符或者字符数组。
字节输出
继承OutputStream的类
字符输出
继承Writer的类
在上面的类图中,以Filter开头的类都是装饰器类,用以改变底层流的表现形式。这给我们提供了灵活性,同时也增加了难度。因为我们很少使用单一的流来创建流对象,而是通过叠加多个流对象来方便我们的使用。(装饰器模式)
字节流和字符流的转换是通过适配器类来实现的,InputStreamReader可以把InputStream转换为Reader,OutputStreamWriter可以把OutputStream转换为Writer
标准I/O
程序的所有输入都可以来自于标准输入,所有的输出也都可以发送到标准输出,以及所有的错误信息都可以发送到标准错误
新I/O
新IO,也可以称之为NIO,在jdk 1.4中引入java类库,其目的在于提高速度。主要由两部分组成:通道(Channel)和缓冲区(ByteBuf),ByteBuf是将数据移进移出通道的唯一方式。通道本质上海市字节性质的,ByteBuf需要调用flip()和clear()来为下一次Read和Write做好准备
ByteBuf继承自Buffer,Buffer是由数据和四个索引组成的,这四个索引是:mark(标记),position(位置),limit(界限)和capacity(容量)。对ByteBuf的操作,其实就是对这四个索引的操作
- capacity:即buffer所能承载的最大数据元素个数.
- limit:buffer中可供read操作的边界值.
- position:即当前操作所处于的索引位置,对于read操作值为[0,limit],对于write操作[0,capacity]
- mark:标记,调用mark方法时设置
| 方法 | 含义 |
|---|---|
| capacity() | 返回缓冲区的容量 |
| clear() | 清空缓冲区,将position设置为0,limit设置为容量 |
| flip() | 将limit设置为position,position设置为0 |
| limit() | 返回limit的值 |
| limit(int lim) | 设置limit的值 |
| mark() | 将mark的值设置为position |
| position() | 返回position |
| position(int pos) | 设置position的值 |
| remaining() | 返回limit - position |
| hasRemaining() | 若limit - position>0,则返回true |
使用ByteBuf读取Java基本类型的时候,需要注意大小端问题。大端是高位优先的,小段是低位优先的
Java IO类库中类虽然看起来很多,但分类还是很清楚的,主要用到了两种模式:装饰器模式和适配器模式。只要理解了这两种模式,IO类库用起来简直是事半功倍