NIO
一.NIO的基本定义
java.nio全称java non-blocking IO,是指jdk1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。(百度百科)
- 对原始数据类型提供一套缓存容器。
- 作用是使用缓存容器(Buffer类)来进行数据的读写操作。
二.NIO和IO的区别
IO | NIO |
---|---|
面向流(Stream Oriented) | 面向缓冲区(Buffer Oriented) |
单向数据操作(in只能读,out只能写) | 双向数据操作,所有数据存放到buffer缓冲区中既可以读也可以写入.(缓冲区要放到通道中) |
阻塞IO(Blocking IO) | 非阻塞IO(Non Blocking IO) |
(无) | 选择器(Selectors) |
三.基于普通的通道和缓冲区的NIO操作
Java NIO系统的核心在于:通道(Channel)和缓冲区 (Buffer)。通道表示打开到 IO 设备(例如:文件、 套接字)的连接。若需要使用 NIO 系统,需要获取 用于连接 IO 设备的通道以及用于容纳数据的缓冲 区。然后操作缓冲区,对数据进行处理。
- 通道是连接程序与目标文件的管道。
- 缓冲区是用来存放要传输的数据的。
- 缓冲区是依靠通道进行传输的。
Channel负责传输,Buffer负责存储。
1.缓冲区
1.1 NIO的缓冲区是java中提供的除了boolean类型以外的基本数据类型的缓存容器,都是来自于Buffer。
1.2Java提供了7个用来存放数据的容器:
ByteBuffer,CharBuffer,DoubleBuffer,FloatBuffer,IntBuffer,LongBuffer,ShortBuffer
1.3缓冲区有三个重要的属性:capacity,position,limit.
- position(起始值)和limit(有效数据值)的含义取决于Buffer处在读模式还是写模式。
- capacity缓冲区的容量大小,在创建缓冲区的时候需要手动指定 .
详细解释:
- capacity:作为一个内存块,Buffer有固定的大小值,也叫作“capacity”,只能往其中写入capacity个byte、long、char等类型。一旦Buffer满了,需要将其清空(通过读数据或者清楚数据)才能继续写数据。
- position:当你写数据到Buffer中时,position表示当前的位置。初始的position值为0,当写入一个字节数据到Buffer中后,position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity-1。当读取数据时,也是从某个特定位置读,讲Buffer从写模式切换到读模式,position会被重置为0。当从Buffer的position处读取一个字节数据后,position向前移动到下一个可读的位置。
- limit:在写模式下,Buffer的limit表示你最多能往Buffer里写多少数据。 写模式下,limit等于Buffer的capacity。当切换Buffer到读模式时, limit表示你最多能读到多少数据。因此,当切换Buffer到读模式时,limit会被设置成写模式下的position值。换句话说,你能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)
- 标记 (mark)与重置 (reset):标记是一个索引,通过 Buffer 中的 mark() 方法指定 Buffer 中一个特定的 position,之后可以通过调用 reset() 方法恢复到这 个 position.
1.4具体的使用操作
步骤: 写入----读取操作
- 创建缓冲区,指定缓冲区容量 。
- 写入数据。
- 切换模式(从写到读或者从读–写)
- 展示数据。
1.5Buffer类常用方法
ByteBuffer类方法
(1)static ByteBuffer
allocate(int capacity) 分配一个新的字节缓冲区。
1.6缓冲区的数据操作
- Buffer 所有子类提供了两个用于数据操作的方法:get() 与 put() 方法
获取 Buffer 中的数据
- get() :读取单个字节
- get(byte[] dst):批量读取多个字节到 dst 中
- get(int index):读取指定索引位置的字节(不会移动 position)
放入数据到 Buffer 中
- put(byte b):将给定单个字节写入缓冲区的当前位置
- put(byte[] src):将 src 中的字节写入缓冲区的当前位置
- put(int index, byte b):将指定字节写入缓冲区的索引位置(不会移动 position)
- put(byte[] src, int offset, int length) 相对批量 put 方法(可选操作)。
(src - 要从中读取字节的数组
offset - 要读取的第一个字节在数组中的偏移量;必须为非负且不大于 array.length
length - 要从给定数组读取的字节的数量;必须为非负且不大于 array.length - offset )
@Test
void bufferDemo() {
//创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(10);//容量为10
//三个属性 写入模式
System.out.println(" ================== 写入模式 ========================");
System.out.println("总容量:"+buffer.capacity());//总容量:10
System.out.println("写入的起始标记:"+buffer.position());//写入的起始标记:0
System.out.println("写入时的limit: "+buffer.limit());//写入时的limit: 10
//转为读取模式
buffer.flip();
System.out.println(" ================== 读模式 ========================");
System.out.println("总容量:"+buffer.capacity());//总容量:10
System.out.println("读取的起始标记:"+buffer.position());//读取的起始标记:0
System.out.println("读取时的limit: "+buffer.limit());//读取时的limit: 0
//给缓冲区写入数据
String mess="abcde";
ByteBuffer put = buffer.put(mess.getBytes());
System.out.println(" ================== 写入数据之后 ========================");
System.out.println("总容量:"+buffer.capacity());
System.out.println("写入的起始标记:"+buffer.position());
System.out.println("写入时的limit:"+buffer.limit());
//转为读取模式
//position会随从缓冲区中读取数据的个数而改变,limit也会改变
buffer.flip();
System.out.println(" ================== 读模式 ========================");
System.out.println("总容量:"+buffer.capacity());
System.out.println("读取的起始标记:"+buffer.position());
System.out.println("读取时的limit:"+buffer.limit());
//使用get方法读取数据
byte[] dest=new byte[buffer.limit()];
// byte b = buffer.get();//读取一个
// System.out.println((char)b);
buffer.get(dest);//读取所有
System.out.println(new String(dest));
buffer.flip();
buffer.limit(buffer.capacity());//设置limit位置
buffer.clear();
System.out.println(" ==================读取数据之后转为写入模式 ========================");
System.out.println("总容量:"+buffer.capacity());
System.out.println("读取的起始标记:"+buffer.position());
System.out.println("读取时的limit:"+buffer.limit());
}
@Test
void buffer01() {
ByteBuffer buffer = ByteBuffer.allocate(10);
//三个属性 写入模式
System.out.println(" ================== 写入模式 ========================");
System.out.println("总容量:"+buffer.capacity());
System.out.println("写入的起始标记:"+buffer.position());
System.out.println("写入时的limit: "+buffer.limit());
//写入数据
buffer.put("abcdef".getBytes());
//转为读模式
buffer.flip();
System.out.println(" ================== 读模式 ========================");
System.out.println("总容量:"+buffer.capacity());
System.out.println("读取的起始标记:"+buffer.position());
System.out.println("读取时的limit:"+buffer.limit());
//此时并没有读取缓冲区中的数据
//获取缓冲区中的数据(读取中的内容是position--limit(不包含)之间的数据)
byte [] bt=new byte[buffer.limit()];
buffer.get(bt);
System.out.println(new String(bt));
// buffer.get();
// byte b = buffer.get();
// System.out.println(b);
//重新读取,将position.limit重新设置
buffer.rewind();
//自定义设置标记
buffer.position(0);
buffer.limit(4);
System.out.println(" ================== 读模式 ========================");
System.out.println("总容量:"+buffer.capacity());
System.out.println("读取的起始标记:"+buffer.position());
System.out.println("读取时的limit:"+buffer.limit());
buffer.flip();
buffer.clear();//清空数据,就是将所有的标记进行恢复,恢复到初始化
System.out.println(" ================== 写入模式 ========================");
System.out.println("总容量:"+buffer.capacity());
System.out.println("写入的起始标记:"+buffer.position());
System.out.println("写入时的limit: "+buffer.limit());
//清空缓冲区,只是将数据隐藏,并没有彻底清空
System.out.println((char)buffer.get());
}
- mark标记和reset重置
//mark标记和reset重置
@Test
void buffer02() {
//创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("abcdef".getBytes());//实际要写入的字节个数要小于等于capacity
//读取操作,读取123
buffer.flip();
byte[] bt=new byte[3];
buffer.get(bt);//==buffer.get(bt,0,bt.length);
//给第一次读取做一个标记position 3 limit 6
buffer.mark();
//读取456
buffer.get(bt);
System.out.println(new String(bt));
System.out.println(" ================== 读模式 ========================");
System.out.println("读取的起始标记:"+buffer.position());
System.out.println("读取时的limit:"+buffer.limit());
//恢复到第一次读取之后的标记
buffer.reset();
System.out.println(" ================== reset ========================");
System.out.println("读取的起始标记:"+buffer.position());
System.out.println("读取时的limit:"+buffer.limit());
}
- 容器的容量是10,存的数据是20
//容器的容量是10,存的数据是20
@Test
void buffer03() {
ByteBuffer buffer = ByteBuffer.allocate(10);
String mess="12345678901234567890";
//存进去
byte[] bytes=mess.getBytes();
buffer.put(bytes,0,buffer.capacity());
System.out.println("================= write =====================");
System.out.println("读取的起始标记:"+buffer.position());
System.out.println("读取时的limit"+buffer.limit());
//读出来
buffer.flip();
byte[] b=new byte[buffer.limit()];
buffer.get(b);
System.out.println(new String(b));
}
- 循环,先先进去,在读出来
@Test
void buffer04() {
Scanner input=new Scanner(System.in);
//容量为10
ByteBuffer buffer = ByteBuffer.allocate(10);
String mess=" 1234568822读书是一件aaadd快乐的事情。对于爱读书的人一但读上5823就让人欲罢不能,无法放弃读书,想让人swwdddd读到天涯海角。有人说:“人生最深最平和的快乐,就是静观天地与人生,慢慢fdddsssss品味出它的和谐与美。”静下心来,翻开书本,那些沉adfgvffff积的墨香一点点溢满空间,那些尘封的快fffffff乐一点点打开。读书真的很快乐!";
byte[] bytes=mess.getBytes();
//循环
boolean flag=true;
int index=0;
while(flag) {
//写
int len=0;
if(buffer.capacity()>(mess.length()-index)) {
len=mess.length()-index;
}else {
len=buffer.capacity();
}
buffer.put(bytes,index,len);
//转为读模式
buffer.flip();
//读取
byte[] b=new byte[buffer.limit()];
buffer.get(b);
System.out.println("读到的数据是"+new String(b));
index+=buffer.position();//对缓冲的操作
System.out.println("下一次写入时字符串的起始下标"+index);
//转为写模式
String next=input.next();
buffer.flip();
if(index==mess.length()) {
flag=false;
}
}
}
1.6.1非直接缓冲区
非直接缓冲区写入步骤:
- 创建一个临时的直接ByteBuffer对象。
- 将非直接缓冲区的内容复制到临时缓冲中。
- 使用临时缓冲区执行低层次I/O操作。
- 临时缓冲区对象离开作用域,并最终成为被回收的无用数据。
1.6.2直接缓冲区
1.6.3判断是否是直接缓冲区还是非直接缓冲区
abstract boolean
isDirect() 判断此字节缓冲区是否为直接的(子类的方法)。
- 非直接缓冲区:java—JVM(缓冲区)—物理内存区
- 直接缓冲区:javaAPP—JVM(省略)----物理内存区
- 读取数据的读取速度