第三章 Netty与NIO的前世今生
3.1 java NIO三件套
- 在NIO中有三个核心对象:缓冲区(Buffer)、选择器(Selector)、通道(Channel)
3.1.1 缓冲区
- **简述:**缓冲区实际上就是一个容器对象,更直接说就是数组在NIO系统中,所有的数据都是用缓冲区来处理的。在I/O系统中,所有数据都是直接写入或读取到Stream对象中
-
Buffer操作基本API
- 对于java中的基本类型,都有预支对应的buffer类型与之匹配
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hRvClUA3-1599830755971)(C:\Users\liulei06\AppData\Roaming\Typora\typora-user-images\image-20200908215945156.png)]
-
使用IntBuffer的例子
/** * buffer操作基本API示例 */ public class IntBufferDemo { public static void main(String[] args) { //buffer中共有3个属性 // position:下一个要被操作的数组索引; // limit:代表还有多少索引需要输出 // capacity:代表buffer的总大小,也可以理解为数组的总大小 IntBuffer intBuffer = IntBuffer.allocate(8); for(int i = 0; i < intBuffer.capacity(); i++){ int j = 2 * (i + 1); intBuffer.put(j); } //重设此缓冲区,将limit设置到position位置上,position设置到0 intBuffer.flip(); //读取position到limit的数据 while (intBuffer.hasRemaining()){ //读取缓冲区当前position位置的数据,知道position=limit int j = intBuffer.get(); System.out.print(j); } } }
-
输入如下
2 4 6 8 10 12 14 16
-
Buffer的基本原理
-
缓冲区对象:本质是一个数组(特殊的数组,能够跟踪和记录对象的变化情况)
- position:指定下一个写入或读取的元素的下标(索引),初始化值为0
- limit:指定还有多少数据需要取出,或还可以放入多少数据,初始值=capacity
- capacity:指缓冲区对象最大的数据容量
-
关系:0 <= position <= limit <= capacity
-
通过下边这个例子,我们能看到buffer状态的变化
/** * buffer基本示例:演示position/limit/capacity的变化 */ public class BufferDemo { public static void main(String[] args) throws IOException { //读取文件流,此处操作时I/O操作 String fileUrl = BufferDemo.class.getClassLoader().getResource("BufferDemoText.text").getFile(); FileInputStream inputStream = new FileInputStream(fileUrl); FileChannel inputChannel = inputStream.getChannel(); //创建一个大小为10的缓冲区 ByteBuffer byteBuffer = ByteBuffer.allocate(100); out("初始化", byteBuffer); //将数据写入缓冲区 inputChannel.read(byteBuffer); out("调用read", byteBuffer); //锁定写入范围 byteBuffer.flip(); out("调用flip", byteBuffer); while (byteBuffer.hasRemaining()){ byte b = byteBuffer.get(); } //可以理解对flip的解锁 byteBuffer.clear(); out("调用clear", byteBuffer); //关闭流 inputStream.close(); } public static void out(String step, Buffer buffer){ System.out.println(step + ":"); System.out.print("position:" + buffer.position() + ","); System.out.print("limit:" + buffer.limit() + ","); System.out.print("capacity:" + buffer.capacity() + ","); System.out.println();System.out.println(); } }
-
输出如下
初始化: position:0,limit:100,capacity:100, 调用read: position:57,limit:100,capacity:100, 调用flip: position:0,limit:57,capacity:100, 调用clear: position:0,limit:100,capacity:100,
-
-
缓冲区分片
-
再NIO中,可以将现有的buffer对象中的一个片段拿出单独操作,但这个片段与buffer对象的数据是共享的,也就是再内存中是同一个地址
-
demo示例如下:
/** * 缓冲区分片 */ public class BufferSliceDemo { public static void main(String[] args) { IntBuffer intBuffer = IntBuffer.allocate(10); //给缓冲区的数据复制:1-10 for(int i = 1; i <= intBuffer.capacity(); i++){ intBuffer.put(i); } //分片:创建子缓冲区 intBuffer.position(3); intBuffer.limit(7); IntBuffer slice = intBuffer.slice(); //给子缓冲区中的数据*10 for (int i = 0; i < slice.capacity(); i++){ int value = slice.get(i); slice.put(i, value * 10); } //重置主buffer的状态位 intBuffer.position(0); intBuffer.limit(intBuffer.capacity()); //读取buffer中的数据 while (intBuffer.hasRemaining()){ System.out.print(intBuffer.get() + " "); } } }
-
输出如下
1 2 3 40 50 60 70 8 9 10
-
结论:可以看到,只有分片(子buffer)中的数据*10,整个buffer的数据都发生了变话
-
-
只读缓冲区
- 顾名思义,该缓冲区只允许读操作,不允许写操作。可以通过正常的buffer对象asReadOnlyBuffer()方法获取该缓冲区对象的读对象
- 通常用来做权限控制,比如暴漏出去的缓冲区对象下hi允许读不允许写。
-
直接缓冲区
-
目的:为了加快I/O的速度
-
原理:java虚拟机在进行I/O操作时,会尝试避免将缓冲区的内存拷贝到中间缓冲区或避免将从一个中间缓冲区拷贝数据。
-
使用方式:
//创建缓冲区对象时,不适用allocate(),使用下面的方法创建,具体的操作与正常的buffer对象一样 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024);
-
-
内存映射
-
内存映射是一种读取数据的方法,比常规的I/O快很多
-
其原理是通过映射文件I/O使文件中的数据表现为内存中的数据来完成(不是通过I/O流将数据读取到内存)
/** * 内存映射:直接操作磁盘文件的buffer示例 */ public class MappedBufferDemo { public static void main(String[] args) throws IOException { String file = MappedBufferDemo.class.getClassLoader().getResource("BufferDemoText.text").getFile(); //RandomAccessFile可以自由访问文件的任意位置 RandomAccessFile raf = new RandomAccessFile(file, "rw"); FileChannel channel = raf.getChannel(); //把缓冲区与文件进行一个映射关联 //只要操作内存中的数据,文件中的内容也会发生变化 MappedByteBuffer mbBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 1024); mbBuffer.put(" | 测试内存映射buffer".getBytes()); ByteBuffer put = mbBuffer.put(0, (byte)97); mbBuffer.put(1023, (byte)122); raf.close(); } }
-