1.NIO概念
我们知道常见的IO是阻塞的且效率低下的,为了解决这个问题引入了NIO,NIO在IO的基础上使用了cha[] 、byte[]进行了封装,采用ByteBuffer类来操作数据,而且针对File和Socket的channel,使用同步非阻塞实现高性能处理。网上一大堆,这里不做过多阐述
2.Buffer类
buffer类是一个抽象类已知子类(除了boolean类型,其他基本类型都有对应的buffer)
StringBuffer在NIO没有涉及,字符缓冲可用CharBuffer来做。
ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer
在NIO中并不是直接操作数据的而是操作的缓冲区。进行CRUD都是操作缓冲区,数据的操作是否正确与缓冲区的操作是否得当有很大关系。
在使用传统的IO时,我们通常使用byte[]、或char[]来提升性能,但是数组的API很少,对于大多数功能使用数据无法达到,这里引入的Buffer类。
3.API部分
在Nio技术的缓冲区中,存在4个核心技术点,分别是:
- capacity 容量
- limit 限制
- position 位置
- mark 标记
这四个值的大小关系如下
0 ≤ mark ≤ position ≤ limit ≤ capacity
capacity介绍
他表示缓冲区包含元素的数量,
capacity不能为负数,且不能更改。
int capacity()返回缓冲区容量。
通过byteBuffer来进行介绍其代码如下所示:
public static void main(String[] args) {
byte[] bytes = new byte[]{1, 2, 3, 4,5};
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
System.out.println(byteBuffer.capacity());
}
源码简单分析,如下所示:
在第一步我们在调用的时候其实调用的是ByteBuffer类的wrap方法,而其实在ByteBuffer类的方法中,拿到HeapByteBuffer子类对象-》这个子类对象负责后续工作。其后续工作就是第三步有调用了super方法,将数组的值传递了过来,此时的capacity的值就是数组的长度
limit介绍
注意:创建buffer时默认limt和capacity是相等的,也就是说没有限制
注意:当limt < mark时则丢弃mark的值
int limit();//返回当前限制下标
Buffer limit(int size);//设置限制的下标
限制存放数据的下标,例如 limit(2)那么从下标为2开始就会不允许存放数据了。
position介绍
position表示指向缓冲区下标的指针,当put时position会自增,当get时缓冲区也会自增,如果mark大于position那么就丢弃当前mark的值。
int position();//返回指针在缓冲区的位置
Buffer position(int position);//设置指针在缓冲区的位置
可以设置和获取position其代码如下所示
@Test
public void test2(){
char[] chars = new char[]{'1','2','3','4'};
CharBuffer buffer = CharBuffer.wrap(chars);
System.out.println("position => "+buffer.position());
buffer.position(2);
buffer.put("d");
System.out.println("position => "+buffer.position());
for (int i = 0; i< chars.length;i++){
System.out.println(chars[i]);
}
}
注意:当设置了position之后又设置了limit,且position > limit 此时会发生什么?
1.当先设置position = 3后设置limit = 2时
此时会将limit的值赋值给position,因为在设置limit的时候会有判断,是不是position > limit
2.当先设置limit = 2,position=3时
直接报错,因为在设置position的时候会抛出异常
int remaining()方法
此方法会返回空闲(没被占用)空间大小,其实就是limit - position
@Test
public void test3(){
float[] floats = new float[]{1.1f,1.2f,1.3f,1.4f,1.5f};
FloatBuffer floatBuffer = FloatBuffer.wrap(floats);
floatBuffer.put(1.11f);
System.out.println(floatBuffer.remaining());
floatBuffer.put(1.22f);
System.out.println(floatBuffer.remaining());
}
mark介绍
方法如下
Buffer mark();
mark的作用是做一个标记,相当于一个传送标记,下次我调用的reset时会传送到这,将position设置为这个位置。要注意:mark不能设置副值,且当mark的值大于position时就失效了会被重置为-1。
测试代码如下
@Test
public void test4(){
char[] chars = new char[]{'1','2','3','4','5','6'};
CharBuffer charBuffer = CharBuffer.wrap(chars);
charBuffer.put('d');
charBuffer.put('o');
System.out.println("position -> "+charBuffer.position());
charBuffer.mark();
charBuffer.get();
charBuffer.position(1);
charBuffer.reset();
charBuffer.put("n");
charBuffer.put('g');
System.out.println("position -> "+charBuffer.position());
charBuffer.reset();
System.out.println("position -> "+charBuffer.position());
}
isReadOnly()判断只读
boolean isReadOnly();//查看此缓冲区是否是只读缓冲区
isDirect()直接缓冲区
boolean isDirect();//判断缓冲区是否是直接缓冲区
什么是非直接缓冲区?
如下,在ByteBuffer操作硬盘的数据时,是与中间缓冲区进行读写操作,这样会造成软件运行效率低下,内存占用增加。,使用非直接缓冲区可以解决这个弊端,ByteBuffer直接与硬盘进行数据交换,增加程序运行效率。
直接缓冲区如下所示
创建非直接缓冲区?
使用 allocate(int capacity)方法来创建
@Test
public void test5(){
ByteBuffer buffer = ByteBuffer.allocate(10);
System.out.println(buffer.isDirect());
}
clear() 还原缓冲区的状态
注意:clear无法清除数据,只是还原limit,position,mark而已,不过下次put数据会覆盖以前数据。
将缓冲区的状态还原,limit设置为capacity的大小,position设置为0,mark标记丢弃,所有一切都还原
final Buffer clear();
flip() 对缓冲区进行反转
当向缓冲区读取数据之后再想读取数据就是使用flip的最佳方式
final buffer flip();//对缓冲区进行反转。
1.设置limit为当前位置
2.将position初始化
3.丢弃mark
测试代码如下所示
@Test
public void test6(){
int[] ints = new int[]{0,1,2,3,4,5,6,7};
IntBuffer buffer = IntBuffer.wrap(ints);
buffer.put(11);
buffer.put(22);
buffer.put(33);
System.out.println("转换前 position -> "+buffer.position()+" limit -> "+ buffer.limit());
buffer.flip();
System.out.println("转换后 position -> "+buffer.position()+" limit -> "+ buffer.limit());
while(buffer.hasRemaining()){
System.out.println(buffer.get());
}
}
hasArray()判断是否有底层实现的数组
final boolean hasArray();//
//源码如下
重绕缓冲区
将位置position设置为0并丢弃标记
rewind方法常在重新读取数据时使用
注意:他和clear()、flip()有区别
clear()意味着还原一切状态
flip()相当于substring截取,将list赋值为position将position置位0,丢弃mark
而rewind()意思是重新读取,重新写入时使用,他不会改变limit的值
final buffer rewind();//
测试代码如下,当调用rewind()此时position会被置为0
@Test
public void test8(){
IntBuffer buffer = IntBuffer.wrap(new int[]{1, 2, 3, 4, 5, 6, 7});
buffer.put(10);
buffer.put(20);
buffer.put(30);
buffer.put(40);
System.out.println(buffer.position());
buffer.rewind();
System.out.println(buffer.position());
}
获得偏移量
返回buffer中实现的数组的第一个数据的偏移量
子类可以不需要添加offset的值,默认为0
final int arryOffset();
如下代码所示,以下的offset都为0,不为0的情况后续在学习
@Test
public void test8(){
IntBuffer buffer = IntBuffer.wrap(new int[]{1, 2, 3, 4, 5, 6, 7});
buffer.put(10);
buffer.put(20);
buffer.put(30);
buffer.put(40);
System.out.println(buffer.arrayOffset());
}