NIO是jdk1.4后引入,是为了解决普通IO效率慢的问题。NIO将最耗时的IO操作(提取和填充缓冲区)转移回操作系统,让操作系统来完成这个操作因此可以极大的提高效率。NIO是以块为单位处理数据,传统IO是以字节为单位,所以NIO更快。
NIO是通过通道Channel来读取和写入数据的,通道就可以类比传统的IO流,通道是直接和操作系统对接的,我们不会直接从通道中读取和写入数据,而是通过缓冲区Buffer。读取数据时,不会直接从通道中读取数据,而是将数据从通道读入到缓冲区,再从缓冲区拿数据。写数据也一样,通过缓存写入到通道中。
我们首先需要做的就是处理好缓冲区,缓冲区其实就是对数组进行了一定封装,这样整体处理起来比较方便。Java提供的缓冲区类是Buffer,使用时都是使用其子类,比如ByteBuffer,CharBuffer,每种基本数据类型都有其对应的缓冲类型(boolean除外)。
下面看看如何使用,以ByteBuffer举例
首先要说的是四个属性:mark,position,limit,capacity
mark是一个标记,调用reset方法可以让position重新回到mark位置
position是当前位置,指向缓存数组
limit是限制指针,通过Buffer读和写都是从position到limit,不能超过limit
capacity是当前缓存的容量大小
四个指针的关系 mark<=position<=limit<=capacity
这四个怎么用,先看这几个操作,几个例子就明白了
初始化
//ByteBuffer是通过分配内存来初始化的,这样就会new出来一个子类HeapByteBuffer,在堆上创建该对象
//感兴趣的看看源码
//分配缓冲区空间:创建一个字节缓冲区,申请内存空间为8字节
ByteBuffer byteBuffer = ByteBuffer.allocate(8);
//在堆外创建对象,不受垃圾回收的限制
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
//通过字节数组初始化ByteBuffer对象
public static ByteBuffer wrap(byte[] array,int offset, int length)
public static ByteBuffer wrap(byte[] array)
写入数据
//向缓冲区中写入一个字节数据
//还有其他重载方法:指定位置向缓冲中添加字节,向缓存中添加字节数组,向缓存中添加另外一个缓存
byteBuffer.put((byte)10);
//指定位置向缓冲区中添加字节,在3号位置添加10
byteBuffer.put(3,(byte)10);
//向一个缓存中添加另外一个缓存
byteBuffer2.put(byteBuffer);
需要注意的是指定索引添加元素不会引起position位置的变化,不指定索引position位置会加1
获取缓存中的值
//获取缓冲中的数据,通过获取index位置的数据
byte b = byteBuffer.get(index);
//不指定索引获取缓冲数据
同样注意的是,不指定索引会引起position位置的变化,获取指定位置的数据不会引起position位置的变化
get操作源码:
还有几个函数需要了解一下
position():返回当前position的值
limit():返回当前limit的值
capacity():返回当前capacity的值
hasRemaining():返回position和limit之间是否有元素 即: return position < limit
remaining():返回position和limit之间的元素数 即:return limit - position
通过例子来了解上面的三个属性是做什么用的
public class NIO_Demo {
public static void main(String[] args) {
//分配缓冲区空间:创建一个字节缓冲区,申请内存空间为8字节
ByteBuffer byteBuffer = ByteBuffer.allocate(8);
//向缓冲区中写入数据
byteBuffer.put((byte)10);
byteBuffer.put((byte)20);
byteBuffer.put((byte)30);
byteBuffer.put((byte)40);
//输出三个状态
System.out.println("position:"+byteBuffer.position());
System.out.println("limit:"+byteBuffer.limit());
System.out.println("capacity:"+byteBuffer.capacity());
//hasRemaining()告知在当前位置和限制之间是否有元素
if(byteBuffer.hasRemaining()) {
//remaining()返回当前位置与限制之间的元素数
for (int i = 0; i < byteBuffer.remaining(); i++) {
byte b = byteBuffer.get(i);
System.out.println(b);
}
}
}
}
可以看到当写入4个字节数后,position已经处于4号位置,capacity是8很好理解,因为分配了8个字节的内存,那么limit为什么也是8,有什么用?
limit的作用:为什么上面说limit是一个中转指针,当需要把缓存中数据写入到内核空间时,数据的有效范围是从position到limit,那么现在来说就是从4到8,可是很明显4-8是没有数据的,数据放在0-4,这个时候limit就要起作用了,不过需要调用一个函数flip
flip():反转此缓冲区 操作很简单: limit=position,position=0
执行完这个函数,拿到的值就正确了。
同样是上面的例子
System.out.println("position:"+byteBuffer.position());
System.out.println("limit:"+byteBuffer.limit());
System.out.println("capacity:"+byteBuffer.capacity());
//缓冲区的反转:转为有效数组 limit=position position=0 有效数据为position-limit
byteBuffer.flip();
//输出三个状态
System.out.println("position:"+byteBuffer.position());
System.out.println("limit:"+byteBuffer.limit());
System.out.println("capacity:"+byteBuffer.capacity());