NIO概述
我们的程序再读取数据的时候是按照:
程序执行效率更多是由I/O效率决定的
在JDK4 中引入了NIO,可以最大限度的满足Java程序I/O的需求
NIO中有三大核心组件:Channel、Buffer、Selector
NIO与传统IO的区别
- 传统的IO是面向流的,每次从流中读取一个或多个字节。只能向后读取,不能向前读取
- NIO是面向缓冲区的,把数据读取到一个缓冲区中,可以在缓冲区中向前/向后移动,增加了程序的灵活性
-
在NIO中,所有的数据都需要通过channel传输,通道可以直接将一块数据映射到内存中,Channel是双向的,可以读取数据,也可以保存数据。程序不能直接读写Channel通道,Channel只与Buffer缓冲区进行交互。
读取模型:
- IO流是线程阻塞的,当我们在调用read()/write()读写数据时,线程会阻塞。直到数据读取完毕或者数据完全写入,在读写过程中,线程不能做其他的任务。而NIO不是线程阻塞的,当线程从Channel读取数据的时候如果通道中没有可用的数据,线程不会阻塞。线程可以做其他的任务
Buffer
Buffer缓冲区实际上就是一个数组,把数组的内容与信息包装成一个Buffer对象,它提供了一组访问这些信息的方法。缓冲区Buffer本质上就是一个数组
Buffer的属性
- capacity:容量,是指缓冲区可以存储多少个数据。容量在创建Buffer缓冲区时指定大小,创建后不能再修改
- 如果缓冲区满了,需要清空缓冲区后才能继续写数据
- position:表示当前位置,即缓冲区写入/读取的位置,刚创建Buffer之后,position初始化为0,写入一个数据后,position就向后移动一个单元,最大值是capacity-1
- 当buffer从写模式切换到读模式:position会被重置为0,从buffer的开始位置读取数据,每读取一个数据,position就向后移动一个单元
- limit:指第一个不能读出或写入的位置。limit上限后面的单元既不能读也不能写。
- 在Buffer缓冲区的写模式下,limit表示能够写入多少个数据
- 在Buffer缓冲区的读模式下,limit表示最多可以读取多少个数据
- mark标记:设置一个标记位置,可以调用mark()方法,将标记设置在对应的position位置。当调用reset()方法时,就把position设置为mark标记的位置
- 0<= mark <= position <=limit <=capacity
下面我们用一个图说明
然后向缓冲区中存储6个字符
调用flip()将缓冲区由写模式切换为读模式
- 切换模式后,position重新置为0,limit置为position的位置
Buffer的常用API
在NIO中关键的Buffer:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer。这些Buffer覆盖了能够通过I/O发送的所有基本类型:byte、char、double、float、int、long、short。开发中常用的是ByteBuffer、CharBuffer
- allocate(capacity):可以创建一个指定容量的缓冲区
- put():用于向缓冲区中存储数据
- get():用于从缓冲区中读取数据
- compact():压缩数据。当缓冲区中还有未读完的数据,可以调用compact()进行压缩,将所有未读取的数据复制到Buffer的起始位置,把position设置到最后一个未读元素的后面。limit属性设置为capacity
- capacity():返回缓冲区的大小
- hasRemaing():用于判断当前的position后面是否还有可处理的数据,即判断position与limit是否还有数据
- limit():返回limit上限的位置
- mark():设置缓冲区的标志位置,这个值只能在0~position之间,可以通过reset()返回到这个位置
- position():可以返回position当前位置
- remaining():返回当前position位置与limit之间的数据个数
- reset():将position设置到mark标记的位置
- rewind():将position设置为0,然后取消mark标记
- clear():清空缓冲区,仅仅是修改position标志为0,设置limit为capacity,缓冲区的数据还是存在的
- flip():可以把缓冲区由写模式切换到读模式,写模式切换到读模式后,先把limit设置为position位置,再把position设置为0。
- remaining():返回当前position到limit的数量
code演示
//创建CharBuffer缓冲区对象
CharBuffer charBuffer = CharBuffer.allocate(12);
//输出Capacity、position、limit
System.out.println("capacity" + charBuffer.capacity() + "position" + charBuffer.position() + "limit" + charBuffer.limit());//输出capacity12position0limit12
//向缓冲区中存储数据 put
charBuffer.put('D');
charBuffer.put('L');
charBuffer.put('6');
charBuffer.put('5');
charBuffer.put('0');//positon指到5的索引
System.out.println("capacity" + charBuffer.capacity() + "position" + charBuffer.position() + "limit" + charBuffer.limit());
//切换缓冲区的读写模式
charBuffer.flip();
//输出limit 5 positon 0 capacity 12
System.out.println("capacity" + charBuffer.capacity() + "position" + charBuffer.position() + "limit" + charBuffer.limit());
//调用get方法读取数据 get从position读取
System.out.println(charBuffer.get());
//读取一个数据后,position会向后移动
System.out.println(charBuffer.position());//输出1
//再向buffer中存储数据,由于此时position为1所以此时存储数据 数据将会存储在1的位置
charBuffer.put('X');
System.out.println(charBuffer.position());//输出2
//设置一个mark标记
charBuffer.mark();
//再读取一个字符
System.out.println(charBuffer.get());//取出的数据是6 position继续后移 position3
System.out.println(charBuffer.position());
//调用reset方法,重置position为mark标记位置
charBuffer.reset();//position设置为mark位置 position为2
System.out.println(charBuffer.position());
//调用compact进行压缩,压缩会把buffer中未读取的数据复制到position位0的位置
charBuffer.compact();//未读取的数据位L50
System.out.println(charBuffer.position());//Position 为3
//调用clear 清空缓冲区 清空后缓冲区的数据还在
charBuffer.clear();
System.out.println(charBuffer.position());
缓冲区的批量传输
public class NIOTest02 {
public static void main(String[] args) {
CharBuffer charBuffer = CharBuffer.allocate(17);
//批量存储到缓冲区中
charBuffer.put("hello who are you");
//切换读模式
charBuffer.flip();
System.out.println(charBuffer);
System.out.println(charBuffer.capacity() + "position" + charBuffer.position() + "limit" +
charBuffer.limit());
//创建数组存储缓冲区中的数据
char[] dst = new char[14];
/* //批量传输时的大小是固定的,如果没有指定大小,意味着把数据填满数组中
CharBuffer remainCharBuffer = charBuffer.get(dst);
//输出 hello who are
System.out.println(dst);
//输出 you 剩余的数据
System.out.println(remainCharBuffer);
//remaining表示剩余数量
charBuffer.get(dst,0,charBuffer.remaining());
System.out.println(Arrays.toString(dst));*/
//循环读取缓冲区的数据
charBuffer.clear();
while (charBuffer.hasRemaining()) {
int remaining = charBuffer.remaining();
int len = Math.min(dst.length, remaining);
charBuffer.get(dst, 0, len);
System.out.println(new String(dst, 0, len));
}
//批量写入数据
char[] contents = {'a', 'b', 'c', 'd'};
//把字符数组中的数据保存到缓冲区中,空间不够会报异常
// charBuffer.put(contents);
//调整position
charBuffer.position(14);
charBuffer.put(contents, 0, charBuffer.remaining());
charBuffer.flip();
System.out.println(charBuffer);
}
}
缓冲区的创建方式
- 分配操作 allocate() 分配一个私有的指定容量大小的数组来存储元素
- 包装操作创建缓冲区 使用提供的数组作为存储空间来存储缓冲区中的数据,不再分配其他空间
//分配方式创建缓冲区
CharBuffer charBuffer = CharBuffer.allocate(16);
//包装方式创建缓冲区
char[] myArray = new char[16];
CharBuffer wrapBuffer = CharBuffer.wrap(myArray);
我们通过put方式向缓冲区中保存数据,会直接影响到数组
对数组进行任何修改,也会影响缓冲区对象
char[] myArray = new char[16];
CharBuffer wrapBuffer = CharBuffer.wrap(myArray);
//调用put向缓冲区中添加数据
wrapBuffer.put("hello");
//查看数组
System.out.println(Arrays.toString(myArray));
//对数组进行修改,看是否会影响缓冲区
myArray[0]='x';
//读取buffer中的数据
wrapBuffer.flip();
wrapBuffer.position(0);
System.out.println(wrapBuffer);
无论使用allocat()还是通过wrap()创建的缓冲区都是间接的,简介缓冲区都会使用备份数组
- hasArray() 可以判断是否有一个可存取的备份数组 如果返回true,可以通过array()返回缓冲区对象使用的备份数组的引用
//判断是否有备份数组
if (wrapBuffer.hasArray()) {
//获取备份数组
char[] array = wrapBuffer.array();
System.out.println(array);
}
缓冲区的复制与分割
- duplicate():复制一个缓冲区
//创建缓冲区
CharBuffer charBuffer = CharBuffer.allocate(16);
//将数据存储到缓冲区
charBuffer.put("hello");
//position = 5
System.out.println(charBuffer.position()+"capacity"+charBuffer.capacity()
+"limit"+charBuffer.limit());
//缓冲区的复制
CharBuffer duplicate = charBuffer.duplicate();
System.out.println(duplicate.position()+"capacity"+duplicate.capacity()
+"limit"+duplicate.limit());
duplicate.flip();
System.out.println(duplicate);
实际上 charbuffer与duplicate使用的是同一个数组
//charbuffer与duplicate使用的是同一个数组
duplicate.clear();
duplicate.put("NIOworld");
//存储数据后,查看charbuffer中的数据是否有变化
charBuffer.flip();
System.out.println(charBuffer);
- slice():分割缓冲区,根据[position、limit)的区间创建一个新的缓冲区
//分割缓冲区,设置dulicate的position为3
duplicate.position(3);
CharBuffer sliceBuffer = duplicate.slice();
System.out.println("capacity"+sliceBuffer.capacity()+"position" + sliceBuffer.position() + "limit" + sliceBuffer.limit());
System.out.println(sliceBuffer);