定义

缓冲区Buffer在java nio中负责数据的存储,缓冲区就是数组,用于存储不同类型数据的数组。

jdk为java七大基本类型数据都准备了响应的缓冲区(boolean值除外):

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntegerBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer

上述缓冲区除了ByteBuffer的 功能稍微多点外,因为ByteBuffer是通用的,所以功能会比较多。其他6种的使用方式几乎是一致的。

/*
* Buffer以上七大基本类型缓冲区类的基类,是个抽象类,定义了一些通用方法以及一些抽象方法。
* 具体方法都是final修饰的,不可被子类覆盖。
*
public abstract class Buffer {/*** 不知道啥用*/static final int SPLITERATOR_CHARACTERISTICS =Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED;//标记当前的position 位置,用reset方法的话,可以把position设置为mark值位置。//如果mark=-1的话会reset失败。private int mark = -1;//游标,标记缓冲区下一个要读取或者写入的数组下标。private int position = 0;//表示缓冲区数组可以操作(读取、写入)的可用数据长度。private int limit;//缓冲区容量private int capacity;//以上四个属性符合 mark<=position<=limit<=capacity,不符合就会报错。// 只对直接缓冲区有用// 记录直接缓冲区的内存地址,用于提高JNI 的方法GetDirectBufferAddress的效率。//ByteBuffer才支持直接缓冲区,所以到后面关于ByteBuffer的会讲。long address;// 传入mark、position、limit、capacity值创建一个缓冲区的构造器//是个包内私有的构造器Buffer(int mark, int pos, int lim, int cap) { //缓冲区容量小于0时抛出异常。if (cap < 0)throw new IllegalArgumentException("Negative capacity: " + cap);//根据传参设置缓冲区大小。this.capacity = cap;//设置limitlimit(lim);//设置positionposition(pos);if (mark >= 0) {if (mark > pos)//mark大于pos并且mark大于等于0时会抛出异常throw new IllegalArgumentException("mark > position: ("+ mark + " > " + pos + ")");//设置markthis.mark = mark;}}/*** 返回缓冲区容量*/public final int capacity() {return capacity;}/*** 返回缓冲区下一次要读取或者写入的数组下标*/public final int position() {return position;}/*** 设置缓冲区的游标*/public final Buffer position(int newPosition) {//要设置的position值不能大于limit值或者小于0,否则就抛出异常。if ((newPosition > limit) || (newPosition < 0))throw new IllegalArgumentException();//设置游标position = newPosition;//如果设置的新的游标值比mark值小,就重置mark值为-1.if (mark > position) mark = -1;return this;}/***返回缓冲区的可用数据长度。*/public final int limit() {return limit;}/*** 设置缓冲区的limit值,同时会保证position和mark值保证这几个值的关系符合规则。*/public final Buffer limit(int newLimit) {//对要设置的新limit进行检查,如果大于缓冲区容量或者小于0就抛出异常if ((newLimit > capacity) || (newLimit < 0))throw new IllegalArgumentException();//设置limit值limit = newLimit;//如果游标比可操作的最大数组下标还大的话,就把游标设置为limit。if (position > limit) position = limit;//如果mark > limit,重置mark。if (mark > limit) mark = -1;return this;}/*** 标记当前position位置。*/public final Buffer mark() {mark = position;return this;}/*** 把游标恢复到标记的位置*/public final Buffer reset() {int m = mark;//标记小于0时抛出异常if (m < 0)throw new InvalidMarkException();//重置游标至标记处position = m;return this;}/*** 清空缓冲区,其实就设置游标为0,limit设置回capacity值,mark重置,数据还是存在的。*/public final Buffer clear() {position = 0;limit = capacity;mark = -1;return this;}/*** 缓冲区创建时默认是写模式的,这个方法把缓冲区改为读模式。* 每次通过通道往存储设备中写数据都需要调用此方法把缓冲区设置为读模式。读取缓冲区的数据。* 最后详细讲解这个*/public final Buffer flip() {//把limit设置为positionlimit = position;//将游标设置为0position = 0;//重置mark。mark = -1;return this;}/*** 重置游标,从新开始读、写数据。*/public final Buffer rewind() {position = 0;mark = -1;return this;}/*** 读模式下,返回剩余可读的数据长度,写模式下,返回剩余可写的缓冲区长度。*/public final int remaining() {return limit - position;}/*** 返回是否还有数据可读或者可写。*/public final boolean hasRemaining() {return position < limit;}/*** 返回缓冲区是否只读,抽象方法,具体看实现类。*/public abstract boolean isReadOnly();/**/*** 是否直接缓冲区,true为直接缓冲区*/public abstract boolean isDirect();//分割线,上面是公共方法API,下面是包私有方法,我们不能调用的///*** 读模式下游标往右移一位,也就是跳过一个数据。* 返回移动前的游标值。*/final int nextGetIndex() {                          // package-private//因为游标加一 所以要确保游标加一前要小于等于limitif (position >= limit)//当前游标>=limit抛出异常throw new BufferUnderflowException();return position++;}/*** 读模式下游标往右移动n位。* 返回移动前的游标*/final int nextGetIndex(int nb) {                    // package-privateif (limit - position < nb)//判断加n后的游标是否大于limit,大于就抛出异常throw new BufferUnderflowException();int p = position;position += nb;//返回移动前的游标。return p;}/*** 写模式下游标往右移动1位。* 返回移动前的游标* 逻辑与上面的一样。*/final int nextPutIndex() {                          // package-privateif (position >= limit)throw new BufferOverflowException();return position++;}/*** 写模式下游标往右移动n位。* 返回移动前的游标* 逻辑与上面的一样。*final int nextPutIndex(int nb) {                    // package-privateif (limit - position < nb)throw new BufferOverflowException();int p = position;position += nb;return p;}final int markValue() {                             // package-private//返回标记的值return mark;}//把缓冲区置为不可用final void truncate() {                             // package-private//重置mark为-1mark = -1;//啥都置零position = 0;limit = 0;capacity = 0;}final void discardMark() {                          // package-private//重置游标-1mark = -1;}}

