NIO概述
java.nio全称Java non-blocking IO或Java New IO,是从jdk1.4 开始引入的一套新的IO api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供同步非阻塞式的高伸缩性网络。
NIO核心组成部分:
- Buffer:缓冲区
- Channel:通道
- Selector:选择器(轮询器)
NIO和IO的区别
IO | NIO | AIO |
---|---|---|
面向流(Stream Oriented) | 面向缓冲区(Buffer Oriented) | AIO是发出IO请求后,由操作系统自己去获取IO权限并进行IO操作 |
阻塞IO(Blocking IO) | 非阻塞(Non Blocking IO) | AIO只是帮助你从内核中将数据复制到用户空间中,并调用你传入的回调方法 |
无 | 选择器(Selectors) |
AIO的做法是,每个水壶上装一个开关,当水开了以后会提醒对应的线程去处理。
NIO的做法是,叫一个线程不停的循环观察每一个水壶,根据每个水壶当前的状态去处理。
BIO的做法是,叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。
Buffer的使用
Java NIO中的Buffer用于和NIO通道进行交互,而数据是从通道读入缓冲区,从缓冲区写入到通道中的。
缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。
可以将这个Buffer看做是一个运载车,存储数据,在内存和外部设备之间的通道上进行运输,所以必然有装载和卸货的过程,这里可以进行读写模式切换。
Java NIO里关键的Buffer实现:
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
一、相关方法
1.Buffer缓冲区的分配
要想获得一个Buffer对象首先要进行分配。 每一个Buffer类都有一个allocate方法。下面是一个分配1024字节capacity的ByteBuffer的例子。
//创建间接缓冲区,大小为1024个字节,是HeapByteBuffer子类
ByteBuffer buf = ByteBuffer.allocate(1024);
//直接缓冲区,是MappedByteBuffer的子类
ByteBuffer buf = ByteBuffer.allocateDirect(1024);
直接缓冲区和见解缓冲区的区别
**间接缓冲区:**在堆中开辟,易于管理,垃圾回收器可以回收,空间有限,读写文件速度较慢。
**直接缓冲区:**不在堆中,物理内存中开辟空间,空间比较大,读写文件速度快,缺点:不受垃圾回收器控制,创建和销毁耗性能。
2.向Buffer写入数据的方法
写数据到Buffer有两种方式:
- 从Channel写到Buffer。后面章节中会做出案例说明。
- 通过Buffer的put()方法写到Buffer里。
//将数据写到Buffer中,除了以下字节方法,还有putInt,putDouble等数据方法
put(byte b);
put(byte[] src);
put(byte[] src, int offset, int length);
put(ByteBuffer src);
put(int index, byte b);
3.读写模式切换
//读写模式切换,一般是写转读
Buffer flip();
4.从Buffer中读取数据
从Buffer中读取数据有两种方式:
- 从Buffer读取数据到Channel(后面章节会介绍)
- 使用get()方法从Buffer中读取数据
//获取数据,除了以下字节方法,还有getInt、getDouble等数据方法
byte get();
ByteBuffer get(byte[] dst);
ByteBuffer get(byte[] dst, int offset, int length);
byte get(int index);
//Buffer转为读取模式后,直接将其转为字节数组
byte[] array();
5.重读
//重绕此缓冲区
Buffer rewind();
6.清空
//清除缓冲区,其实并不是真正意义上的清楚,原数据还在,只是覆盖写入
Buffer clear();
//压缩此缓冲区,用于未读完的情况,将未读数据前置(原已读数据被覆盖),从空位置续写,而不是覆盖写
ByteBuffer compact();
7.标记
//添加标记
buffer.mark();
//恢复到标记位置
buffer.reset();
8.获取相关的Buffer缓冲区属性方法
//返回此缓冲区的容量(创建的时候指定的长度,一般不可变)
int capacity();
//返回缓冲区的数据长度
int limit();
//返回此缓冲区指针所在的位置,默认从0位置开始
int position()
此外,还有mark标记属性,这四个属性的活动范围(值的大小)关系为:
0 <= mark <= position <= limit <= capacity
二、Buffer的实现原理
该方法的读写原理主要是操作 position 和 limit 这两个属性完成的,本质上操作的是数组。
测试代码如下:
//测试代码
public class BufferDemo2 {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
System.out.println("--------写入数据之前-------");
System.out.println("容量" + buffer.capacity());
System.out.println("限制" + buffer.limit());
System.out.println("位置" + buffer.position());
System.out.println("--------写入数据之后-------");
buffer.put("hello".getBytes());
System.out.println("容量" + buffer.capacity());
System.out.println("限制" + buffer.limit());
System.out.println("位置" + buffer.position());
System.out.println("-------转换为读取模式后-----");
buffer.flip();
System.out.println("容量" + buffer.capacity());
System.out.println("限制" + buffer.limit());
System.out.println("位置" + buffer.position());
//读取
System.out.println("-------读取之后-----");
byte[] data = new byte[buffer.limit()];
buffer.get(data);
System.out.println("容量" + buffer.capacity());
System.out.println("限制" + buffer.limit());
System.out.println("位置" + buffer.position());
System.out.println("---rewind()重复读取--");
buffer.rewind();
System.out.println("容量" + buffer.capacity());
System.out.println("限制" + buffer.limit());
System.out.println("位置" + buffer.position());
System.out.println("-----读取三个字节,但是没读完hello-----");
byte b1 = buffer.get();
byte b2 = buffer.get();
byte b3 = buffer.get();//读取三个字节,但是没读完hello
System.out.println("容量" + buffer.capacity());
System.out.println("限制" + buffer.limit());
System.out.println("位置" + buffer.position());
System.out.println("----compact压缩,后面未读完的前移----");
buffer.compact();
System.out.println("容量" + buffer.capacity());
System.out.println("限制" + buffer.limit());
System.out.println("位置" + buffer.position());
buffer.flip();
System.out.println("flip后限制" + buffer.limit());
byte b4 = buffer.get();
byte b5 = buffer.get();
System.out.println((char) b4);
System.out.println((char) b5);
//清空,只是把指针位置修改了,内容还在,可被覆盖
System.out.println("------清空----------");
buffer.clear();
System.out.println("容量" + buffer.capacity());
System.out.println("限制" + buffer.limit());
System.out.println("位置" + buffer.position());
}
}
//打印结果
--------写入数据之前-------
容量1024
限制1024
位置0
--------写入数据之后-------
容量1024
限制1024
位置5
-------转换为读取模式后-----
容量1024
限制5
位置0
-------读取之后-----
容量1024
限制5
位置5
---rewind()重复读取--
容量1024
限制5
位置0
-----读取三个字节,但是没读完hello-----
容量1024
限制5
位置3
----compact压缩,后面未读完的前移----
容量1024
限制1024
位置2
flip后限制2
l
o
------清空----------
容量1024
限制1024
位置0
写入数据前
写入数据后
转为读取模式后
读取之后
rewind()重读
读取三个字节,但是没读完
compact压缩,后面未读完的前移,写入的时候是追加写
compact()方法执行后,Buffer处于写模式(追加写)
使用 flip() 方法后
清空clear()
清空后所有属性归位,处于写模式,但是里面的数据还在,也就是覆盖写。
mark标记和reset返回标记
public class BufferMarkDemo3 {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
buffer.put("hello".getBytes());
buffer.flip();
byte[] data = new byte[2];
buffer.get(data);
System.out.println("第一次读取");
System.out.println(new String(data));
buffer.mark();
buffer.get(data);
System.out.println("第二次读取");
System.out.println(new String(data));
buffer.reset();//回到上一个标记位置
buffer.get(data);
System.out.println("第三次读取");
System.out.println(new String(data));
}
}
//打印结果:
第一次读取
he
第二次读取
ll
第三次读取
ll
三、Buffer的使用基本步骤
- 创建缓冲区,容量自定义
- 向缓冲区中添加内容
- 切换为读模式
- 批量读取,limit方法调取容量
- 清空
public class BufferDemo {
public static void main(String[] args) {
//1.创建缓冲区,容量1024
ByteBuffer buffer = ByteBuffer.allocate(1024);
//2.写入数据
buffer.put("我爱魔兽世界".getBytes());
//3.切换模式,写入模式转为读取模式
buffer.flip();
//4.读取,分为单字节读取和批量读取
/*4.1单字节读取
byte b = buffer.get();
System.out.println((char) b);*/
/*4.2字节数组读取
byte[] data = new byte[buffer.limit()];
buffer.get(data); //此方法将此缓冲区的字节传输到给定的目标数组中
System.out.println(new String(data));
*/
//4.3缓冲区直接转字节数组读取
System.out.println(new String(buffer.array(), 0, buffer.limit()));
//5.清空
buffer.clear();
}
}