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的 ...
随机推荐
- ubuntu 16.04下node和pm2安装
一.安装node,这里安装9.0的版本,安装其它版本直接到https://deb.nodesource.com/setup_9.x找相应版本的更改既可 1.sudo apt-get remove no ...
- IntelliJ IDEA设置主题和背景图片(背景色)
设置主题以及背景图片 设置代码背景颜色
- 语义分析的waf 目前就看到长亭 机器学习的waf有fortnet 阿里云的waf也算
近期,在全球权威咨询机构 Gartner 发布的 2019 Web 应用防火墙魔力象限中,阿里云 Web 应用防火墙成功入围,是亚太地区唯一一家进入该魔力象限的厂商! Web 应用防火墙,简称 WAF ...
- 构建根文件系统之busybox
配置busybox 首先将busybox的压缩包放入服务器进行解压缩: busybox集合了几百个命令,在一般的系统中并不需要全部使用.可以通过配置busybox来选择这些命令.定制某些命令的功能(选 ...
- nginx 的 一些配置说明
default 配置参考https://www.cnblogs.com/kuku0223/p/10740735.html 设置了default 除了指定的域名, 如果是没有配置的域名解析过来 ...
- 201871010102-常龙龙《面向对象程序设计(java)》第十七周学习总结
项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.com/nwnu-daizh/p ...
- flutter环境配置window10
第一步,配置git环境,这个作为前端的都是会的,如果你不会,去问度娘去 第二步,配置java的开发环境,这里建议下载jdk为1.8版本的,我最初使用的是如下图的jdk版本,后面和flutter版本不一 ...
- 使用jattach 在host 节点查看容器jvm信息
jattach是基于hostspot attach api 指南编写的轻量all in one(jmap,jstack,jcmd,jinfo) 的工具 包含了以下命令 load 家在agent lib ...
- xpath获取标签对本身含内容, 获取html内容
通常使用xpath我们直接定位到标签后, 使用/text() 或 //text()来获取标签对之间的文本值, 但特殊情况下我们也需要获取标签本身含文本值, 操作如下: 文件为html, 标签对结构如下 ...
- eclipse中查找类、方法及变量被引用的地方
1.选中要查看的类.方法或变量,然后Ctrl+Shift+G或右键-->References--->Project,就可以找到它所有被引用的地方. 2.对于方法,还可以通过右键--> ...