源码解析-JavaNIO之Buffer,Channel
NIO: New IO / Native IO
jdk1.4诞生出来的,优化了传统IO的拷贝次数
(重点在 不再需要内核缓冲区,用户缓冲区与JVM内存之间的频繁拷贝
直接创建一块堆外内存,直接映射对应的磁盘物理地址,然后通过虚拟地址缺页中断,切换到内核直接将物理地址加载到堆外内存,一次拷贝都不需要 四舍五入相当于堆外内存直接打通磁盘了)
Buffer
抽象类,抽象理解为在JVM内存开辟一块空间,用作缓冲数据。Buffer实际理解为就是一个数组,可以存储byte类型数据。
几个重要属性:capacity, position, limit
capacity: 就是buffer的最大值 如果任何操作超过了这个值直接抛 java.nio.BufferOverflowException 异常
position: 每读一个值 或者每写入一个值,position+1
limit: 限制当前可操作的最大值
mark: 做标记,标记一下当前position的位置,方便以后可以回到这个地方重读(reset操作)
实际的操作分为读写两种
读操作 每读一个值position+1, limit为当前数据实际长度,也就是可读的最大长度
写操作 每写入一个值position+1,limit = capacity,也就是可写入的最大长度
核心继承关系
Buffer:抽象类,负责来来回回操作那四个属性 capacity, position, limit,mark
ByteBuffer: 抽象类,真正的创建了数组 final byte[] hb, 以后实际用来存放字节数据的就是hb,定义了put(),get()等读写缓冲区的方法
HeapByteBuffer: 实现类,直接创建JVM堆中字节缓存空间
MappedByteBuffer: 抽象类,创建了堆外缓存空间,直接映射一块磁盘物理地址。 force()方法,强制将缓冲区数据写入磁盘物理地址
DirectByteBuffer: MappedByteBuffer的实现类
Buffer的主要功能很简单,put() 一下数据、flip() 切换到读模式、然后用 get() 获取数据、clear() 一下清空数据、重新回到 put() 写入数据。
重要意义在于开辟了对外空间直接映射磁盘物理地址这一骚操作, 想了解操作系统级底层图可以参考一下子 https://www.cnblogs.com/ttaall/p/14128788.html
Channel
Channel的意思是一个管道,说是一个管道其实更像是目的地。比如物理磁盘的地址目的地,
MappedByteBuffer开创了一块堆外内存,FileChannel找到磁盘文件物理位置,就可以直接把buffer传给channel, 从而实现把buffer中的数据写到channel,从channel读数据到buffer中
nio包下重点关注的Channel继承关系
FileChannel: 文件通道,用于文件的读和写
SocketChannel: 把它理解为 TCP 连接通道,简单理解就是 TCP 客户端
ServerSocketChannel: TCP 对应的服务端,用于监听某个端口进来的请求
DatagramChannel: 用于 UDP 连接的接收和发送
FileChannel 写入实现
1. 第一部分获取Channel
getChannel()
发现了吗,单例模式懒汉式 ,直接来对象锁,如果channel为null创建
FileChannelImpl.open();
FileChannel实现了两个open()方法,区别是什么呢?
都调用了new FileChannelImpl() 只是传的第五个参数不同,上边的方法直接写死了传的false
var3 是否可读 var4 是否可写 var5 是否可追加
FileInputStream 实现的 可读,不可写,不可追加
FileOutPutStram 实现的 可读,可写,能否追加取决于创建FileOutPutStram的时候咋实现的,默认是false
2. 第二部分开辟Buffer
ByteBuffer抽象类的静态方法 allocateDirect() 参数就是创建buffer的字节大小(话说抽象父类具有子类的创建方式,这... 难道说就因为DirectByteBuffer设计成了default的,就要通过父类的静态方法new了吗?)
DirectByteBuffer的 put方法实现底层就是这个了,通过unsafe类把字节put出去
刷新一下,将limit设置为当前数据大小,重置一下position为0 重置一下mark为-1
read() write()的方法就不看了,底层都是调用native本地库,估计是c语言的类库来读写的