(二:NIO系列) Java NIO Buffer
出处:Java NIO Buffer
Buffer是一个抽象类,位于java.nio包中,主要用作缓冲区。Buffer缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。
注意:Buffer是非线程安全类。
1.1. Buffer类型的标记属性
Buffer在内部也是利用byte[]作为内存缓冲区,只不过多提供了一些标记变量属性而已。当多线程访问的时候,可以清楚的知道当前数据的位置。
有三个重要的标记属性:capacity、position、limit。
除此之外,还有一个标记属性:mark,可以临时保持一个特定的position,需要的时候,可以恢复到这个位置。
1.1.1. capacity
作为一个内存块,Buffer有一个固定的大小值,也叫“capacity”。你只能往里写capacity个数据。一旦Buffer满了,就不能再写入。
capacity与缓存的数据类型相关。指的不是内存的字节的数量,而是写入的对象的数量。比如使用的是一个保存double类型的Buffer(DoubleBuffer),写入的数据是double类型, 如果其 capacity 是100,那么我们最多可以写入100个 double 数据.
capacity一旦初始化,就不能不会改变。
原因是什么呢?
Buffer对象在初始化时,会按照capacity分配内部的内存。内存分配好后,大小就不能变了。分配内存时,一般使用Buffer的抽象子类ByteBuffer.allocate()方法,实际上是生成ByteArrayBuffer类。
1.1.2. position
position表示当前的位置。position在Buffer的两种模式下的值是不同的。
读模式下的position的值为:
当读取数据时,也是从position位置开始读。当将Buffer从写模式切换到读模式,position会被重置为0。当从Buffer的position处读取数据时,position向前移动到下一个可读的位置。
写模式下的position的值为:
在写模式下,当写数据到Buffer中时,position表示当前的写入位置。初始的position值为0,position最大可为capacity – 1。
每当一个数据(byte、long等)写到Buffer后, position会向后移动到下一个可插入数据的可写的位置。
1.1.3. limit
limit表示最大的限制。在Buffer的两种模式下,limit的值是不同的。
读模式下的limit的值为:
读模式下,Buffer的limit表示最多能从Buffer里读多少数据。当Buffer从写切换到读模式时,limit的值,设置成写模式的position 值,也是是写模式下之前写入的数量值。
举一个简单的例子,说明一下读模式下的limit值:
先向Buffer写数据,Buffer在写模式。每写入一个数据,position向后面移动一个位置,值加一。假定写入了5个数,当写入完成后,position的值为5。这时,就可以读取数据了。当开始读取数据时,Buffer切换到读模式。limit的值,先会被设置成写入数据时的position值。这里是5,表示可以读取的最大限制是5个数。
写模式下的limit的值为:
limit表示表示可以写入的数据最大限制。在切换成写模式时,limit的值会被更改,设置成Buffer的capacity,为Buffer的容量。
1.1.4. 总结:
在Buffer的四个属性之间,有一个简单的数量关系,如下:
capacity>=limit>=position>=mark>=0
用一个表格,对着4个属性的进行一下对比:
属性 |
描述 |
capacity |
容量,即可以容纳的最大数据量;在缓冲区创建时被设定并且不能改变 |
limit |
上界,缓冲区中当前数据量 |
position |
位置,下一个要被读或写的元素的索引 |
mark(位置标记) |
调用mark(pos)来设置mark=pos,再调用reset()可以让position恢复到标记的位置即position=mark |
1.2. Buffer 类型
在NIO中主要有八种缓冲区类,分别如下:
ByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
MappedByteBuffer
这些 Buffer 覆盖了能从 IO 中传输的所有的 Java 基本数据类型。其中MappedByteBuffer是专门用于内存映射的一种ByteBuffer)。
1.3. Buffer中的方法
本节结合Buffer的几个方法,做了一个完整的实例,包含了从Buffer实例的获取、写入、读取、重复读、标记和重置等一个系列操作的完整流程。
1.3.1. 获取allocate()方法
为了获取一个 Buffer 对象,我们首先需要分配内存空间。分配内存空间使用allocate()方法。
public static void allocate() {
IntBuffer intBuffer = IntBuffer.allocate(20);
System.out.println("----------after allocate-----------"); // position:位置,下一个要被读或写的元素的索引
System.out.println("position=" + intBuffer.position());
// limit: 上界,缓冲区中当前数据量
System.out.println("limit=" + intBuffer.limit());
// capacity:容量,即可以容纳的最大数据量;在缓冲区创建时被设定并且不能改变
System.out.println("capacity=" + intBuffer.capacity());
}
这里我们分配了20* sizeof(int)字节的内存空间.
输出的结果如下:
1.3.2. 写put()方法
调用allocate分配内存后,buffer处于写模式。可以通过buffer的put方法写入数据。put方法有一个要求,需要写入的数据类型与Buffer的类型一致。
接着前面的例子,继续上写入的实例代码:
public static void putDate() {
IntBuffer intBuffer = IntBuffer.allocate(20);
for(int i = 0; i < 5; i++) {
intBuffer.put(i);
}
System.out.println("----------after put-----------");
System.out.println("position=" + intBuffer.position());
System.out.println("limit=" + intBuffer.limit());
System.out.println("capacity=" + intBuffer.capacity());
}
写入5个元素后,输出的结果为:
调用了put方法后,buffer处于写模式。写入5个数据后,可以看到,position 变成了5,指向了第6个可以写入的元素位置。
除了在新建的buffer之后,如何将buffer切换成写模式呢?
调用 Buffer.clear() 清空或 Buffer.compact()压缩方法,可以将 Buffer 转换为写模式。
1.3.3. 读切换flip()方法
put方法写入数据之后,可以直接从buffer中读吗?
呵呵,不能。
还需要调用filp()走一个转换的工作。flip()方法是Buffer的一个模式转变的重要方法。简单的说,是写模式翻转成读模式——写转读。
接着前面的例子,继续上flip()方法的例子代码:
public static void flipDate() {
IntBuffer intBuffer = IntBuffer.allocate(20);
for(int i = 0; i < 5; i++) {
intBuffer.put(i);
}
intBuffer.flip();
System.out.println("----------after flip-----------");
System.out.println("position=" + intBuffer.position());
System.out.println("limit=" + intBuffer.limit());
System.out.println("capacity=" + intBuffer.capacity());
}
接着上一步的写入,在调用flip之后,buffer的属性有一些奇妙的变化。
运行上面的程序,输出如下:
注意到没有,position从前一个小节的5,变成了0。而limit的保存了之前的position,从20变成5。
这是为什么呢? 先看其源码,Buffer.flip()方法的源码如下:
public final Buffer flip() {
limit = position;
position = 0;
mark = UNSET_MARK;
return this;
}
解释一下啊,flip()方法主要是从读模式切换成写模式,调整的规则是:
(1)首先设置可读的长度limit。将写模式下的Buffer中内容的最后位置position值变为读模式下的limit位置值,新的limit值作为读越界位置;
(2)其次设置读的起始位置。将当position值置为0,表示从0位置开始读。转换后重头开始读。
(3)如果之前有mark保存的标记位置,还要消除。因为那是写模式下的mark标记。
1.3.4. 读get() 方法
get()读数据很简单,每次从postion的位置读取一个数据,并且进行相应的buffer属性的调整。
接着前面的例子,继续上读取buffer的例子代码:
public static void getDate() {
IntBuffer intBuffer = IntBuffer.allocate(20);
for(int i = 0; i < 5; i++) {
intBuffer.put(i);
}
intBuffer.flip();
System.out.println("----------after getDate 2-----------");
for(int i = 0; i < 2; i++) {
int j = intBuffer.get();
System.out.println("j = " + j);
}
System.out.println("position=" + intBuffer.position());
System.out.println("limit=" + intBuffer.limit());
System.out.println("capacity=" + intBuffer.capacity()); System.out.println("----------after getDate 3-----------"); for(int i = 0; i < 3; i++) {
int j = intBuffer.get();
System.out.println("j = " + j);
}
System.out.println("position=" + intBuffer.position());
System.out.println("limit=" + intBuffer.limit());
System.out.println("capacity=" + intBuffer.capacity());
}
先读2个,再读3个,输出的Buffer属性值如下:
读完之后,缓存的position 值变成了一个没有数据的元素位置,和limit的值相等,已经不能在读了。
读完之后,是否可以直接写数据呢?
不能。一旦读取了所有的 Buffer 数据,那么我们必须清理 Buffer,让其重新可写,可以调用 Buffer.clear() 或 Buffer.compact()。
1.3.5. 倒带rewind()方法
已经读完的数据,需要再读一遍,可以直接使用get方法吗?
答案是,不能。怎么办呢?
使用rewind() 方法,可以进重复读的设置。rewind()也叫倒带,就像播放磁带一样,倒回去,重新播放。
接着前面的例子,继续上重复读的例子代码:
public static void rewind() {
IntBuffer intBuffer = IntBuffer.allocate(20);
for(int i = 0; i < 5; i++) {
intBuffer.put(i);
}
System.out.println("----------Test rewind: before read-----------");
intBuffer.flip();
for(int i = 0; i < 2; i++) {
int j = intBuffer.get();
System.out.println("j = " + j);
}
System.out.println("position=" + intBuffer.position());
System.out.println("limit=" + intBuffer.limit());
System.out.println("capacity=" + intBuffer.capacity()); for(int i = 0; i < 3; i++) {
int j = intBuffer.get();
System.out.println("j = " + j);
}
System.out.println("position=" + intBuffer.position());
System.out.println("limit=" + intBuffer.limit());
System.out.println("capacity=" + intBuffer.capacity()); intBuffer.rewind();
System.out.println("----------after rewind-----------");
System.out.println("position=" + intBuffer.position());
System.out.println("limit=" + intBuffer.limit());
System.out.println("capacity=" + intBuffer.capacity());
}
实例的结果如下:
flip()方法主要是调整Buffer的 position 属性,调整的规则是:
(1)position设回0,所以你可以重读Buffer中的所有数据;
(2)limit保持不变,数据量还是一样的,仍然表示能从Buffer中读取多少个元素。
Buffer.rewind()方法的源码如下:
public final Buffer rewind() {
position = 0;
mark = -1;
return this; }
看到了实现的源码应该就会清楚flip()的作用了。rewind()方法与flip()很相似,区别在于rewind()不会影响limit,而flip()会重设limit属性值。
1.3.6. mark( )和reset( )
Buffer.mark()方法将当前的 position 的值保存起来,放在mark属性中,让mark属性记住当前位置,之后可以调用Buffer.reset()方法将 position 的值恢复回来。
Buffer.mark()和Buffer.reset()方法是一一配套使用的。都是需要操作mark属性。
在重复读的实例代码中,读到第3个元素,使用mark()方法,设置一下mark 属性,保存为第3个元素的位置。
下面上实例,演示一下mark和reset的结合使用。
实例继续接着上面的rewind倒带后的buffer 状态,开始reRead重复读,实例代码如下:
public static void reRead(){
Logger.info("------------after reRead------------------");
for (int i = 0; i < 5; i++){
int j = byteBuffer.get();
Logger.info("j = " + j);
if (i == 2){
byteBuffer.mark();
}
}
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}
然后接着上一段reset()实例代码,如下:
public static void afterReset(){
Logger.info("------------after reset------------------");
byteBuffer.reset();
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}
上面我们调用 mark() 方法将当前的 position 保存起来(在读模式,因此保存的是读的 position)。接着使用 reset() 恢复原来的读 position,因此读 position 就为3,可以再次开始从第2个元素读取数据.
输出的结果是:
afterReset |> ------------after reset------------------
afterReset |> position=3
afterReset |> limit=5
afterReset |> capacity=20
调用reset之后,position的值为3,表示可以从第三个元素开始读。
Buffer.mark()和Buffer.reset()其实很简答,其源码如下:
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;
}
1.3.7. clear()清空
clear()方法的作用有两种:
(1)写模式下,当一个 buffer 已经写满数据时,调用 clear()方法,切换成读模式,可以从头读取 buffer 的数据;
(2)读模式下,调用 clear()方法,将buffer切换为写模式,将postion为清零,limit设置为capacity最大容量值,可以一直写入,直到buffer写满。
接着上面的实例,使用实例代码,演示一下clear方法。
代码如下:
public static void clearDemo(){
Logger.info("------------after clear------------------");
byteBuffer.clear();
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
}
运行之后,结果如下:
main |> 清空
clearDemo |> ------------after clear------------------
clearDemo |> position=0
clearDemo |> limit=20
clearDemo |> capacity=20
在clear()之前,buffer是在读模式下。clear()之后,可以看到,清空了position 的值,设置为起始位置。
clear 方法源码:
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
根据源码我们可以知道,clear 将 positin 设置为0,将 limit 设置为 capacity。
1.4. Buffer 的使用
1.4.1. 使用的基本步骤
总结一下,使用 NIO Buffer 的步骤如下:
一:将数据写入到 Buffer 中;
二:调用 Buffer.flip()方法,将 NIO Buffer 转换为读模式;
三:从 Buffer 中读取数据;
四:调用 Buffer.clear() 或 Buffer.compact()方法,将 Buffer 转换为写模式。
当我们将数据写入到 Buffer 中时,Buffer 会记录我们已经写了多少的数据;当我们需要从 Buffer 中读取数据时,必须调用 Buffer.flip()将 Buffer 切换为读模式。
1.4.2. 完整的实例代码
import com.crazymakercircle.util.Logger; import java.nio.IntBuffer; public class BufferDemo
{
static IntBuffer byteBuffer = null; public static void allocatTest()
{
byteBuffer = IntBuffer.allocate(20); Logger.info("------------after allocate------------------");
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
} public static void putTest()
{
for (int i = 0; i < 5; i++)
{
byteBuffer.put(i);
}
Logger.info("------------after putTest------------------");
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
} public static void flipTest()
{
byteBuffer.flip();
Logger.info("------------after flipTest ------------------"); Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
} public static void rewindTest()
{
byteBuffer.rewind();
Logger.info("------------after flipTest ------------------"); Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
} public static void getTest()
{
Logger.info("------------after &getTest 2------------------");
for (int i = 0; i < 2; i++)
{
int j = byteBuffer.get();
Logger.info("j = " + j);
} Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity()); Logger.info("------------after &getTest 3------------------"); for (int i = 0; i < 3; i++)
{
int j = byteBuffer.get();
Logger.info("j = " + j);
}
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
} public static void reRead()
{
Logger.info("------------after reRead------------------");
for (int i = 0; i < 5; i++)
{
int j = byteBuffer.get();
Logger.info("j = " + j); if (i == 2)
{
byteBuffer.mark();
}
}
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
} public static void afterReset()
{
Logger.info("------------after reset------------------"); byteBuffer.reset();
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
} public static void clearDemo()
{
Logger.info("------------after clear------------------"); byteBuffer.clear();
Logger.info("position=" + byteBuffer.position());
Logger.info("limit=" + byteBuffer.limit());
Logger.info("capacity=" + byteBuffer.capacity());
} public static void main(String[] args)
{
Logger.info("分配内存");
allocatTest();
Logger.info("写入");
putTest();
Logger.info("翻转");
flipTest();
Logger.info("读取");
getTest();
Logger.info("重复读");
rewindTest();
reRead();
Logger.info("make&reset写读");
afterReset();
Logger.info("清空");
clearDemo();
}
}
(二:NIO系列) Java NIO Buffer的更多相关文章
- 【Java nio】java nio笔记
缓冲区操作:缓冲区,以及缓冲区如何工作,是所有I/O的基础.所谓“输入/输出”讲的无非就是把数据移出货移进缓冲区.进程执行I/O操作,归纳起来也就是向操作系统发出请求,让它要么把缓冲区里的数据排干,要 ...
- Java-杂项-java.nio:java.nio
ylbtech-Java-杂项-java.nio:java.nio java.nio全称java non-blocking IO,是指jdk1.4 及以上版本里提供的新api(New IO) ,为所有 ...
- java的nio之:java的nio系列教程之buffer的概念
一:java的nio的buffer==>Java NIO中的Buffer用于和NIO通道Channel进行交互.==>数据是从通道channel读入缓冲区buffer,从缓冲区buffer ...
- 转:Java NIO系列教程(三) Buffer
Java NIO中的Buffer用于和NIO通道进行交互.如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的. 缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存.这块内存被包装成NIO ...
- 【NIO】Java NIO之选择器
一.前言 前面已经学习了缓冲和通道,接着学习选择器. 二.选择器 2.1 选择器基础 选择器管理一个被注册的通道集合的信息和它们的就绪状态,通道和选择器一起被注册,并且选择器可更新通道的就绪状态,也可 ...
- 【NIO】Java NIO之通道
一.前言 前面学习了缓冲区的相关知识点,接下来学习通道. 二.通道 2.1 层次结构图 对于通道的类层次结构如下图所示. 其中,Channel是所有类的父类,其定义了通道的基本操作.从 Channel ...
- 【NIO】Java NIO之缓冲
一.前言 在笔者打算学习Netty框架时,发现很有必要先学习NIO,因此便有了本博文,首先介绍的是NIO中的缓冲. 二.缓冲 2.1 层次结构图 除了布尔类型外,其他基本类型都有相对应的缓冲区类,其继 ...
- 我的Java开发学习之旅------>Java NIO 报java.nio.charset.MalformedInputException: Input length = 1异常
今天在使用Java NIO的Channel和Buffer进行文件操作时候,报了java.nio.charset.MalformedInputException: Input length = 1异常, ...
- 【JAVA NIO】java NIO
本文是博主深入学习Netty前的一些铺垫,之前只是使用Netty,用的很粗暴,导包,上网找个DEMO就直接用,对Netty中的组件了解并不深入. 于是再此总结下基础,并对一些核心组件作如下记录: 1. ...
- Java入门系列Java NIO
jdk1.4中新加入的NIO,引入了通道与缓冲区的IO方式,它可以调用Native方法直接分配堆外内存,这个堆外内存就是本机内存,不会影响到堆内存的大小.
随机推荐
- SpringBoot使用Easypoi导出excel示例
SpringBoot使用Easypoi导出excel示例 https://blog.csdn.net/justry_deng/article/details/84842111
- div拖拽到iframe上方 导致 缩放和拖拽的不平滑和鼠标事件未放开 解决方法
思路一:用在开始进行缩放(触发了resizable的start事件)为iframe添加z-index属性,将iframe放置在最下层. $("#draggable").resiza ...
- Mysql数据库表类型
MySQL的数据表类型很多,其中比较重要的是MyISAM,InnoDB这两种. 这两种类型各有优缺点,需要根据实际情况选择适合的,MySQL支持对不同的表设置不同的类型.下面做个对比: MyISA ...
- RabbitMQ 工作图解
(转网上的图) (原文地址 ,http://www.cnblogs.com/knowledgesea/p/5296008.html)
- UVALive 6858 Frame (模拟)
Frame 题目链接: http://acm.hust.edu.cn/vjudge/contest/130303#problem/D Description http://7xjob4.com1.z0 ...
- 重新理解了重定向,利用重定向可以防止用户重复提交表单(兼谈springmvc重定向操作)
自己用springmvc框架有一段时间了,但是都还一直分不清楚什么时候应该用转发,什么时候应该用重定向.可能用转发的情形太多了,以致于自己都忘记了还有重定向. 当用户提交post请求之后,刷新页面就会 ...
- 接上SQL SERVER的锁机制(一)——概述(锁的种类与范围)
二.完整的锁兼容性矩阵(见下图) 对上图的是代码说明:见下图. 三.下表列出了数据库引擎可以锁定的资源. 名称 资源 缩写 编码 呈现锁定时,描述该资源的方式 说明 数据行 RID RID 9 文件编 ...
- mongoexport导出记录到csv文件
root@service:~# mongoexport -d prod -c employees -f _id,platform,phone --csv -o /opt/employees.csv 2 ...
- TreeSet 源码分析
TreeSet 1)底层由 TreeMap 支持的 Set 接口实现,Set 中的元素按照自然顺序或指定的比较器排序. 创建实例 /** * 支持此 Set 的底层的 TreeMap 对象 */ pr ...
- PHP密码和token
密码 直接md5和sha1不安全!!! crypt()和hash_equals(): http://php.net/manual/zh/function.crypt.php <?php // c ...