首页 > 代码库 > Java NIO总结
Java NIO总结
引入Java NIO的目的:
引入NIO的目的是为了提高速度(包括存储速度和网络IO速度),因为NIO中采用了结构更接近于操作系统执行IO操作的方式:通道和缓冲器;客户端通过缓冲器与通道交互以进行IO操作,用户通过调用FileInputStream、FileOutputStream、RandomAccessFile的FileChannel来获取通道,将ByteBuffer指定到某个通道,通道(如:FileOutputStream)要么从缓冲器(ByteBuffer)中获取数据,要么通道(FileInputStream、RandomAccessFile)将数据发送到缓冲器中,用户从缓冲器中获取数据。
并非所有的数据流都可以用于产生通道,在Java类库中有三个IO类库(FileInputStream、FileOutputStream、RandomAccessFile)可以用于产生通道FileChannel,而Reader、Writer这种字符模式类则不能用于产生通道,但是java.noi.channel.Channels类提供了实现的方式,用以在通道中产生Channel
产生通道的例子:
将字节存放于ByteBuffer的方法:(1)、使用put()方法直接对他们进行填充,填入一个或者多个字节,或者基本数据类型的值;(2)、使用wrap()方法将自己存在的字节数组包装到ByteBuffer中,一旦如此就不在复制底层的数组,而是直接把它作为所产生的ByteBuffer的存储器,我们称之为数组支持的ByteBuffer。
ByteBuffer操作的注意事项:
(1)、对于只读访问,我们必须显式地使用静态的allocate()方法来分配ByteBuffer,NIO的目标是快速移动大量数据,因此设置ByteBuffer大小非常必要,设置大了会影响其他性能,设置小了会导致速度变慢,具体要看实际操作。
(2)、一旦调用read()来告知FileChannel向ByteBuffer存储字节后,必须调用缓冲器上的flip(),让它做好让别人读取字节的准备,如果还要进一步执行read()操作的话,我们还得调用clear()操作来将position置0.
具体例子如下:
代码解析:
每次read操作之后,就会将数据写入到缓冲器中,flip()则是准备缓冲器以便它的信息可以由write()提取。write()操作之后,信息仍在缓冲器中,接着使用clear()则对所有的内部指针重新安排,以便缓冲器在另一个read()期间能够做好接收数据的准备。
另一种更高效的操作:
NIO中可以使用transferTo()和transferFrom()将一个通道和另一个通道直接相连,例如:
数据转换:
在java.nio.CharBuffer这个类中,有一个toString()方法,功能是:返回一个包含缓冲器中所有字符的字符串。因而可以看做是ByteBuffer具有asCharBuffer()方法。那么ByteBuffer的asCharBuffer怎么用呢?例子如下:
获取基本数据类型:
尽管ByteBuffer只能保存字节类型的数据,但是它具有可以从其所容纳的自己中产生出各种不同基本类型值的方法。向ByteBuffer插入基本数据类型的最简单的方法是:利用asCharBuffer(),asShortBuffer等获得该缓冲器上的视图,然后使用视图的put方法。此方法适用于除ShortBuffer之外的所有基本数据类型,使用ShortBuffer的put方法时需要进行类型转换。
视图缓冲器:
视图缓冲器可以让我们通过某个特定的基本数据类型的视窗查看其底层的ByteBuffer.ByteBuffer依然是实际存储数据的地方,支持着前面的视图,因此,对视图的任何修改都会映射到ByteBuffer中。这使得我们可以很方便地向ByteBuffer插入数据,视图还允许我们从ByteBuffer一次一个地或者成批地(放入数组)读取基本类型值
例如:
字节存放顺序:
不同的机器可能会使用不同的字节排序方式来存储数据,“Big endian”(高位优先)将最重要的字节存放在地址最低位的存储单元,而"Little endian"(低位优先)则是将最重要的字节存放在地址最高的存储单元上。ByteBuffer是以高位优先的形式存储的,并且数据在网上传输也是以高位优先的形式,但是我们可以使用带有参数ByteOrder.BIG_ENDIAN或者ByteOrder.LITTLE_ENDIAN的order()方法改变ByteBuffer的字节排序法。
例如:
使用缓冲器操纵数据:
如果想把一个字节数组写到文件中,那么应该使用ByteBuffer.wrap()方法把字节数组包装起来,然后调用getChannel()方法在FileOutputStream上打开一个通道,接着来自于ByteBuffer的数据写到FileChannel中。例子前面已经举过,这里不再重举。
当然,要注意:ByteBuffer是将数据移进移出通道的唯一方式,我们需要基本类型时,只能创建独立的基本类型缓冲器或者使用ByteBuffer的as方法冲ByteBuffer获得。
缓冲器的细节:
Buffer由数据和可以高效访问以及操纵这些数据的四个索引组成,这四个索引是:mark(标记)、position(位置)、limit(界限)、capacity(容量)。下面是用于设置和抚慰索引以及查询他们的方法。
capacity() | 返回缓冲器容量 |
clear() | 清空缓冲区,将position置为0,limit置为capacity。我们常常调用此方法覆写缓冲区 |
flip() | 将limit设置为position,position设置为0,此方法用于准备从缓冲区读取已经写入的数据 |
limit() | 返回limit的值 |
limit(int lim) | 设置limit的值 |
position() | 返回position的值、position(int pos)为设置position的值 |
remaining() | 返回(limit-position) |
hasRemaning() | 若有介于position和limit之间的元素,则返回true |
在缓冲器中使用插入和提取数据的方法会更新这些索引,用于反映所发生的变化。例如:
SymmetricScramble()方法缓冲器的样子如下:
position指针指向缓冲区的第一个元素,capacity和limit则指向最后一个元素
在程序的SymmetricScramble()方法中,迭代的执行while循环直到position等于limit。一旦调用缓冲器上的相对的get和put函数,position指针就会随之相应改变。
上述程序当执行到while循环时,调用mark()方法来设置mark值,此时缓冲器状态如下:
两个相对的get()调用把前两个字符保存到变量c1和c2中,调用完这两个方法之后,缓冲器如下:
为了实现交换,我们必须要在position=0时写入c2,position=1时写入c1。我们也可以使用绝对的put()方法来实现,或者使用reset()把position的值设为mark的值:
之后的缓冲器如下:
两个put()方法先写c2,接着写c1,写过之后缓冲器状态如下:
在下一次循环迭代期间,将mark设置为position的当前值,设置过后的缓冲器状态:
这个过程将持续到遍历完整个缓冲器。在while循环的最后,position指向缓冲器的末尾。如果要打印缓冲器,只能打印出position和limit之间的字符。因此,如果想要显示缓存器的全部内容,必须使用rewind()把position设置到缓冲器的开始位置。下面是调用rewind()之后缓冲器的状态。
后续待更新...............................