limit、position与缓冲区的读写模式:

Buffer有分读模式和写模式,其实质是由limit值和position值决定的。这种模式没有特定的死规定。

写模式:
往缓冲区Buffer里面填充数据的模式。Buffer刚刚创建好时就是处于这个模式。

public class ByteBufferDemo {public static void main(String[] args) {//创建一个10和字节大小的字节缓冲区。ByteBuffer byteBuffer = ByteBuffer.allocate(10);System.out.println("capacity = " + byteBuffer.capacity());System.out.println("limit = " + byteBuffer.limit());System.out.println("position = " + byteBuffer.position());}
}

结果:

相应的图:

解释:
此时的游标position=0,limit=10,所以可以通过put方法(此方法在各自子类中申明定义,基类没有申明)往Buffer中写入n个数据。position也会相应右移n为,但是n必须符合n<=limit-position才行。否则会抛出异常。

写入5个数据后:

public class ByteBufferDemo {public static void main(String[] args) {ByteBuffer byteBuffer = ByteBuffer.allocate(10);//写入5个数据。byteBuffer.put("hello".getBytes());System.out.println("capacity = " + byteBuffer.capacity());System.out.println("limit = " + byteBuffer.limit());System.out.println("position = " + byteBuffer.position());}
}

执行结果:

相应的图:

解释:
因为写入了5个数据,所以position=5,继续写入数据会从缓冲区数组的下标5开始写入,此时剩余的可写数据长度为limit-position=5。

此时,数据写入完成,用通道channel读取缓冲区数据到设备上,通道channel读取缓冲区数据到设备上会把缓冲区的数组从position值开始读,一直读到limit前结束。

假设我们没有把缓冲区从写模式切换到读模式,通道channel读取缓冲区数据时就会从position=5的下标读取到limit=10前的9下标。也就是读取缓冲区数组的5-9下标,这些下标的元素是没有被写入的,所以是错误的。

public class ByteBufferDemo {public static void main(String[] args) {ByteBuffer byteBuffer = ByteBuffer.allocate(10);byteBuffer.put("hello".getBytes());byte[] bytes = new byte[byteBuffer.remaining()];//读取缓冲区,这里就不往设备上读了byteBuffer.get(bytes);System.out.println(Arrays.toString(bytes));}
}

执行结果:

解释:
读取的是后面的没有被写入的缓冲区数组区域,是错误的。

所以要把缓冲区切换为读模式:
读模式:从缓冲区数组中读取缓冲区数组数据的模式。


public class ByteBufferDemo {public static void main(String[] args) {ByteBuffer byteBuffer = ByteBuffer.allocate(10);byteBuffer.put("hello".getBytes());//切换为读模式byteBuffer.flip();System.out.println("capacity = " + byteBuffer.capacity());System.out.println("limit = " + byteBuffer.limit());System.out.println("position = " + byteBuffer.position());}
}//切换为读模式前,limit=10 position=5
//从上面的flip方法的源码可知,该方法把limit值设置为position值,把position设置为0.
//所以调用flip方法后应该是limit=5 position=0.

执行结果:

相应图:

此时处于读模式,如果读取缓冲区的数组从position读取到limit前一个下标,就会读取下标0-4的数据,这样就是正确的读取。所以每次进行数据读取时都要把缓冲区切换为读模式才能正确地读取数据。

public class ByteBufferDemo {public static void main(String[] args) {ByteBuffer byteBuffer = ByteBuffer.allocate(10);byteBuffer.put("hello".getBytes());byteBuffer.flip();byte[] bytes = new byte[byteBuffer.remaining()];byteBuffer.get(bytes);System.out.println(Arrays.toString(bytes));System.out.println("==============>读取数据后");System.out.println("capacity = " + byteBuffer.capacity());System.out.println("limit = " + byteBuffer.limit());System.out.println("position = " + byteBuffer.position());//读完后要情况缓冲区,重写开始写数据System.out.println("==============>清空缓冲区后");byteBuffer.clear();System.out.println("capacity = " + byteBuffer.capacity());System.out.println("limit = " + byteBuffer.limit());System.out.println("position = " + byteBuffer.position());}
}

执行结果:

解释:

