Java NIO源码剖析及使用实例(一):Buffer
来源:互联网 发布:mac imovie不见了 编辑:程序博客网 时间:2024/04/30 16:39
现在越来越多的公司开始使用NIO,面试中也经常被问到NIO的知识,这里给大家介绍下,包括基本使用方法以及一些实现原理等,因为NIO知识较多,会分多篇介绍。
首先来说NIO是做什么的,Java中NIO
大家可以理解为new io,即新出的一个处理io流的包,它最重要的几个特性就是Channel
(管道)、Buffer
(缓冲区)、Selector
(选择器)。相较与IO,NIO新增了缓冲区以及非阻塞读取等特效,今天首先来看看Buffer。
首先我们直接通过读源码来学习下Buffer,后面再举出具体使用的例子,首先我们看下Buffer的几个属性:
// Invariants: mark <= position <= limit <= capacityprivate int mark = -1;private int position = 0;private int limit;private int capacity;
position
首先我们看position,有助于后面理解mark。position
故名思议就是记录当前的位置,他的几个重要方法是:
final int nextGetIndex() { // package-private if (position >= limit) throw new BufferUnderflowException(); return position++;}final int nextGetIndex(int nb) { // package-private if (limit - position < nb) throw new BufferUnderflowException(); int p = position; position += nb; return p;}final int nextPutIndex() { // package-private if (position >= limit) throw new BufferOverflowException(); return position++;}final int nextPutIndex(int nb) { // package-private if (limit - position < nb) throw new BufferOverflowException(); int p = position; position += nb; return p;}
nextGetIndex方法其实在读取buffer里数据的时候取出position的值之后自增或者增加所需步长,即取出当前位置,再由具体的Buffer实现取出position对应的具体值来达到一个流式的读取效果。nextPutIndex是同样的原理,只是其应用于写数据的时候
mark
mark及后面几个参数都是继承自Buffer
类里的,要了解mark,我们可以看一下Buffer源码里有关于mark值变化的部分:
public final Buffer mark() { mark = position; return this;}public final Buffer reset() { int m = mark; if (m < 0) throw new InvalidMarkException(); position = m; return this;}
上面两个方法可以看出来,mark的作用,其实就跟它的名字一样,就是通过mark()方法在当前位置做一个标记,然后需要时通过reset()来回到标记的位置,未标记时默认值为-1
limit
limit其实就是记录具体的读写数量,取值时受限于limit。我们在使用Buffer时会先指定Buffer缓冲区大小,比如我们指定5个byte,但是读取时实际只有3个byte,此时limit值就为3,我们取值时就只会取3个值,而不是总容量大小5个。其重要使用的地方就是上面介绍过的nextGetIndex和nextPutIndex,大家可以看到取下一个位置时是要跟limit做比较的,它还有个重要方法是:
public final boolean hasRemaining() { return position < limit;}
这个方法其实就是获取是否还有可读取的值,即通过当前读取到的位置和limit的值来判断
capacity
capacity就是我们缓冲区的总大小。
接下来我们看个具体实例来看下如何使用Buffer,且顺带看下它的常用方法:
private static void testChannel() throws IOException { RandomAccessFile accessFile = new RandomAccessFile("d://testNIO.txt" , "rw"); FileChannel channel = accessFile.getChannel(); ByteBuffer byteBuffer = ByteBuffer.allocate(3); int readBytes = channel.read(byteBuffer); while (readBytes != -1){ byteBuffer.flip(); while (byteBuffer.hasRemaining()){ System.out.printf((char)byteBuffer.get() + ""); } byteBuffer.clear(); readBytes = channel.read(byteBuffer); } accessFile.close();}
testNIO.txt文件内容:
aaaccbbacccadbac
执行结果如下:
aaaccbbacccadbac
这就是一个简单的读取文件的例子,对于其中几个大家可能不太了解的方法下面我们通过源码来分析下具体实现以了解上述代码是如何工作的:
首先此处用到的是ByteBuffer,我们来看下代码中大家没使用过的一些方法:
ByteBuffer.allocate(3):
public static ByteBuffer allocate(int capacity) { if (capacity < 0) throw new IllegalArgumentException(); return new HeapByteBuffer(capacity, capacity);}HeapByteBuffer(int cap, int lim) { // package-private super(-1, 0, lim, cap, new byte[cap], 0);}ByteBuffer(int mark, int pos, int lim, int cap, // package-private byte[] hb, int offset){ super(mark, pos, lim, cap); this.hb = hb; this.offset = offset;}
通过上面可以看到,allocate(3)方法其实就是new了一个mark=-1,pos=0,limit=3,capacity=3,hb=new Byte[3]的这么一个初始HeapByteBuff缓冲区。
channel.read(byteBuffer)
这里只能看到read方法,再里面的具体实现看不到,这里就不多讲了,这个方法一看也就知道其实就是通过管道将数据读取到缓冲区。
byteBuffer.flip()
public final Buffer flip() { limit = position; position = 0; mark = -1; return this;}
从源码可以看出来,flip方法其实就是将limit置为当前position值,即表示读取了多少数据,以及将position归0,这样后面我们才能读取数据。
byteBuffer.get()
public byte get() { return hb[ix(nextGetIndex())];}final int nextGetIndex() { // package-private if (position >= limit) throw new BufferUnderflowException(); return position++;}protected int ix(int i) { return i + offset;}
从源码可以看出,get方法其实就是去除hb数组中当前position的值,且将position加1以读取后续值,这就是为什么读取数据时需要先调用flip方法将position值归0的原因。
byteBuffer.clear()
public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this;}
看源码可以看出clear方法其实就是将Buffer中的几个属性重新初始化了,此处并没有清空缓存区的值,网上有人说此处会清空缓存区的值是错误且不负责任的!我们通过以下一个例子可以看出来其实是没有清空值的:
将testNIO.txt内容改为如下:
aaaccdd
然后去掉上面代码中的byteBuffer.flip(),执行原方法,结果如下:
cd
这是为什么呢?是因为缓冲区第一次读取数据时存放的是aaa,第二次覆盖掉第一次的,值为ccd,第三次读取时只有一个d值覆盖掉原来的ccd中的第一个c,此时缓冲区的值为dcd,我们去掉flip方法后再来读取数据,此时position为1,limit为3,因此会读出后面的cd值打印到控制台,所以说clear方法是不会清除缓存区的值的。
我的博客:blog.scarlettbai.com
我的公众号:读书健身编程
- Java NIO源码剖析及使用实例(一):Buffer
- Java NIO详解及实例和源码下载(一)
- Java NIO(一) Buffer类源码分析
- 【NIO详解】Buffer源码剖析
- Java NIO使用及原理之--缓冲器buffer(1)
- Java NIO使用及原理之--缓冲器buffer(2)
- JAVA NIO之Buffer(一)
- java nio(一)--Buffer基础
- java.nio.Buffer源码解读
- Java NIO详解及实例和源码下载(二)
- java nio之Buffer(一)
- Tomcat6 NIO源码剖析一
- Tomcat6 NIO源码剖析一
- java nio(nio机制buffer及buffer优化)
- 《Java源码分析》:Java NIO 之 Buffer
- 《Java源码分析》:Java NIO 之 Buffer
- Java NIO编程实例之一Buffer
- Java NIO源码分析之Buffer
- 在Global.asax文件里实现通用防SQL注入漏洞程序
- 在命令行中运行eclipse中创建的java项目
- 分布式服务框架 Zookeeper -- 管理分布式环境中的数据
- git跟踪文件的修改信息及日志信息
- struts校验框架的一个异常
- Java NIO源码剖析及使用实例(一):Buffer
- 网页下拉加载更多
- Hibernate经典总结
- Java程序员应当知道的10个面向对象设计原则
- redux 配合 react 在项目中的使用(个人总结与备忘)
- 关于JQuery 中表单提交中使用post方法,return false 不起作用解决办法
- CCF计算机职业认证—数位之和
- 深入理解Android WebView
- ecshop后台增加模块菜单详细教程