Java NIO学习系列一:Buffer
前面三篇文章中分别总结了标准Java IO系统中的File、RandomAccessFile、I/O流系统,对于I/O系统从其继承体系入手,力求对类数量繁多的的I/O系统有一个清晰的认识,然后结合一些I/O的常规用法来加深对标准I/O系统的掌握,感兴趣的同学可以看一下:
<<Java I/O系统学习系列一:File和RandomAccessFile>>
<<Java I/O系统学习系列三:I/O流的典型使用方式>>
从本文开始我会开始总结NIO部分,Java NIO(注意,这里的NIO其实叫New IO)是用来替换标准Java IO以及Java 网络API的,其提供了一系列不同与标准IO API的方式来处理IO,从JDK1.4开始引入,其目的在于提高速度。
之所以能够提高速度是因为其所使用的结构更接近于操作系统执行I/O的方式:通道和缓冲器。我们可以把它想象成一个煤矿,通道是一个包含煤层(数据)的矿藏,而缓冲器则是派送到矿藏的卡车。卡车满载煤炭而归,我们再从卡车上获得煤炭。也就是说,我们并没有直接和通道交互,而是和缓冲器交互,并把缓冲器派送到通道。通道要么从缓冲器获得数据,要么向缓冲器发送数据。
在标准IO的API中,使用字节流和字符流。而在Java NIO中是使用Channel(通道)和Buffer(缓冲区),数据从channel中读取到buffer中,或从buffer写入到channel中。Java NIO类库中的核心组件为:
- Buffer
- Channel
- Selector
本文中我们会着重总结Buffer相关的知识点(后面的文章中会继续介绍Channel即Selector),本文主要会围绕如下几个方面展开:
1. Buffer简介
Java NIO中的Buffer一般和Channel配对使用。可以从Channel中读取数据到Buffer,或者写数据到Channel中。一个Buffer其实就是代表一个内存块,你可以往里面写数据或者从中读取数据。这个内存块被包装成一个Buffer对象,并且提供了一系列方法使得操作内存块更便捷。
通过Buffer来读写数据通常包括如下4步:
- 写数据到Buffer中;
- 调用buffer.flip();
- 从Buffer读取数据;
- 调用buffer.clear()或buffer.compact();
当往Buffer中写数据时,Buffer能够记录写了多少数据。当要从Buffer中读取数据时,就需要通过调用flip()方法将Buffer从写模式切换到读模式。一旦读完所有数据,需要清空Buffer,让它再次处于写状态。可以通过调用clear()或compact()方法来完成这一步:
- clear()方法会清空整个Buffer;
- compact()方法仅仅清空你已经从Buffer中读取的数据,未读数据会被移动到Buffer起始位置,可以紧接着未读的数据写入新的数据;
如下是一个简单的使用例子,通过FileChannel和ByteBuffer读取pom.xml文件,并逐字节输出:
public class BufferDemo { public static void main(String[] args) {
try {
RandomAccessFile raf = new RandomAccessFile("pom.xml","r");
FileChannel channel = raf.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(48);
int byteReaded = channel.read(buffer);
while(byteReaded != -1) {
buffer.flip();
while(buffer.hasRemaining()) {
System.out.print((char)buffer.get());
}
buffer.clear();
byteReaded = channel.read(buffer);
}
raf.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
2. Buffer的内部结构
上面说到Buffer封装了一块内存块,并提供了一系列的方法使得可以方便地操纵内存中的数据。至于如何操纵?Buffer提供了4个索引。要理解Buffer的工作原理,就需要从这些索引说起:
- capacity(容量);
- position(位置);
- limit(界限);
- mark(标记);
其中position和limit的含义取决于Buffer是处于什么模式(读或者写模式),capacity的含义则和模式无关,而mark则只是一个标记,可以通过mark()方法进行设置。下图描述了读写模式下三种属性分别代表的含义,详细解释见下文:
2.1 Capacity
Buffer代表一个内存块,所以其是有确定大小的,也叫“容量”。可以往buffer中写入各种数据如byte、long、chars等,当Buffer被写满了则需要将其清空(可以通过读取数据或者清空数据)之后才能继续写入数据。
2.2 Position
当往Buffer中写数据时,写入的地方就是所谓的position,其初始值为0,最大值为capacity-1。当往Buffer中写入一个byte或者long的数据时,position会前移以指向下一个即将被插入的位置。
当从Buffer中读取数据时,读取数据的地方就是所谓的position。当执行flip将Buffer从写模式切换到读模式时,position会被重置为0。随着不断从Buffer读取数据,position也会不断后移指向下一个将被读取的数据。
2.3 Limit
在写模式下,Buffer的limit是指能够往Buffer中写入多少数据,其值等于Buffer的capacity。
在读模式下,Buffer的limit是指能够从Buffer读取多少数据出来。因此当从写模式切换到读模式下时,limit就被设置为写模式下的position的值(这很好理解,写了多少才能读到多少)。
2.4 Mark
mark其实就是一个标记,可以通过mark()方法设置,设置值为当前的position。
下面是用于设置和复位索引以及查询它们值的方法:
capacity() 返回缓冲区容量
clear() 清空缓冲区,将position设置为0,limit设置为容量。我们可以调用此方法覆写缓冲区
flip() 将limit设置为position,position设置为0。此方法用于准备从缓冲区读取已经写入的数据
limit() 返回limit值
limit(int lim) 设置limit值
mark() 将mark设置为position
position() 返回position值
position(int pos) 设置position值
remaining() 返回(limit - position)
hasRemaining() 若有介于position和limit之间的元素,则返回true
3. Buffer的主要API
除了如上和索引相关的方法之外,Buffer还提供了一些其他的方法用于写入、读取等操作。
3.1 给Buffer分配空间
要获得一个Buffer对象就可以通过Buffer类的allocate()方法来实现,如下分别是分配一个48字节的ByteBuffer和1024字符的CharBuffer:
ByteBuffer buf = ByteBuffer.allocate(48);
CharBuffer buf = CharBuffer.allocate(1024);
3.2 往Buffer中写数据
有两种方式往Buffer中写入数据:
- 从Channel中往Buffer写数据;
- 通过Buffer的put()方法写入数据;
int bytesRead = inChannel.read(buf); // read into buffer
buf.put(127);
put()方法有多个重载版本,比如从指定位置写入数据,或写入字节数组等。
3.3 flip()
flip()方法将Buffer从写模式切换到读模式。调用flip()方法会将position设为0,limit设为position之前的值。
3.4 从Buffer读数据
也有两种方法从Buffer读取数据:
- 从Buffer中读数据到Channel中;
- 调用Buffer的get()方法读取数据;
int bytesWritten = inChannel.write(buf); // read from buffer into channel
byte aByte = buf.get();
3.5 rewind()
rewind()方法将position设置为0,可以从头开始读数据。
3.6 clear()和compact()
当从Buffer读取数据结束之后要将其切换回写模式,可以调用clear()、compact()这两个方法,两者之间的区别如下:
调用clear(),会将position设为0,limit设为capacity,也就是说Buffer被清空了,但是里面的数据仍然存在,只是这时没有标记可以告诉你哪些数据是已读,哪些是未读。
如果读取到一半需要写入数据,但是未读的数据稍后还需要读取,这时可以使用compact(),其会将所有未读取的数据复制到Buffer的前面,将position设置到这些数据后面,limit设置为capacity,所以此时是从未读的数据后面开始写入新的数据。
3.7 mark()和reset()
调用mark()方法可以标志一个指定的位置(即设置mark值),之后调用reset()方法时position又会回到之前标记的位置。
4. ByteBuffer
ByteBuffer是一个比较基础的缓冲器,继承自Buffer,是可以存储未加工字节的缓冲器,并且也是唯一直接与通道交互的缓冲器。可以通过ByteBuffer的allocate()方法来分配一个固定大小的ByteBuffer,并且其还有一个方法选择集,用于以原始的字节形式或基本类型输出和读取数据。但是,没办法输出或读取对象,即使是字符串对象也不行。这种处理虽然很低级,但却正好,因为这是大多数操作系统中更有效的映射方式。
ByteBuffer也分为直接和非直接缓冲器,通过allocate()创建的就是非直接缓冲器,而通过allocateDirect()方法就可以创建出一个缓冲器直接缓冲器,这是一个与操作系统有更高耦合性的缓冲器,也就意味着它能够带来更高的速度,但是分配的开支也会更大。
尽管ByteBuffer只能保存字节类型的数据,但是它具有可以从其所容纳的字节中产生出各种不同基本类型值的方法。下面的例子展示怎样使用这些方法来插入和抽取各种数值:
public class GetData {
private static final int BSIZE = 1024;
public static void main(String[] args){
ByteBuffer bb = ByteBuffer.allocate(BSIZE);
int i = 0;
while(i++ < bb.limit())
if(bb.get() != 0)
System.out.println("nonzero");
System.out.println("i = " + i);
bb.rewind();
// store and read a char array:
bb.asCharBuffer().put("Howdy!");
char c;
while((c = bb.getChar()) != 0)
System.out.print(c + " ");
System.out.println();
bb.rewind();
// store and read a short:
bb.asShortBuffer().put((short)471142);
System.out.println(bb.getShort());
bb.rewind();
// sotre and read an int:
bb.asIntBuffer().put(99471142);
System.out.println(bb.getInt());
bb.rewind();
// store and read a long:
bb.asLongBuffer().put(99471142);
System.out.println(bb.getLong());
bb.rewind();
// store and read a float:
bb.asFloatBuffer().put(99471142);
System.out.println(bb.getFloat());
bb.rewind();
// store and read a double:
bb.asDoubleBuffer().put(99471142);
System.out.println(bb.getDouble());
bb.rewind();
}
}
5. Buffer类型
Java NIO中包含了如下几种Buffer:
- ByteBuffer
- MappedByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
这些Buffer类型代表着不同的数据类型,使得可以通过Buffer直接操作如char、short等类型的数据而不是字节数据。其中MappedByteBuffer略有不同,后面会专门总结。
通过ByteBuffer我们只能往Buffer直接写入或者读取字节数组,但是通过对应类型的Buffer比如CharBuffer、DoubleBuffer等我们可以直接往Buffer写入char、double等类型的数据。或者利用ByteBuffer的asCharBuffer()、asShorBuffer()等方法获取其视图,然后再使用其put()方法即可直接写入基本数据类型,就像上面的例子。
这就是视图缓冲器(view buffer)可以让我们通过某个特定的基本数据类型的视窗查看其底层的ByteBuffer。ByteBuffer依然是实际存储数据的地方,“支持”着前面的视图,因此对视图的任何修改都会映射成为对ByteBuffer中数据的修改。这使得我们可以很方便地向ByteBuffer插入数据。视图还允许我们从ByteBuffer一次一个地(与ByteBuffer所支持的方式相同)或者成批地(通过放入数组中)读取基本类型值。在下面的例子中,通过IntBuffer操纵ByteBuffer中的int型数据:
public class IntBufferDemo {
private static final int BSIZE = 1024;
public static void main(String[] args){
ByteBuffer bb = ByteBuffer.allocate(BSIZE);
IntBuffer ib = bb.asIntBuffer();
// store an array of int:
ib.put(new int[]{11,42,47,99,143,811,1016});
// absolute location read and write:
System.out.println(ib.get(3));
ib.put(3,1811);
// setting a new limit before rewinding the buffer.
ib.flip();
while(ib.hasRemaining()){
int i = ib.get();
System.out.println(i);
}
}
}
上例中先用重载后的put()方法存储一个整数数组。接着get()和put()方法调用直接访问底层ByteBuffer中的某个整数位置。这些通过直接与ByteBuffer对话访问绝对位置的方式也同样适用于基本类型。
6. 总结
本文简单总结了Java NIO(Java New IO),其目的在于提高速度。Java NIO类库中主要包括Buffer、Channel、Selector,本文主要总结了Buffer相关的知识点:
- Buffer叫缓冲器,她是和Channel(通道)交互的,可以从channel中读数据到buffer中,或者从buffer往channel中写数据;
- Buffer内部封装了一块内存,提供了一系列API使得可以方便地操作内存中的数据。其内部是通过capacity、position、limit、mark等变量来跟踪标记封装的数据的;
- ByteBuffer是最基本的Buffer,是唯一可以直接与通道交互的缓冲器,其可以直接操纵字节数据或字节数组;
- 除了ByteBuffer之外,Buffer还有许多别的类型如:MappedByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer;
- 虽然只有ByteBuffer能够直接和通道交互,但是可以从ByteBuffer获取多种不同的视图缓冲器,进而同时具备了直接操作基本数据类型和与通道交互的能力;
基础知识的总结也许是比较枯燥的,但是如果你已经看到这里说明你很有耐心,如果觉得对你有帮助的话,不妨点个赞关注一下吧^_^
Java NIO学习系列一:Buffer的更多相关文章
- Java NIO学习系列四:NIO和IO对比
前面的一些文章中我总结了一些Java IO和NIO相关的主要知识点,也是管中窥豹,IO类库已经功能很强大了,但是Java 为什么又要引入NIO,这是我一直不是很清楚的?前面也只是简单提及了一下:因为性 ...
- Java NIO学习系列三:Selector
前面的两篇文章中总结了Java NIO中的两大基础组件Buffer和Channel的相关知识点,在NIO中都是通过Channel和Buffer的协作来读写数据的,在这个基础上通过selector来协调 ...
- Java NIO学习系列七:Path、Files、AsynchronousFileChannel
相对于标准Java IO中通过File来指向文件和目录,Java NIO中提供了更丰富的类来支持对文件和目录的操作,不仅仅支持更多操作,还支持诸如异步读写等特性,本文我们就来学习一些Java NIO提 ...
- Java NIO学习系列二:Channel
上文总结了Java NIO中的Buffer相关知识点,本文中我们来总结一下它的好兄弟:Channel.上文有说到,Java NIO中的Buffer一般和Channel配对使用,NIO中的所有IO都起始 ...
- Java NIO学习系列六:Java中的IO模型
前文中我们总结了linux系统中的5中IO模型,并且着重介绍了其中的4种IO模型: 阻塞I/O(blocking IO) 非阻塞I/O(nonblocking IO) I/O多路复用(IO multi ...
- Java NIO学习系列五:I/O模型
前面总结了很多IO.NIO相关的基础知识点,还总结了IO和NIO之间的区别及各自适用场景,本文会从另一个视角来学习一下IO,即IO模型.什么是IO模型?对于不同人.在不同场景下给出的答案是不同的,所以 ...
- Java NIO 学习笔记(一)----概述,Channel/Buffer
目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...
- Java命令学习系列(二)——Jstack
Java命令学习系列(二)——Jstack 2015-04-18 分类:Java 阅读(512) 评论(0) jstack是java虚拟机自带的一种堆栈跟踪工具. 功能 jstack用于生成java虚 ...
- Java NIO学习与记录(八): Reactor两种多线程模型的实现
Reactor两种多线程模型的实现 注:本篇文章例子基于上一篇进行:Java NIO学习与记录(七): Reactor单线程模型的实现 紧接着上篇Reactor单线程模型的例子来,假设Handler的 ...
随机推荐
- Code::Blocks 免安装版本下载及配置
在编程的时候选择一款好用的IDE非常重要,对于初学者或需要开发项目的程序员来说更为重要,众多的IDE中 Code::Blocks 是一个不错的选择.Code::Blocks开源.版本多,并且还有免安装 ...
- Mybatis-plus入门学习]
需要的数据库建表语句: #创建用户表 CREATE TABLE user ( id BIGINT(20) PRIMARY KEY NOT NULL COMMENT '主键', name VARCHAR ...
- c# 第9节 数据类型之值类型
本节内容: 1:数据类型是什么 1:数据类型是什么 2:数据类型--值类型 3:值类型和引用类型的区分 画图现象: 3:值类型的种类 整数: 浮点数: 字符:
- Linux下的SVN服务器搭建(八)
1. 通过yum命令安装svnserve yum -y install subversion #查看svn安装位置 rpm -ql subversion 2. 创建版本库目录(此仅为目录,为后面创建版 ...
- zz神经网络模型量化方法简介
神经网络模型量化方法简介 https://chenrudan.github.io/blog/2018/10/02/networkquantization.html 2018-10-02 本文主要梳理了 ...
- [RN] React Native ScrollView去掉自带的间隔
React Native ScrollView去掉自带的间隔 使用ScrollView时,自带了一个类似marginTop的效果,将其去掉 <ScrollView automaticallyAd ...
- Centos7下搭建NFS服务器与连接详解
一,环境介绍 本实验使用了两台centos7虚拟机,其中 服务器:192.168.1.188 客户端:192.168.1.189 二,实验步骤 192.168.1.1 ...
- A1033 To Fill or Not to Fill (25 分)
一.技术总结 是贪心算法的题目,题目主要考虑的问题有几个,是否会在第一个加油站的最近距离大于0,如果是这样那么直接输出答案,因为初始油箱没有汽油: 第二个是如何选定加油站,如果在可到达距离范围类,我们 ...
- [BJOI2019]奥术神杖(AC自动机,DP,分数规划)
题目大意: 给出一个长度 $n$ 的字符串 $T$,只由数字和点组成.你可以把每个点替换成一个任意的数字.再给出 $m$ 个数字串 $S_i$,第 $i$ 个权值为 $t_i$. 对于一个替换方案,这 ...
- Java高级工程师常见问题
MySQL数据库调优 定位慢查询得到生产环境那些sql语句响应慢,根据执行计划进行分析调优事物管理索引结构(B+树)平衡二叉树.B树.B+树分库分表后,如何解决查询 使用第三方数据库中间件( ...