Buffer
IO流
在计算机中涉及到数据传输,使用的一定是IO流
同步和异步
同步:在一个时间段内,只有1个线程可以访问某个资源
异步:在一个时间段内,多个线程可以同时访问资源
阻塞和非阻塞
阻塞:当逻辑没有执行成功或者没有收到预期的结果时,就不在往下执行,而是等待,叫做阻塞。
例如BIO中的socket,其中的connect,write,read,服务器端的accept等方法都是阻塞的
非阻塞:不管逻辑取得的结果是啥都不做等待,继续往下执行
所以,同步不等于阻塞,异步也不等于非阻塞,两个是不同的东西
java中的IO分类
BIO:BlockingIO,同步阻塞式IO,传统IO都是阻塞式IO;同一时间只能一个线程操作流,并且如果没有读到数据或者没有写出数据,会hang住。
NIO: 新IO,或者NonBlockingIO,同步非阻塞式IO;NIO是同步的,保证线程安全,但是NIO也是非阻塞的,读取不到,连接不到,没有写入等都不会等待
AIO:AsynchronousIO,异步非阻塞式IO,jdk1.8提供。 在NIO的基础上做出改进,也叫NIO.2;AIO异步的,可以多个线程一起使用, 而且没有阻塞。 AIO也是效率最高的IO
BIO的缺点
1、阻塞式IO,只要读不到就会一直等,写数据时如果写不出去也会一直等。
2、因为阻塞,所以必须使用多线程。客户端每发起一次连接,服务器端就需要创建一个线程来处理客户端的这个连接。如果服务器端启动的线程数量太多,会导致服务器卡顿
3、无法解决无用连接的问题;如果客户端连接至服务器,不执行任务操作,只是在while(true)的话,会导致大量的无用连接,资源浪费
4、BIO只能用于单向传输,分为输入流和输出流。所以完成一次双向的读写需要创建多个对象。同样会浪费服务器资源。
如下图:服务器接受到一个客户端请求,必须利用子线程来处理这个请求
NIO解决了以上的四个缺点
NIO核心数据存储-Buffer
存储数据可以使用多种结构,变量,数组,链表等。
在NIO中,用于数据存储的就是Buffer,对数据进行 存储 (相当于一节火车,专门存数据的)
Buffer底层是也是数组,就有8中基本数据类型中的7中,提供了以下几种数据传输的容器:
ByteBuffer,ShortBuffer,IntBuffer,LongBuffer,DoubleBuffer,FloatBuffer,CharBuffer
关于buffer的几个重要位置属性
capacity:容量位。用于标记当前缓冲区的容量。
limit:限制位。用于标记操作位所能达到的最大下标。当操作位和限制位重合就表示所有的位置已经操作完。当缓冲区刚创建的时候,limit和capacity是重合的 。
position:操作位。等价于数组中的下标。读写后都会自动后移。无论以何种方式创建的缓冲区,position指向第0位 ;读到哪,写到哪,这个位置就在下一位
mark:标记位。mark的值默认为-1表示不启用
创建缓冲区
//直接分配一个字节数组,并且给定容量
ByteBuffer buffer = ByteBuffer.allocate(10);
向缓冲区中存入数据
// 添加数据,添加一个字节数组
buffer.put("abc".getBytes());
//添加一个字节
buffer.put((byte) 0);
从缓冲区取出数据
//取出position指向的字节
byte b1 = buffer.get();
//当需要发送的数据已知时,可以快速获取Buffer,此时buffer的position还是0
ByteBuffer buffer = ByteBuffer.wrap("hello big1910".getBytes());
//不再时1个字节1个字节的取出,直接获取Buffer底层的字节数组的地址
byte[] data = buffer.array();
"获取底层的字节数组后,需要注意有效数据的位置"
ByteBuffer buffer = ByteBuffer.allocate(100);
buffer.put("hello big1910".getBytes());
byte[] data = buffer.array();
System.out.println(data.length); //这里返回100
System.out.println(new String(data, 0, buffer.position()));
"或者"
buffer.flip();
System.out.println(new String(data, 0, buffer.limit()));
获取位置量
// 获取缓冲区的容量位
System.out.println(buffer.capacity());
// 获取缓冲区的操作位
System.out.println(buffer.position());
// 获取缓冲区的限制位
System.out.println(buffer.limit());
设置位置量
//设置limit的位置为position的位置
buffer.limit(buffer.position());
// 设置position的位置为起始位置
buffer.position(0);
遍历缓冲区
在遍历缓冲区区之前,需要对Buffer进行翻转,因为存储数据结束后,position指向最后一个数据的下一位。
需要先将limit移动至position,然后再将position移动为0位,然后开始从position读取到limit
buffer.limit(buffer.position());
buffer.position(0);
while(buffer.position() < buffer.limit()){
byte b = buffer.get();
System.out.println(b);
}
"等价于"
buffer.flip(); //翻转缓冲区,设置limit,设置position
while (buffer.hasRemaining()) { //等价于判断position是否小于limit
byte b = buffer.get();
System.out.println(b);
}
翻转,清空,重置,重绕
"翻转"
public final Buffer flip(){
limit = position;//设置limit到操作位
position = 0; //操作位重来
mark = -1; //翻转缓冲区,mark标记会被清除
return this;
}
"清空缓冲区数据"
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
"reset:重置缓冲区,重置mark到position这一段数据"
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
"rewind:重绕缓冲区(当数据需要重复读取时,不修改limit,只重置position)"
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
#其他
1、多个线程同时操作缓冲区,会不会造成指针争抢呢?
不会,应为NIO是同步非阻塞,操作Buffer是同步的,保证了同一时间内,只有一个线程能够使用。