  1. 读到的数据不为0,正确地读到了数据。
  2. 读取数据后,缓冲区将变得不可用,因为limit-position=0,不能写也不能继续读。
  3. 需要把缓冲区清空后恢复到写模式来继续写数据。

Java NIO学习篇之缓冲区Buffer详解相关推荐

  1. Java NIO学习篇之缓冲区ByteBuffer详解

    定义: ByteBuffer是Buffer的实现类之一,是一个通用的缓冲区,功能要比其他缓冲区子类多.支持直接内存.是一个抽象类.子类实现是HeapByteBuffer(非直接缓冲区子类),Direc ...

  2. Java NIO学习篇之缓冲区CharSet详解

    定义: CharSet是对java nio编码解码的解决方案,专门负责字符的编码和解码. 编码:字符数组.字符串 ===> 字节数组. 解码:字节数组 ==> 字符数组.字符串 API详解 ...

  3. Java NIO学习篇之通道FileChannel详解

    定义: FileChannel是Java NIO对应于磁盘等存储设备文件操作的通道. 常用API详解: 获取FileChannel的API /** * 打开一个与文件的连接通道,用于进行文件操作. * ...

  4. Java NIO学习篇之通道Channel详解

    定义: Channel:通道,运输的介质,可以大致比喻成铁路的铁轨,连接着两个车站,而channel用于打开与IO设备的连接,比如磁盘,套接字等. 通道使用完需要关闭. 与传统IO的Stream比较: ...

  5. Java NIO学习篇之直接缓冲区和非直接缓冲区

    定义 以上是书深入理解java虚拟机对直接内存的描述.直接缓冲区用的就是直接内存. java nio字节缓冲区要么是直接的,要么是非直接的.如果为直接字节缓冲区,则java虚拟机会尽最大努力直接在此缓 ...

  6. java 检查bytebuf长度_Java学习笔记16-Netty缓冲区ByteBuf详解

    Java学习笔记16-Netty缓冲区ByteBuf详解 Netty自己的ByteBuf ByteBuf是为解决ByteBuffer的问题和满足网络应用程序开发人员的日常需求而设计的. JDK Byt ...

  7. [网络安全学习篇2]:IP详解及简单的DOS命令(千峰网络安全视频笔记 2 day)

    引言:我的系列博客[网络安全学习篇]上线了,小编也是初次创作博客,经验不足:对千峰网络信息安全开源的视频公开课程的学习整理的笔记整理的也比较粗糙,其实看到目录有300多集的时候,讲道理,有点怂了,所以 ...

  8. Java NIO学习篇之NIO的基本认识

    定义: NIO:是从jdk1.4提出的,本意是New IO(相对于传统的IO),也叫 No Blocked IO(只相对于网络IO),它的出现弥补传统IO的不足,提出了更加高效的方式. NIO对于网络 ...

  9. Java NIO学习系列一:Buffer

    从本文开始我会开始总结NIO部分,Java NIO(注意,这里的NIO其实叫New IO)是用来替换标准Java IO以及Java 网络API的,其提供了一系列不同与标准IO API的方式来处理IO, ...

最新文章

  1. 玉米田Corn Fields
  2. 3.6 激活函数-深度学习-Stanford吴恩达教授
  3. html表格数据点击事件,如何在iview的table单元格里实现点击事件?
  4. python从命令行获取参数_python从命令行获取参数操作
  5. web服务器-nginx
  6. 阿里面试回来,想和 Java 程序员谈一谈
  7. 垃圾回收算法_垃圾回收算法有哪些
  8. numpy找到最大值坐标_学习OpenCV convexhull并用numpy实现
  9. maven pom.xml详解
  10. Matlab根据滤波器系数画出幅频特性曲线
  11. 为何架设好服务器不显示补丁,WSUS补丁服务器分发后,客户端不在管理控制台显示 - winServer论坛 - 51CTO技术论坛_中国领先的IT技术社区...
  12. 【NTC 热敏电阻与 Arduino 读取温度】
  13. python语言实验——某年某月的天数 OJ1160
  14. 程序员值得收藏的41个电子书搜索网站
  15. Linux 痕迹清理
  16. QQ语音对方会听到自己电脑声音
  17. linux 命令总结之tr命令
  18. 游戏App上架iOS被拒各种奇葩问题
  19. python中赋值语句和判断语句结合_Python 3 学习的第二小节——判断赋值语句与简单循环...
  20. Newdex Swap闪兑系统已通过PeckShield安全审计服务

热门文章

  1. 初探Thymeleaf模板引擎
  2. Java讲课笔记16:内部类
  3. 【BZOJ4034】T2,树链剖分练习
  4. 2011年工作简单总结
  5. 6.边缘检测:梯度——回顾、简化的图像、边缘概念_1
  6. bzoj 2962 序列操作 线段树
  7. 【英语学习】【加州教材】【G1】【科学】Science目录及术语表
  8. (四)基于Multisim的超外差接收系统:检波器的设计
  9. vue保存页面的值_vue中使用localStorage存储信息
  10. c52单片机c语言编程,c52单片机c语言编程怎样实现阴历查询