Java Nio学习总结(一)

在学习操作系统时,我们都知道DMA(直接内存读取)的作用,以前I/O是调用一次系统中断去告诉cpu我搬来4k的数据块让cpu来计算,
如此循环往复,这样cpu与I/O的交互次数就变得非常多,而DMA是将以大块数据块(远大于4k)全部传输完再去调用中断让cpu工作。
因此在DMA传输任务完成之前,cpu是有大把的事件快活。
我们再来看看java虚拟机,传统的java I/O类 喜欢读取小块字节数据,这时候获取了DMA提供的大块缓冲区,此时java的流数据类就会把
他拆成很多小块。这样效率就大大降低了,因此java推出了 NIO(ByteBuffer对象)。
传统的I/O模型其实也可以移动大量数据,RandomAccessFile持使用基于数组的read( )和write( )方法,这些方法和系统调用相当接近,但
他必须至少保存一份缓冲区拷贝。

I/O

![](http://images2017.cnblogs.com/blog/1202548/201801/1202548-20180128102943225-1943025775.png) 用户空间就是我们常说的目态(非特权区域,不能直接调用系统权利例如读取硬件设备),内核空间就是管态(特权区域,为操作系统所 持)

为什么不能直接从磁盘读取数据到用户空间呢?

因为磁盘上的操作的数据块是固定大小的而用户进程请求的可能是任意大小,甚至是边界不对齐的数据块,这时候需要第三者(内核)来负 责数据分块、组合工作,然后进行转发。

基础概念

进入NIO正题:缓冲区(Buffer) 下面这张图介绍了缓冲区家族成员 ![](http://images2017.cnblogs.com/blog/1202548/201801/1202548-20180128103956459-2039687889.png)

缓冲区的属性:容量,上界,位置,标记。
容量(CapCapacity):缓冲区可以容纳数据的最大数量。
上界(Limit):缓冲区第一个不能读/写的元素。即,当前缓冲区存在元素的个数。
位置(Position):下一个要操作(读/写)的元素,调用get()/put()会更新对应的position.
标记(mark):一个标记,用来记录位置,可以理解用来备忘位置的属性。(注意:在被调用 前是未被定义,如果使用reset()会报错)

上述属性的关系如下:

0 <= mark <= position <= limit <= capacity

Buffer

![](http://images2017.cnblogs.com/blog/1202548/201801/1202548-20180128122548787-2053463519.png) NIO中的类是支持级联调用的(缓冲区的函数将引用返回到它们在(this)上被引用的对象),如下: ``` //级联调用 buffer.mark().position(5).reset(); //代替如下写法 buffer.mark(); buffer.position(5); buffer.reset(); ``` 创建一个容量为10字节缓冲区(ByteBuffer) ``` ByteBuffer buffer = ByteBuffer.allocate(10)
![](http://images2017.cnblogs.com/blog/1202548/201801/1202548-20180128123519412-1131014609.png)
此时他的mark属性如我们之前所说的没有被定义,position初始位置为0,limit和capacity为10(capacity始终不变)
<h4>填充(put函数)</h4>
接下来我借用书中的例子来理解这几个属性的意义:
1.首先我们向创建的缓冲区中put5个元素(大端)

buffer.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l').put((byte)'o');

此时缓冲区的属性如下:(mark依然没有被定义,)
![](http://images2017.cnblogs.com/blog/1202548/201801/1202548-20180128124030959-1763492142.png)
<h4>修改</h4>
我们可以在put的时候添加索引参数进行修改,例如:

buffer.put(0,(byte)'M').put((byte)'w');

此时,缓冲区如下:
![](http://images2017.cnblogs.com/blog/1202548/201801/1202548-20180128124706131-228781043.png)

注意此时limit属性保持不变,因为在没有调用flip()函数之前缓冲保持写状态,下面会介绍翻转
<h4>翻转</h4>
当我们需要将缓冲区的数据读出来,就需要将当前的写状态翻转成读状态,因此我们可以采用以下方式:

buffer.limit(buffer.position()).position(0);

limit将设置为可以读到数据的最大位置后一个元素,可以理解为当前缓冲区数据元素的计数器;
此时将从第一位开始读,因此position设置为0(缓冲区位置从0开始)
上述操作可以用一个函数完成,(flip即翻转函数)

buffer.flip();

翻转后的缓冲区:
![](http://images2017.cnblogs.com/blog/1202548/201801/1202548-20180128125220615-1465498420.png)

rewind()函数和flip()函数类似,但他不会改变limit的位置,只是将position设置为0。
在put和get时要注意不能越界(>=limit),否则会抛出异常。
<h4>释放(clear)</h4>
Clear()函数将缓冲区重置为空状态。它不改变缓冲区中的任何数据元素,而是仅仅将上界设为容量的值,并把位置设回0,这使得缓冲区可以被重新填入.
<h4>压缩</h4>
只需要取出并释放部分数据时,比如我们当前get前两个数据元素,然后重新填充就需要compact函数

buffer.compact();

结果如下:
![](http://images2017.cnblogs.com/blog/1202548/201801/1202548-20180128163728100-423531526.png)
未被读取的数据会被下移使得第一恶元素的位置为0,数据元素2-5被复制到0-3位置。位置4和5不受影响,
但是position只在4位置,4,5    位置的元素是“”死的“”  ,会被之后的put覆盖掉。换言之,现在缓冲区被定位到最后一个存货的元素后面的位置。
上界属性被设置为容量的值,这意味着缓冲区可以重新被填满。
<h4>标记</h4>
标记就如其字面意思一样只是用来标记后续需要的话可以返回,可以当做一个备忘的工具,可以用mark()标记当前位置。使用reset()可使当前位置为定义的标记位置
,但使用前要确保标记已经被定义,否则会抛出 InvalidMarkException异常。同时要和clear()区别开,clear()函数清空缓冲区,而reset()将position设为标记位置。
<h4>缓冲区的比较</h4>
缓冲区的比较类似字符串的比较,使用equals方法来测试缓冲区是否相同,使用comparetTo方法来比较缓冲区。
缓冲区相等的充要条件:
1.首先缓冲区的类型要相等,和字符串不同的是compareTo如果传入一个类型错误的对象会抛出类型强转异常。
2.缓冲区比较的是剩余数据元素是否相等,(即position到limit之间的数据元素)
3.使用get()函数返回的数据序列应该相等。

A.compareTo(B) 返回负数 ,A 小于 B 反之大于。
注意:直到不相等的元素被发现或者到达缓冲区的上界。如果一个缓冲区在不相等元素发现前已经被耗尽,较短的缓冲区被认为是小于较长的缓冲区。不像equals( ),compareTo( )不可交换
<h4>批量移动</h4>
原文地址:https://www.cnblogs.com/shuoli/p/8370676.html