[NIO系列]NIO源码分析之Buffer
在以前的一篇文章中我们介绍过IO模型
IO模型总结 http://www.cnblogs.com/coldridgeValley/p/5449758.html
,而在实际运用中多路复用IO使用很多,JDK早在1.4的时候就引入了NIO(new IO),今天我们来学习NIO基础组件之一的Buffer的相关原理。
Java NIO由以下几个核心部分组成:
- Buffer
- Channel
- Selector
传统的IO操作是面向数据流的,这样就意味着数据从流中读取出来直到读取完毕都是没有任何地方可以缓存的。而NIO操作面向的是缓冲区,数据从Channel中读取到Buffer缓冲区,在Buffer中处理数据,本文我们从源码角度来解析缓冲区Buffer的代码实现,从而理解其原理。
Buffer
A container for data of a specific primitive type.
A buffer is a linear, finite sequence of elements of a specific primitive type. Aside from its content, the essential properties of a buffer are its capacity, limit, and position:
A buffer's capacity is the number of elements it contains. The capacity of a buffer is never negative and never changes.
A buffer's limit is the index of the first element that should not be read or written. A buffer's limit is never negative and is never greater than its capacity.
A buffer's position is the index of the next element to be read or written. A buffer's position is never negative and is never greater than its limit.
There is one subclass of this class for each non-boolean primitive type.
上文是JDK文档中对于Buffer的描述,简单而言就是Buffer是一个线性的,有限容量的基本数据类型容器,对于所有的非布尔型变量,都有一种buffer可以与之对应。Buffer这个容器中有几个重要的变量:
- capacity:表示buffer的容量,固定变量。
- limit:表示读写的边界位置,读写的数据不会超过此值,改值永远不大于capacity。
- position:表示下次读写开始的位置,大小永远不大于limit。
引用一幅网上流传非常广泛的图
看起来还是比较容易理解的。
Java NIO有如下常用的buffer类型:
- ByteBuffer
- CharBuffer
- DoubleBuffer
- ShortBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
可以看到Buffer的类型代表不同的数据类型,也就是说Buffer中存放的数据可以是基本数据类型,MappedByteBuffer稍有不同,会单独介绍。
我们在看Buffer的源码之前先来看个Buffer的Demo
public class BufferDemoOne {public static void main(String[] args)throws Exception {testBuffer();}private static void testBuffer() throws Exception{RandomAccessFile file = new RandomAccessFile("/test.txt", "rw");FileChannel channel = file.getChannel();ByteBuffer buffer = ByteBuffer.allocate(1024);int read = channel.read(buffer);while (read != -1) {buffer.flip();while (buffer.hasRemaining()) {System.out.print((char)buffer.get());}buffer.clear();read = channel.read(buffer);}file.close();}
}
可以看到利用Buffer来读写数据基本包含四步:
- 数据写入buffer
- 调用flip()将buffer转换为读模式
- 从buffer中读取数据
- 调用buffer.clear()清空数据
ByteBuffer是我们最常用的Buffer之一,接下来我们从源码角度一步一步来分析其内部一些重要方法的实现。
put
我们正常出了使用channel向buffer中写入数据,还有其本身的put方法,put方法的重载版本很多。我们这里找最简单的放入一个byte数据看下:
public ByteBuffer put(byte x) {hb[ix(nextPutIndex())] = x;return this;}final int nextPutIndex() { // package-privateif (position >= limit)throw new BufferOverflowException();return position++;}
很简单,把内部的position指针累加,同时把byte数据存放在内部的byte数组中。
mark
public final Buffer mark() {mark = position;return this;}
mark变量是一个ByteBuffer特有的变量,mark()方法的目的就是将position值赋给mark变量从而达到记录position的目的。
reset
public final Buffer reset() {int m = mark;if (m < 0)throw new InvalidMarkException();position = m;return this;}
利用mark中记录下来的position进行恢复,所以叫reset
flip
public final Buffer flip() {limit = position;position = 0;mark = -1;return this;}
flip的作用是将buffer的模式从写模式转换为读模式,所以我们可以看到,通过flip调用以后,buffer的limit限制到了之前写入的position,position被设置成了0,mark也被重置了。通过这样的方式可以让数据position为0的地方开始读取,并且不会超出写入的数据上限。
remaining
public final int remaining() {return limit - position;}
对比两个变量的含义很容易就理解,remaining表示还有多少字节未读取。
hasRemaining
public final boolean hasRemaining() {return position < limit;}
是否还有未读数据
clear
public final Buffer clear() {position = 0;limit = capacity;mark = -1;return this;}
虽然clear表面意思看起来像是清除数据,但是实际上数据并没有被清除,只是相关参数被重置了。相当于buffer被创建的时候的初始状态,所以如果buffer中还存在未读取的数据,调用clear,那么数据就没法读取了。
compact
public ByteBuffer compact() {System.arraycopy(hb, ix(position()), hb, ix(0), remaining());position(remaining());limit(capacity());discardMark();return this;}
简单看下就是把未读取的数据copy到数据起始位置,外加把相关变量重置。compact和clear区别在于:如果存在未读取的数据,那么调用clear会无法再读取数据,compact则是把剩余未读取数据复制到数组起始位置以便读取。
allocate
public static ByteBuffer allocate(int capacity) {if (capacity < 0)throw new IllegalArgumentException();//调用 HeapByteBuffer 构造函数return new HeapByteBuffer(capacity, capacity);}HeapByteBuffer(int cap, int lim) { // package-privatesuper(-1, 0, lim, cap, new byte[cap], 0);/*hb = new byte[cap];offset = 0;*/}ByteBuffer(int mark, int pos, int lim, int cap, // package-privatebyte[] hb, int offset){super(mark, pos, lim, cap);this.hb = hb;this.offset = offset;}Buffer(int mark, int pos, int lim, int cap) { // package-privateif (cap < 0)throw new IllegalArgumentException("Negative capacity: " + cap);this.capacity = cap;limit(lim);position(pos);if (mark >= 0) {if (mark > pos)throw new IllegalArgumentException("mark > position: ("+ mark + " > " + pos + ")");this.mark = mark;}}
allocate方法创建了一个HeapByteBuffer实例。HeapByteBuffer是ByteBuffer的一个子类,在HeapByteBuffer的构造器中调用了父类ByteBuffer的构造器,可以看到最终实际上是把ByteBuffer中每个变量赋值,同时我们也看到了在HeapByteBuffer中真正存放数据的是底层的byte数组。
allocateDirect
public static ByteBuffer allocateDirect(int capacity) {return new DirectByteBuffer(capacity);}// Primary constructor//DirectByteBuffer(int cap) { // package-privatesuper(-1, 0, cap, cap);boolean pa = VM.isDirectMemoryPageAligned();int ps = Bits.pageSize();long size = Math.max(1L, (long)cap + (pa ? ps : 0));Bits.reserveMemory(size, cap);long base = 0;try {base = unsafe.allocateMemory(size);} catch (OutOfMemoryError x) {Bits.unreserveMemory(size, cap);throw x;}unsafe.setMemory(base, size, (byte) 0);if (pa && (base % ps != 0)) {// Round up to page boundaryaddress = base + ps - (base & (ps - 1));} else {address = base;}cleaner = Cleaner.create(this, new Deallocator(base, size, cap));att = null;}
allocateDirect 方法创建了一个DirectByteBuffer实例。DirectByteBuffer也是ByteBuffer的一个实例,和HeapByteBuffer的区别在于,HeapByteBuffer是指在JVM堆内存中,而DirectByteBuffer是指直接内存。通过看代码可以看到,DirectByteBuffer通过unsafe.allocateMemory(size)直接申请内存,通过unsafe.setMemory(base, size, (byte) 0);将申请的内存数据清空。并且通过address变量维护指向改内存的地址。
总结:
Buffer在NIO中充当着数据容器的角色,灵活的使用其非常重要。从源码角度分析我们看到了,Buffer内部通过维护不同的变量来实现读写转换,数据的存储。所以理解这些关键变量(例如 postion mark limit capacity)基本就可以理解其所有api的使用逻辑以及场景。同时Buffer存在两个重要的子类,一个是Heap,一个是Direct 分别在不同的场景里使用。
转载于:https://www.cnblogs.com/coldridgeValley/p/6926335.html
[NIO系列]NIO源码分析之Buffer相关推荐
- Java集合Collection源码系列-ArrayList源码分析
Java集合系列-ArrayList源码分析 文章目录 Java集合系列-ArrayList源码分析 前言 一.为什么想去分析ArrayList源码? 二.源码分析 1.宏观上分析List 2.方法汇 ...
- Java NIO——Selector机制源码分析---转
一直不明白pipe是如何唤醒selector的,所以又去看了jdk的源码(openjdk下载),整理了如下: 以Java nio自带demo : OperationServer.java Oper ...
- java selector 源码_Java NIO——Selector机制源码分析---转
一直不明白pipe是如何唤醒selector的,所以又去看了jdk的源码(openjdk下载),整理了如下: 以Java nio自带demo : OperationServer.java Oper ...
- View系列 :源码分析:RecyclerView滑动删除 全解析
1:效果展示 效果很简单,就是 RecycleView的 滑动删除功能 2:效果分析 主要是三个步骤: 步骤一:是RecyclerView 的每一个条目上增加 删除 View控件,这个是静态xml页面 ...
- 【muduo源码分析】Buffer类的设计
目录 1.muduo的IO模型 2.为什么 non-blocking 网络编程中应用层 buffer 是必须的? 2.1 TcpConnection 必须要有 output buffer 2.2 Tc ...
- Linux驱动学习--wifi驱动(rtl88xx系列网卡芯片)源码分析
目录 一.引言 二.驱动框架 三.网络设备框架常用接口介绍 ------> 网卡部分 ------> USB部分 一.引言 今天来和大家分析一下rtl88xx 系列网卡的驱动框架,该网卡是 ...
- muduo源码分析之Buffer
这一次我们来分析下muduo中Buffer的作用,我们知道,当我们客户端向服务器发送数据时候,服务器就会读取我们发送的数据,然后进行一系列处理,然后再发送到其他地方,在这里我们想象一下最简单的Echo ...
- 文件系统源码分析之buffer.c
buffer是在内存里开辟一块空间做缓存,他是应用层和硬盘之间的一层缓存,主要是为了不用每次都访问硬盘,提高效率.缓存的结构由两部分组成,一个是哈希链表,一个是双向循环链表,第一个链表是使用数据的时候 ...
- 转载_LKM backdoor研究linux系列--insmod源码分析篇
问题的提出是前一阵和lgx聊天发现,一个被strip的module也可以被成功的 insmod,当时知道一些insmod 的原理觉得不太可能,因为一个正常的module文件 其实就是标准的ELF格 ...
最新文章
- C#内容分页简单实现代码及祥解
- mysql损坏表修复
- 服务器安全配置之注册表设置
- Spark Streaming初步使用以及工作原理详解
- java数据包解析_请教http请求数据包如何解析 重组
- Shiro并发登录人数控制遇到的问题和解决
- locustfile中的User类和HttpUser类
- Redis基础(十)——性能监控和监视器
- python类基础知识
- 多目标优化问题和遗传算法学习
- 关于坐标系(大地坐标、平面坐标、投影、北京54、西安80、WGS84)的一些理解
- 更换鼠标垫(鼠标)的心路历程
- python批量提取视频帧
- 开启HSTS让浏览器强制跳转HTTPS访问
- 被火龙强奸了100次~~~怨念!
- 全屏在线秒表_在线秒表
- 这段代码,c 1秒,java 9秒,c# 14秒,而python。。。
- rust放置木箱转向_rust笔记五 - cyper的个人空间 - OSCHINA - 中文开源技术交流社区...
- AutoVue软件在电子制造行业…
- 阿里产品岗需是技术出身?分享技术转型产品的成功经验
热门文章
- Python基础、条件语句和基本数据类型
- (四)maven之查找jar包坐标,选择jar包版本
- 学习《html5.css3.0》网页布局和样式精粹(第二天)
- 1001.Reverse Root
- 创建Server 2012 VHDX虚拟磁盘模板
- 解放学校网络管理员的双手 ——陕西省基础教育专网×××接入程序的优化
- Android Activity 提示[No Launcher activity found!]
- CRT 入口函数 CRTStartup
- OpenCV获取图像某点的颜色值,并设置某点的颜色
- kotlin学习笔记——内联函数