ByteBuffer类是在Java NIO中常常使用的一个缓冲区类,使用它可以进行高效的IO操作,但是,如果对常用方法的理解有错误,那么就会出现意想不到的bug。
ByteBuffer类的常用方法
先来看看一个基本的程序
publicvoid test()throwsIOException{ByteBuffer buff =ByteBuffer.allocate(128);FileChannel fin =null;FileChannel fout =null;try{
fin =newFileInputStream("filein").getChannel();
fout =newFileOutputStream("fileout").getChannel();while(fin.read(buff)!=-1){
buff.flip();
fout.write(buff);
buff.clear();}}catch(FileNotFoundException e){}finally{try{if(fin !=null){
fin.close();}if(fout !=null){
fout.close();}}catch(IOException e){throw e;}}}
在test方法中,首先通过ByteBuffer.allocate()方法分配了一段内存空间,作为缓存,allocate方法对缓存自动清零,然后打开一个输入文件管道fin和一个输出文件管道fout,在循环中先从fin读出数据存放到buff缓冲区中,再将buff缓冲中的内容写入fout。当然这对于先从文件中读,然后再写这样的场景,这不是高效的做法。
可以看到先从fin中读出数据后,首先要调用ByteBuffer.flip()方法,若将数据写入输出文件后,还要调用ByteBuffer.clear()方法,为什么要这样做呢?
ByteBuffer可以作为一个缓冲区,是因为它是内存中的一段连续的空间,在ByteBuffer对象内部定义了四个索引,分别是mark,position,limit,capacity,其中
-
mark用于对当前position的标记
-
position表示当前可读写的指针,如果是向ByteBuffer对象中写入一个字节,那么就会向position所指向的地址写入这个字节,如果是从ByteBuffer读出一个字节,那么就会读出position所指向的地址读出这个字节,读写完成后,position加1
-
limit是可以读写的边界,当position到达limit时,就表示将ByteBuffer中的内容读完,或者将ByteBuffer写满了。
-
capacity是这个ByteBuffer的容量,上面的程序中调用
ByteBuffer.allocate(128)
就表示创建了一个容量为capacity字节的ByteBuffer对象。
了解了这四个变量之后,再来看看前面的程序。之所以调用ByteBuffer.flip()方法是因为在向ByteBuffer写入数据后,position为缓冲区中刚刚读入的数据的最后一个字节的位置,flip方法将limit值置为position值,position置0,这样在调用get*()方法从ByteBuffer中取数据时就可以取到ByteBuffer中的有效数据,JDK中flip方法的代码如下:
publicfinalBuffer flip(){
limit = position;
position =0;
mark =-1;returnthis;}
在调用four.write(buff)
时,就将buff缓冲区中的数据写入到输出管道,此时调用ByteBuffer.clear()方法为下次从管道中读取数据做准备,但是调用clear方法并不将缓冲区的数据清空,而是设置position,mark,limit这三个变量的值,JDK中clear方法的代码如下:
publicfinalBuffer clear(){
position =0;
limit = capacity;
mark =-1;returnthis;}
这个方法命名给人的感觉就是将数据清空了,但是实际上却不是的,它并没有清空缓冲区中的数据,而至重置了对象中的三个索引值,如果不清空的话,假设此次该ByteBuffer中的数据是满的,下次读取的数据不足以填满缓冲区,那么就会存在上一次已经处理的的数据,所以在判断缓冲区中是否还有可用数据时,使用ByteBuffer.hasRemaining()方法,在JDK中,这个方法的代码如下:
publicfinalboolean hasRemaining(){return position < limit;}
在该方法中,比较了position和limit的值,用以判断是否还有可用数据。
在ByteBuffer类中,还有个方法是compact,对于ByteBuffer,其子类HeapByteBuffer的compact方法实现是这样的:
publicByteBuffer compact(){System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
position(remaining());
limit(capacity());returnthis;}
如果position()方法返回当前缓冲区中的position值,remaining()方法返回limit与position这段区间的长度,JDK中的remaining()方法代码如下
publicfinalint remaining(){return limit - position;}
所以compact()方法中第一条语句作用是将数组hb当前position所指向的位置开始复制长度为limit-position的数据到hb数组的开始出,其中使用到了ix()函数,这个函数是将参数值加上一个offset值,offset即一个偏移值,在这样的比如一个这样的场景对于一个很大的缓冲区,将其分成两段,第一段的起始位置是p1,长度是q1,第二段起始位置是p2,长度是q2,那么可以分别将这两段包装成一个HeapByteBuffer对象,然后这两个HeapByteBuffer对象(ByteBuffer的子类,默认实现)的offset属性分别设置为p1和p2,这样就可以通过在内部使用ix()函数来简化ByteBuffer对外提供的接口,在使用者看来,与默认的ByteBuffer并没有区别。
在compact函数中,接着将当前的缓冲区的position索引置为limit-position
,limit索引置为缓冲区的容量,这样调用compact方法中就可以将缓冲区的有效数据全部移到缓冲区的首部,而position指向下一个可写位置。
比如刚刚创建一个ByteBuffer对象buff时,position=0,limit=capacity,那么此时调用buff.hasRemaining()则会返回true
,这样来判断缓冲区中是否有数据是不行的,因为此时缓冲区中的存储的全部是0,但是调用一次compact()
方法就可以将position置为limit值,这样再通过buff.hasRemaining()就会返回false
,可以与后面的逻辑一起处理了。
ByteBuffer还有一个名为mark的方法,该方法设置mark索引为position的值,JDK中的代码如下:
publicfinalBuffer mark(){
mark = position;returnthis;}
与其功能相反的方法为reset方法,即将position的值设置为mark,JDK中的代码如下:
publicfinalBuffer reset(){int m = mark;if(m <0)thrownewInvalidMarkException();
position = m;returnthis;}
此外还有一个名为rewind的方法,这个方法将position索引置为0,mark索引置为-1,JDK中的代码如下:
publicfinalBuffer rewind(){
position =0;
mark =-1;returnthis;}
通过这些方法,就可以很方便的操作一个缓冲区,关键是要理解这些方法具体的作用,以及对三个索引值的影响(capacity是不变的)。
ByteBuffer继承自Buffer类,上面的方法四个索引值都定义在Buffer类中,操作索引值的方法也都定义在Buffer类中。
总结
通过对ByteBuffer中的四个索引值操作方法的分析,加深了对ByteBuffer的理解。理解ByteBuffer和其他几种Buffer的关键是要理解在使用中各个方法是如何操作索引值的,特别要注意的是clear方法并没有清除缓冲区的内容。
相关推荐
Apache Mina是一个高性能的网络应用框架,主要用于简化网络...通过阅读《深入理解Apache Mina (6)---- Java Nio ByteBuffer与Mina ByteBuffer的区别》的相关资料,可以更深入地理解这两个类的具体实现和应用场景。
同时,理解源码可以帮助深入理解Java NIO的工作原理,提升对非阻塞I/O的理解。 总的来说,掌握Java NIO中的ByteBuffer用法对于编写高性能的I/O密集型应用至关重要。通过熟练运用ByteBuffer,开发者可以设计出更高效...
易语言汇编版ByteBuffer...总之,易语言汇编版ByteBuffer源码是一个结合了易语言和汇编语言优势的实用工具,对于进行网络编程的开发者来说,它提供了高效的数据处理手段,同时也为学习者提供了深入理解底层机制的机会。
了解并熟练掌握`ByteBuffer`的这些特性与操作,对于开发高效、低延迟的Java应用程序,尤其是在处理大数据流和并发I/O时,显得尤为重要。通过深入学习和实践,我们可以利用Java NIO提供的强大功能,编写出更加灵活和...
总的来说,深入理解Apache Mina框架是一项系统性的工程,涉及到网络编程的方方面面,包括对Java NIO的深入理解、对框架核心组件的认识,以及对高性能网络通信实现原理的把握。对于想要开发稳定、高效的网络应用的IT...
同时,根据描述中的提示,可以参考相关的网址来了解具体用法和示例,这对于快速上手和深入理解该组件非常有帮助。总的来说,ByteBuffer是易语言环境中进行网络编程不可或缺的一个组件,它简化了二进制数据的处理,...
这个压缩包文件包含了关于Apache Mina的深入讲解,从基本组件到高级特性的运用,对学习和理解Mina框架有极大的帮助。通过对这些内容的学习,开发者能够更好地掌握如何利用Mina构建高效、稳定、易扩展的网络应用。
深入理解Apache_Mina_(1)----_Mina的几个类 深入理解Apache_Mina_(2)----_与IoFilter相关的几个...深入理解Apache_Mina_(6)----_Java_Nio_ByteBuffer与Mina_ByteBuffer的区别(类图) 相信你们也愿意去下载更有价值的东西
本文将深入探讨如何基于Socket在Android/Java环境中使用Javolution库来接收和解析结构体数据。 首先,Javolution是一个高性能、可移植的Java库,设计用于处理复杂的数据结构,如结构体和固定大小的数组。它提供了...
此视频教程由美团技术团队成员精心制作,旨在帮助开发者深入了解Netty内部机制及其实现原理。通过本课程的学习,开发者不仅能够掌握如何使用Netty开发高性能的应用程序,还能够学会如何阅读和理解其源代码,从而更好...
最后,这份《深入浅出Netty》PDF文件将带领你逐步理解Netty的架构、特性,并通过实例让你掌握如何在项目中运用Netty。无论是新手还是有一定经验的开发者,都能从中受益匪浅,进一步提升自己的技术能力。
通过深入理解这些关键组件,我们可以更好地利用Mina来构建高效的网络应用。例如,IoFilter可以用于实现安全过滤、流量控制、日志记录等功能;IoHandler可以定制业务逻辑,处理各种网络事件;而自定义的协议编码解码...
《深入浅出Netty》是一本专注于介绍Netty框架的专著,旨在帮助读者全面理解并熟练运用这个高性能、异步事件驱动的网络应用框架。Netty是由Java编写的,广泛应用于各种网络通信场景,包括但不限于服务器开发、云计算...
这个“Netty4源码深入剖析”高清视频教程,显然是针对那些希望深入理解Netty工作原理和源码实现的学习者设计的。在深入探讨之前,让我们先了解一下Netty的一些基本概念和特性。 Netty 的核心在于其NIO(非阻塞I/O)...
通过阅读和理解这些代码,我们可以深入学习如何在Java中运用字节流和字符流进行文件操作,以及如何在需要时进行两者之间的转换。 总的来说,字节流和字符流是Java I/O系统的重要组成部分,理解它们的工作原理和应用...
本文通过对Flink源码的深入分析,帮助读者从源码层面上理解和掌握了Flink的核心架构及执行流程,从而使得Flink的学习更加深入和全面。需要注意的是,由于Flink技术的快速迭代,文中所提及的内容以Flink 1.5 RELEASE...
在本篇解读中,我们将深入理解ByteBuffer的工作原理和应用场景。 ByteBuffer是一个可以容纳不同类型数据的可变容器,它允许程序员直接操作字节,而不是通过传统的字符或对象数组。这在处理二进制数据,如网络通信和...
这些例子可以帮助你深入理解Netty的工作原理,并提供实际操作经验。 1. **ServerBootstrap**配置:学习如何设置服务器启动参数,如绑定端口、配置EventLoopGroup等。 2. **ChannelHandler**编写:实现自定义处理器...
深入理解MINA的ByteBuffer和Java Nio ByteBuffer的区别,可以帮助开发者优化性能并减少内存拷贝。 线程模型是MINA的另一个关键配置项。MINA提供了多种线程模型,如简单的单线程模型、多线程模型以及Event-driven...