一.Java新IO概述
1.传统的输入, 输出流都是通过字节的移动来处理的, (即使不直接去处理字节流, 但底层的实现还是依赖于字节处理). 从JDK1.4开始, Java提供了一系列 改进的输入/输出处理的新特性, 这些功能被统称为新IO, 这些类都被放在java.nio 包及其子包下.
2. 新IO和传统IO有相同的目的, 都是用于进行输入/输出功能, 但新IO使用了不同的方式来处理输入输出, 新IO采用内存映射文件的方式来处理输入输出, 新IO将文件或文件中的一段区域映射到内存中, 这样就可以像访问内存一样来访问文件(模拟了操作系统上虚拟内存的概念), 通过这种方式来进行输入输出比传统的输入输出要快得多.
3. Channel(通道)和Buffer(缓冲)是新IO中的两个核心对象, Channel是对传统输入输出系统中的模拟, 在新IO系统中所有数据都需要通过通道传输; Channel与传统的InputStream, OutputStream最大的区别在于它提供了一个map方法, 通过该map方法可以直接将"一块数据"映射到内存中. 如果说传统的输入输出系统是面向流的处理, 而新IO则是面向块的处理.
4.Buffer可以被理解成一个容器, 它的本质是一个数组, 发送到Channel中的所有对象都必须首先放到Buffer中去, 而从Channel中读取的数据也必须先读到Buffer中.
5.除了Channel和Buffer之外, 新IO还提供了用于将UNICODE字符串映射成字节序列以及逆映射操作的Charset类, 还提供了用于支持非阻塞式输入输出的Selector类.
二.Buffer
1.从内部结构上看Buffer就像一个数组, 它可以保存多个类型相同的数据. Buffer是一个抽象类, 其最常用的子类是ByteBuffer, 它可以在底层字节数组上进行get/set操作, 除了ByteBuffer之外, 对应其他基本数据类型(boolean类型)都有相应的Buffer类: ByteBuffer, CharBuffer, ShortBuffer, IntBuffer, LongBuffer, DoubleBuffer, FloatBuffer.
上面这些Buffer类, 除了ByteBuffer之外, 它们都采用相同或相似的方法来管理数据, 只是各自管理的数据类型不同. 这些Buffer都没有提供构造器, 通过如下方法来得到一个Buffer对象:
static XxxBuffer allocate(int capacity):创建一个容量为capacity的XxxBuffer对象
ByteBuffer和CharBuffer在实际开发中使用较多, 其中ByteBuffer类的一个子类: MappedByteBuffer, 它用于表示Channel将磁盘文件的部分或全部内容映射到内存中后得到的结果, 通常MappedByteBuffer对象由Channel的map方法返回.
2.Buffer中三个重要的概念:
容量(capacity), 界限(limit), 位置(position)
容量: 缓冲区的容量表示该Buffer的最大数据容量, 缓冲区的容量不能为负, 在创建后也不能改变.
界限: 第一个不应该被读出或者写入的缓冲区位置索引, 位于limit后的数据既不可读, 也不可写.
位置: 用于指明下一个可以被读出的或者写入的缓冲区位置索引, 当刚刚新建一个Buffer对象时, 其position为0 , 如果从Channel中读取了2个数据到该Buffer中, 则position为2.
3.Buffer的主要作用就是装入数据, 然后输出数据, 开始时Buffer的position为0, limit为capacity, 程序调用put不断向Buffer中放入数据, 每放一些数据, position相应的向后移动一些位置.
4. flip方法: 该方法将limit设置为position所在位置, 将position设置为0 , 当Buffer调用flip方法后, Buffer为输出数据做好了准备.
clear方法: 当Buffer输出数据结束之后, 调用clear方法, clear方法不是清空Buffer的数据, 它仅仅将position置为0, 将limit置为capacity, 再次向Buffer中装入数据做好准备.(执行clear方法之后,Buffer缓冲区内容并没有被清除, 还可以继续访问它)
5.当使用put和get来访问Buffer中的数据时, 分为绝对和相对两种:
相对:从Buffer的当前位置读取或者写入数据, 然后将位置(position)的值按处理元素的个数增加
绝对:直接根据索引来向Buffer中读取或者写入数据, 使用绝对方式来访问Buffer里的数据时, 并不会影响position的值.
三.Channel
1.Channel类似于传统的流对象, 但与传统的流不同的是, Channel有两个主要的区别:
(1)Channel可以直接将指定文件的部分或全部直接映射成Buffer.
(2)程序不能直接访问Channel中的数据, 包括读取, 写入都不行, Channel只能与Buffer进行交互. 如果要从Channel中取得数据, 必须先使用Buffer从Channel中取出这些数据; 如果要将程序中的数据写入Channel中, 一样先让程序将数据放入Buffer中, 再将Buffer里的输入写入Channel中.
2. Channel是一个接口, 位于java.nio.channels包下. 系统为该接口提供了很多子实现类.
3. 所有的Channel都是通过传统的节点InputStream, OutputStream的getChannel 方法来返回对应的Channel. 不同的节点流获得的Channel不一样.
4. Channel中最常用的三类方法是map, read和write. map方法用于将Channel对应的部分或全部数据映射成ByteBuffer; 而read或write方法都有一系列重载形式, 这些方法用于从Buffer中读取数据或者向Buffer里写入数据.
四.编码集和Charset
计算机里的所有文件在底层都是二进制文件, 即全部都是字节码. 对于文本文件而言, 之所以我们可以看到一个一个的字符, 这完全是因为系统将底层的字节序列转换成字符序列的缘故. 在这个过程中涉及两个概念: 编码(Encode)和解码(Decode). 把明文的字符串序列转换成计算机理解的字节序列(二进制文件)称为编码, 把字节序列转换成能看懂的明文字符串称为解码.
对于一份文本文件, 必须采用合适的字符集来解码它, 才可以将这份文本文件解码成正确的文本内容. 使用强制的字符集来解码将看到乱码现象.
JDk1.4提供了Charset来处理字节序列和字符序列之间的转换关系. Charset类是不可变的.
<***>GBK---->一个中文对应两个字节
utf-8----->一个中文对应三个字节
获得Charset对象之后可以通过该对象的newEncoder(), newDecoder()方法分别返回CharsetEncoder和CharsetDecoder对象, 代表该Charset的解码器和编码器. 调用CharsetDecoder的decode方法就可以将ByteBuffer(字节序列)转换成CharBuffer(字符序列), 调用CharsetEncoder的encode方法就可以将CharBuffer或String转换成ByteBuffer(字节序列).