Java中的NIO学习(一、缓冲区与Buffer)

这里有一个很好的NIO系列学习教程: http://ifeve.com/overview/

看完之后感觉主要就讲了以下一些东西。

Channel 通道

Buffer 缓冲区

Selector 选择器

其中Channel对应以前的流,Buffer不是什么新东西,Selector是因为nio可以使用异步的非堵塞模式才加入的东西。

以前的流总是堵塞的,一个线程只要对它进行操作,其它操作就会被堵塞,也就相当于水管没有阀门,你伸手接水的时候,不管水到了没有,你就都只能耗在接水(流)上。

nio的Channel的加入,相当于增加了水龙头(有阀门),虽然一个时刻也只能接一个水管的水,但依赖轮换策略,在水量不大的时候,各个水管里流出来的水,都可以得到妥善接纳,这个关键之处就是增加了一个接水工,也就是Selector,他负责协调,也就是看哪根水管有水了的话,在当前水管的水接到一定程度的时候,就切换一下:临时关上当前水龙头,试着打开另一个水龙头(看看有没有水)。

当其他人需要用水的时候,不是直接去接水,而是事前提了一个水桶给接水工,这个水桶就是Buffer。也就是,其他人虽然也可能要等,但不会在现场等,而是回家等,可以做其它事去,水接满了,接水工会通知他们。

这其实也是非常接近当前社会分工细化的现实,也是统分利用现有资源达到并发效果的一种很经济的手段,而不是动不动就来个并行处理,虽然那样是最简单的,但也是最浪费资源的方式。

下面进入正题

在整个java的IO中,大部分操作都属于阻塞性操作,例如,键盘输入数据,程序必须一直等待输入数据,否则程序无法向下继续执行,还有就是网络中Socket程序必须通过accept()方法一直等待用户的连接。这样一来,势必造成大量系统资源的浪费。所以JAVA在jdk1.4之后增加了新IO,(说新,现在一点都不新了啊),NIO的操作基本上都是使用缓冲区完成的。

既然使用了缓冲区,那么操作的性能将是最高的。

缓冲区Buffer

在基本IO操作中,所有的数据都是以流的形式操作的,而在NIO中,则都是使用缓冲区,所有的读写操作都是使用缓冲区完成的。缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。

java.nio.Buffer下有七个子类,这些子类缓冲区分别用于存储不同类型的数据。

分别是:ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer ,都是针对基本数据类型,没有针对布尔型的。

(在ByteBuffer下还有一个MappedByteBuffer,用于表示内存映射文件)

在Buffer中的函数

public final int capacity()              //Returns this buffer's capacity.

public final int position()              //Returns this buffer's position.

public final Buffer position(int newPosition)      //Sets this buffer's position. If the mark is defined and larger than the new position then it is discarded.

public final int limit()              //Returns this buffer's limit.

public final Buffer limit(int newLimit)       //Sets this buffer's limit. If the position is larger than the new limit then it is set to the new limit. If the mark is defined and larger than the new                           limit then it is discarded.

public final Buffer flip()            //Flips this buffer. The limit is set to the current position and then the position is set to zero. 

public final int remaining()          //Returns the number of elements between the current position and the limit.

……

capacity的含义总是一样的,返回的是缓冲区的大小。而position和limit的含义取决于Buffer处在读模式还是写模式。

capacity

作为一个内存块,Buffer有一个固定的大小值,也叫"capacity".你只能往里写capacity个byte、long,char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据。

position

当你写数据到Buffer中时,position表示当前可写入的位置,既此指针永远放到写入的最后一个元素之后(例如,你已经写入四个元素,那么此指针将指向第五个位置)。初始的position值为0.当一个byte、long等数据写到Buffer后, position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity – 1.(其实就是一个数组)

当读取数据时,也是从某个特定位置读。当将Buffer从写模式切换到读模式,position会被重置为0. 当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。

limit

在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。

当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)

flip()方法

flip方法将Buffer从写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值。

换句话说,position现在用于标记读的位置,limit表示之前写进了多少个byte、char等 —— 现在能读取多少个byte、char等。

下面通过一段简单代码看看它的功能。

import java.nio.IntBuffer;

public class IntBufferDemo {
    public static void main(String[] args) {
        IntBuffer buf = IntBuffer.allocate(10) ;    // 准备出10个大小的缓冲区
        System.out.print("1、写入数据之前的position、limit和capacity:") ;
        System.out.println("position = " + buf.position() + ",limit = " + buf.limit() + ",capacty = " + buf.capacity()) ;
        int temp[] = {5,7,9} ;              // 定义一个int数组
        buf.put(3) ;                        // 设置一个数据
        buf.put(temp) ;                     // 此时已经存放了四个记录
        System.out.print("2、写入数据之后的position、limit和capacity:") ;
        System.out.println("position = " + buf.position() + ",limit = " + buf.limit() + ",capacty = " + buf.capacity()) ;

        buf.flip() ;                        // 重设缓冲区  postion = 0 ,limit = 原本position
        System.out.print("3、准备输出数据时的position、limit和capacity:") ;
        System.out.println("position = " + buf.position() + ",limit = " + buf.limit() + ",capacty = " + buf.capacity()) ;
        System.out.print("缓冲区中的内容:") ;
        while(buf.hasRemaining()){           //如果缓冲区中还有内容
            int x = buf.get() ;
            System.out.print(x + "、") ;
        }
    }
}

输出结果:    缓冲区中的内容:3、5、7、9

缓冲区操作细节如下图所示:

A:开辟缓冲区

B:向缓冲区中增加一个数据

C:向缓冲区中增加一组数据

D:执行flip()方法,limit设置为position,position设置为0

注:0 <= position <= limit <= capacity

另一些其他的重要方法:也可直接查看原API文档   http://docs.oracle.com/javase/8/docs/api/java/nio/Buffer.html 

rewind()方法

Buffer.rewind()将position设回0,所以你可以重读Buffer中的所有数据。limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)。

clear()与compact()方法 

一旦读完Buffer中的数据,需要让Buffer准备好再次被写入。可以通过clear()或compact()方法来完成。 

如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值。换句话说,Buffer 被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。 

如果Buffer中有一些未读的数据,调用clear()方法,数据将“被遗忘”,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。 

如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先写些数据,那么使用compact()方法。

compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。

mark()与reset()方法 

通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position。

也可创建只读缓冲区 buf.asReadOnlyBuffer()

public static void main(String args[]){
        IntBuffer buf = IntBuffer.allocate(10) ;    // 准备出10个大小的缓冲区
        IntBuffer read = null ;    // 定义子缓冲区
        for(int i=0;i<10;i++){
            buf.put(2 * i + 1) ;    // 在主缓冲区中加入10个奇数
        }
        read = buf.asReadOnlyBuffer()  ;// 创建只读缓冲区
        


        read.flip() ;    // 重设缓冲区
        System.out.print("主缓冲区中的内容:") ;
        while(read.hasRemaining()){
            int x = read.get() ;
            System.out.print(x + "、") ;
        }
        read.put(30) ;    // 修改,错误
    }

大部分放的部分都是从网上直接抄的, 代码部分是自己写的。

原文地址:https://www.cnblogs.com/maydayit/p/4250326.html