在JDK4中引入了NIO,可以最大限度的满足Java程序I/O的需求
在NIO中有三大核心组件:Channel、Buffer、Selector
传统IO与JavaNIO的区别:
IO | NIO |
---|---|
传统I/O是面向流的,每次可以从流中读取一个或多个字节,只能向后读取,不能向前移动; | NIO是面向缓冲区的,把数据读取到一个缓冲区中,可以在缓冲区中向前/向后移动,增加了程序的灵活性 |
IO流是线程阻塞的,在调用read()/write()读写数据时,线程阻塞,直到数据读取完毕或者数据完全写入,在读取过程中,线程不能做其它操作; | NIO不是线程阻塞的,当线程从Channel中读取数据时,如果通道中没有可用的数据,线程不阻塞,可以做其它的任务 |
/ | 在NIO中,所有数据都需要通过Channel传输,通道可以直接将一块数据映射到内存中,channel是双向的,不仅可以读取数据,还能保存数据,程序不能直接读写channel通道,Channel只与Buffer缓冲区交互,数据通过channel写到buffer,程序从buffer中读数据 |
1、Buffer的属性
属性 | 作用 |
---|---|
capacity | 是指缓冲区可以存储多少个数据,容量在创建buffer缓冲区时指定大小,创建后不能再修改,如果缓冲区满了,需要清空后才能继续写数据 |
position | 表示当前位置,即缓冲区写入/读取的位置,刚刚创建Buffer对象后,position初始化为0,写入一个数据,position就向后移动一个单元,它的最大值为capacity - 1,当Buffer从写模式切换到读模式,position会被重置为0,每读一个数据,position就向后移动一个单元 |
limit | 是指第一个不能被读取或写入的位置,limit上限后面的单元既不能读也不能写,在Buffer缓冲区写模式下,limit表示能够写入多少个数据;在读取模式下,limit表示最多可以读取多少个数据 |
mark | 设置一个标记位置,可以调用mark()方法,把标记设置在position位置,当调用reset()方法时,就把position设置为mark标记的位置 |
2、Buffer的API
API | 作用 |
---|---|
allocate(capacity) | 可以创建一个指定容量的缓冲区 |
put() | 用于向缓冲区中存储数据 |
get() | 用于从缓冲区中读取数据 |
compact() | 当缓冲区还有未读完的数据,可以调用compact()方法进行压缩,将所有未读取的数据复制到Buffer的起始位置,把position设置到最后一个未读元素的后面。limit属性设置为capacity |
capacity() | 返回缓冲区大小 |
limit() | 返回limit上限的位置 |
mark() | 设置缓冲区的标志位置,这个值只能在0 ~ position之间,以后可以通过reset()方法返回到这个位置 |
position() | 可以返回position当前位置 |
remaining() | 返回当前position位置与limit之间的数据量 |
rewind() | 将position设置为0,取消mark标志位 |
clear() | 清空缓冲区,仅仅是修改position标志为0,设置limit为capacity,缓冲区数据还是存在的 |
flip() | 可以把缓冲区由写模式切换到读模式,先把limit设置为position位置,再把position设置为0 |
duplicate() | 缓冲区的复制,之前的缓冲区与实际的缓冲区引用的是同一个数组 |
slice() | 分隔缓冲区,根据[position,limit)区间来创建新的缓冲区 |
3、案例
1)、创建buffer缓冲区
package JavaNIO;
import java.nio.CharBuffer;
import java.util.Arrays;
/*
缓冲区的创建方式
1)、分配操作创建缓冲区,allocate()分配一个私有的、指定容量大小的数组来存储元素
2)、包装操作创建缓冲区,它使用提供的数组作为存储空间来存储缓冲区中的数据,不再分配其他空间
*/
public class CreadBuffer {
public static void main(String[] args) {
//1)、分配操作创建缓冲区
CharBuffer buffer = CharBuffer.allocate(16);
//2)、包装操作创建缓冲区
char[] myarray = new char[16];
//把已存在的数组包装成一个buffer对象
CharBuffer buffer1 = CharBuffer.wrap(myarray);
//通过调用put方法向缓冲区中保存数据,也会直接影响到数组
buffer1.put("hello");
buffer1.flip(); //切换到读模式
System.out.println(buffer1);
System.out.println(Arrays.toString(myarray));
//对数组做的任何修改,也会影响缓冲区对象
/*
不管是allocate()还是wrap()创建的缓冲区都是间接的,间接缓冲区会使用备份数组,
hasArray()方法可以判断是否有一个可存取的备份数组
如果hasArray()返回true,可以通过array()返回缓冲区对象使用的备份数组的引用
备份数组的内容跟原始数组的内容是一致的
*/
if (buffer1.hasArray()){
char[] arr2 = buffer1.array();
System.out.println(Arrays.toString(arr2));
}
}
}
2)、相关API应用
package JavaNIO;
import java.nio.CharBuffer;
public class Buffer {
public static void main(String[] args) {
//1、创建CharBuffer缓冲区对象
CharBuffer buffer = CharBuffer.allocate(12);
//2、打印capacity,limit,position
System.out.println("capacity:" + buffer.capacity()
+ ",limit:" + buffer.limit() + ",position:" + buffer.position());
//capacity:12,limit:12,position:0
//3、向缓冲区存储数据
buffer.put('成');
buffer.put('吉');
buffer.put('思');
buffer.put('汗');
System.out.println("capacity:" + buffer.capacity()
+ ",limit:" + buffer.limit() + ",position:" + buffer.position());
//capacity:12,limit:12,position:4
//4、调用filp()方法把缓冲区切换为读模式
buffer.flip();
System.out.println("capacity:" + buffer.capacity()
+ ",limit:" + buffer.limit() + ",position:" + buffer.position());
//capacity:12,limit:4,position:0
//5、调用get()方法直接读取缓冲区数据
System.out.println(buffer.get());
System.out.println("capacity:" + buffer.capacity()
+ ",limit:" + buffer.limit() + ",position:" + buffer.position());
//capacity:12,limit:4,position:1
//6、再次存储数据,把数据保存在position位置
buffer.put('X');
System.out.println("capacity:" + buffer.capacity()
+ ",limit:" + buffer.limit() + ",position:" + buffer.position());
//capacity:12,limit:4,position:2
//7、设置标记
buffer.mark();
//8、再读取一个字符
System.out.println(buffer.get());
System.out.println("capacity:" + buffer.capacity()
+ ",limit:" + buffer.limit() + ",position:" + buffer.position());
//capacity:12,limit:4,position:3
//9、调用reset(),把position重置为mark标记位置
buffer.reset();
System.out.println("capacity:" + buffer.capacity()
+ ",limit:" + buffer.limit() + ",position:" + buffer.position());
//capacity:12,limit:4,position:2
//10、调用compact()压缩,会把postion后的数据移动到起始位置,limit重置为capacity
buffer.compact();
System.out.println("capacity:" + buffer.capacity()
+ ",limit:" + buffer.limit() + ",position:" + buffer.position());
//11、调用clear清空
buffer.clear();
System.out.println("capacity:" + buffer.capacity()
+ ",limit:" + buffer.limit() + ",position:" + buffer.position());
//12、clear清空后,缓冲区的数据依然存在
while (buffer.hasRemaining()){
System.out.println(buffer.get());
}
}
}
3)、批量读取/写入
package JavaNIO;
import java.nio.CharBuffer;
import java.util.Arrays;
/*
使用缓冲区就是为了提高数据传输效率,一次读写一个字符或一个字节效率并不高,可以进行批量的操作
可以借助于数组,把缓冲区中的一块数据读取到数组中,也可以把数组中的部分内容保存到缓冲区中
*/
public class BatchRead {
public static void main(String[] args) {
//创建CharBuffer
CharBuffer buffer = CharBuffer.allocate(15);
buffer.put("云想衣裳花想容,春风拂槛露华浓");
//切换为读模式
buffer.flip();
System.out.println(buffer);
//定义字符数组
char[] dst = new char[8];
//调用get()方法把缓冲区中的数据读到字符数组中
//注意批量传输时大小总是固定的,如果没有指定传输的大小,意味着把数组填满
CharBuffer remainBuffer = buffer.get(dst);
System.out.println(Arrays.toString(dst));
//[云, 想, 衣, 裳, 花, 想, 容, ,],相当于把前8位写入字符数组中
System.out.println(remainBuffer);
//春风拂槛露华浓,后面再调用get()方法,剩余的就是remainBuffer
//继续把buffer缓冲区的内容读到字符数组
//buffer.get(dst);这时候是会报错的
//当缓冲区的数据量不足以填满整个数组时,会抛出异常,剩下的是"春风拂槛露华浓",只有7个字符
//此时可以调用get的重载,可以指定缓冲区长度
buffer.get(dst,0,buffer.remaining());
System.out.println(dst);
}
}
4)、直接字节缓冲区
//创建直接字节缓冲区
ByteBuffer.allocateDirect()
1、channel概述
2、Scatter/Gather
ByteBuffer buf1 = ByteBuffer.allocate(48);
ByteBuffer buf2 = ByteBuffer.allocate(1024);
ByteBuffer[] bufArray = {buf1,buf2};
Channel.read(bufArray)
read()方法从channel中读取数据,按照在数组中的顺序依次存储到缓冲区中,注意必须在buf1缓冲区满后才能写入buf2.使用scatter/gather处理的数据大小都是固定的