Java NIO系列(二) - Buffer

前言

Java NIO中,缓冲区用来临时存储数据,可以理解为是I/O操作中数据暂存的中转站。缓冲区直接为通道(Channel)服务,数据是从通道读入缓冲区,从缓冲区写入到通道中的。

缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问这块内存

正文

Buffer的类型

Java NIO提供以下几种Buffer类型:

  • ByteBuffer
  • MappedByteBuffer
  • ShortBuffer
  • LongBuffer
  • FloatBuffer
  • CharBuffer
  • IntBuffer
  • DoubleBuffer

这些Buffer类型代表了Java中7种基本数据类型。换句话说,就是可以通过bytecharshortintlongfloatdouble类型来操作缓冲区中的数据。

Buffer的基本用法

使用Buffer读写数据一般遵循以下四个步骤:

  1. 写入数据到Buffer中;
  2. 调用Bufferflip()方法;
  3. Buffer中读取数据;
  4. 调用clear()方法或者compact()方法。

当向Buffer写入数据时,Buffer会记录下写了多少数据。一旦要读取数据,需要通过flip()方法将Buffer写模式切换到读模式。在读模式下,可以读取之前写入到Buffer的所有数据。

一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。两种方式能清空缓冲区:调用clear()compact()方法。

  • clear()方法:清空整个缓冲区,包括已读未读的数据。
  • compact()方法:只会清空已读的数据,未读的数据都被移到缓冲区起始处,新写入的数据将放到缓冲区未读数据后面

下面给出一个ByteBuffer的简单使用示例,其他缓冲区API的使用类似:

123456789101112131415161718192021222324
public static void testReadFromBuffer() {    try {        RandomAccessFile file = new RandomAccessFile("D://test.txt", "rw");        FileChannel fileChannel = file.getChannel();        //创建容量为10byte的buffer        ByteBuffer byteBuffer = ByteBuffer.allocate(10);        // 不断地写入缓冲区,写一次读一次        while (fileChannel.read(byteBuffer) != -1) {            // 设置buffer切换模式为读模式            byteBuffer.flip();            while (byteBuffer.hasRemaining()) {                // 每次读取1byte,也就是一个字节                System.out.print((char) byteBuffer.get());            }            // 清空整个缓存区,准备下次写入            byteBuffer.clear();        }        fileChannel.close();    } catch (FileNotFoundException e) {        e.printStackTrace();    } catch (IOException e) {        e.printStackTrace();    }}

Buffer的重要属性

为了理解Buffer的工作原理,需要熟悉它的4个核心属性:

属性 含义 具体描述
capacity 容量 缓冲区可以容纳的最大数据量,在缓冲区创建时被设定并且不能改变
limit 上界 缓冲区中当前已使用的数据量
position 位置 缓冲区下一个要被读或写的元素的索引
mark 标记 调用mark()来设置mark=position,再调用reset()可以让position恢复到标记的位置即position=mark

其中,positionlimit的含义取决于Buffer处在读模式还是写模式。不管Buffer处在什么模式,capacity的含义总是一样的。

capacity

作为一个内存块Buffer有一个固定的大小值,也叫capacity。你最多只能写入capacity个的bytecharintlong等类型数据。一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续往里写数据。

position

  • 写入数据时

当你写数据到Buffer中时,position表示下一个可写入的数据的位置。position初始位置0,当一个bytecharintlong等数据写到Buffer后,position会向前移动到下一个可插入数据的Buffer单元。position最大可为capacity – 1

  • 读取数据时

当从Buffer读取数据时,position表示下一个可读取的数据的位置。当将Buffer写模式切换到读模式position会被重置为0。当从Bufferposition读取数据时,position向前移动到下一个可读的位置。

limit

  • 写入数据时

写模式下,Bufferlimit表示你最多能往Buffer里写多少数据。写模式下,limit等于Buffercapacity,也就是内存块的最大容量。

  • 写入数据时

当切换Buffer到读模式时,limit会被设置成写模式下的position值,limit表示你最多能读到多少数据。limit被设置成已写数据的数量,这个值在写模式下就是position

Buffer的方法

Buffer的分配

要想获得一个Buffer对象首先要进行分配,每一个Buffer类都有一个allocate()方法。下面是一个分配10字节capacityByteBuffer的例子:

1
ByteBuffer buf = ByteBuffer.allocate(10);

这是分配一个可存储1024个字符的CharBuffer

1
CharBuffer buf = CharBuffer.allocate(1024);

向Buffer中写入数据

写数据到Buffer有两种方式:

  • Channel将数据写入Buffer
1
int bytesRead = channel.read(buffer);
  • 通过Bufferput()方法写到Buffer中。
1
buffer.put(1);

put()ByteBuffer中为抽象方法,在ByteBuffer有很多的重载,由其子类HeapByteBufferDirectByteBuffer实现。

写模式切换为读模式

flip()方法将Buffer写模式切换到读模式。调用flip()方法会将position设回0,并将limit设置成之前position的值。

1
buffer.flip();

查看flip()方法的源码确认:

123456
public final Buffer flip() {    limit = position;    position = 0;    mark = -1;    return this;}

从Buffer从读取数据

Buffer中读取数据也有两种方式:

  • Buffer读取数据到Channel中。
1
int bytesWritten = channel.write(buf);
  • 通过Bufferget()方法从Buffer中读取数据。
1
byte b = buffer.get();

get()方法和put()一样有很多的重载,允许以不同的方式从Buffer中读取数据。例如:从指定position读取,或者从Buffer中读取数据到字节数组。

clear()和compact()方法

一旦读完Buffer中的数据,需要让Buffer准备好再次被写入。前面也说了,可以通过clear()compact()方法来完成。

clear()方法

如果调用的是clear()方法,position将被设回0limit被设置成capacity的值。

1
buffer.clear();

查看clear()方法的源码确认:

123456
public final Buffer clear() {    position = 0;    limit = capacity;    mark = -1;    return this;}

换句话说,Buffer被清空了。Buffer中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。

compact()方法

如果调用的是compact()方法,所有的未读数据都将被拷贝到Buffer的起始位置,position会设置为最后一个未读元素的后面。limit()方法和clear()方法一样,会被设置为capacity的大小。

查看compact()方法的实现,此方法在ByteBuffer中为抽象方法,查看其子类HeapByteBuffer的实现:

1234567891011
public ByteBuffer compact() {    // 将未读的数据往前移动    System.arraycopy(hb, ix(position()), hb, ix(0), remaining());    // 设置postion为最后一个未读数据后面的位置    position(remaining());    // 设置limit为最大的容量    limit(capacity());    // 清除标记位    discardMark();    return this;}

现在Buffer准备好写数据了,但是不会覆盖未读的数据。

mark()与reset()方法

通过调用mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用reset()方法恢复到这个position。例如:

mark()方法

1
buffer.mark();

查看mark()方法的源码,mark变量被设置为position的值:

1234
public final Buffer mark() {    mark = position;    return this;}

reset()方法

1
buffer.reset();

查看mark()方法的源码,position变量被设置为之前的mark的值:

1234567
public final Buffer reset() {    int m = mark;    if (m < 0)        throw new InvalidMarkException();    position = m;    return this;}

equals()与compareTo()方法

可以使用equals()compareTo()方法比较两个Buffer

equals()方法

当同时满足下列条件时,表示两个Buffer相等:

  1. 有相同的类型(bytecharintlong类型等)。
  2. Buffer中剩余的bytechar等元素的个数相等。
  3. Buffer中所有剩余的bytechar等都相同。

equals()方法比较的实际是Buffer中的剩余元素是否相等。它只是比较Buffer的一部分,不是每一个在它里面的元素都比较。

compareTo()方法

compareTo()方法比较两个Buffer的剩余元素(bytechar等)。
当满足下列条件时,则认为一个Buffer小于另一个Buffer

  1. 第一个不相等的元素小于另一个Buffer中对应的元素。
  2. 所有元素都相等,但第一个Buffer比另一个先耗尽(第一个Buffer的元素个数比另一个少)。

总结

这里只是对Buffer进行了入门的介绍,具体深入学习还需要查看各种缓冲区以及相关的具体实现。



欢迎关注技术公众号: 零壹技术栈

零壹技术栈

本帐号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。

原文地址:https://www.cnblogs.com/ostenant/p/9695177.html

时间: 2024-07-29 12:13:07

Java NIO系列(二) - Buffer的相关文章

Java NIO系列教程(三) Buffer

原文链接:http://ifeve.com/buffers/ 声明:Java NIO系列教材并非本人原创,只因阅读原文之后有感于文章之精妙,意欲与诸位共享,故而出此下策,忘原作者见谅.另附上原文地址. Java NIO的通道类似流,但又有些不同: Java NIO中的Buffer用于和NIO通道进行交互.如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的. 缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存.这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问

Java NIO系列教程(二) Channel

原文地址:http://ifeve.com/channels/ 声明:Java NIO系列教材并非本人原创,只因阅读原文之后有感于文章之精妙,意欲与诸位共享,故而出此下策,忘原作者见谅.另附上原文地址. Java NIO的通道类似流,但又有些不同: 既可以从通道中读取数据,又可以写数据到通道.但流的读写通常是单向的. 通道可以异步地读写. 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入. 正如上面所说,从通道读取数据到缓冲区,从缓冲区写入数据到通道.如下图所示: C

Java NIO (二) 缓冲区(Buffer)

缓冲区(Buffer):一个用于特定基本数据类型的容器,由 java.nio 包定义的,所有缓冲区都是 Buffer 抽象类的子类. Java NIO 中的Buffer 主要用于和NIO中的通道(Channel)进行交互, 数据从通道(Channel)读入缓冲区(Buffer)或者从缓冲区(Buffer)写入通道(Channel).如下,我画的一个简图,Chanenl直接和数据源或者目的位置接触,Buffer作为中介这,从一个Channel中读取数据,然后将数据写入另一个Channel中. Bu

Java NIO系列教程(三) Buffer(转)

Java NIO中的Buffer用于和NIO通道进行交互.如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的. 缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存.这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存. 下面是NIO Buffer相关的话题列表: Buffer的基本用法 Buffer的capacity,position和limit Buffer的类型 Buffer的分配 向Buffer中写数据 flip()方法 从Buffer中读取数据

java NIO系列教程1

ava NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式. Java NIO: Channels and Buffers(通道和缓冲区) 标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中. Java NIO: Asynchronous IO(异步IO) Java NIO可以让你

Java NIO系列3:缓冲区

前言 一直想把NIO系列的文章更新下去,只不过发现在入职支付宝后工作实在是忙,所以一直拖到现在.直接从一个学生过狗成为一名加班狗,好吧,这就是互联网公司的现状吧,但是每天都是充实的,而且发现其他的员工也基本非常乐意加班,难道这就是阿里的文化熏陶?! 废话不多说,还是进入今天的正题,在前面的文章中,我们已经对Java的NIO有了一个粗浅的认识--主要之为了支持非阻塞I/O的操作,之前的BIO则是阻塞的.因为异步性的引入,大大提高了Java在处理异步任务的效率.下面认识下NIO中的缓冲区: 认识缓冲

Java NIO 系列教程(转)

原文中说了最重要的3个概念,Channel 通道Buffer 缓冲区Selector 选择器其中Channel对应以前的流,Buffer不是什么新东西,Selector是因为nio可以使用异步的非堵塞模式才加入的东西.以前的流总是堵塞的,一个线程只要对它进行操作,其它操作就会被堵塞,也就相当于水管没有阀门,你伸手接水的时候,不管水到了没有,你就都只能耗在接水(流)上.nio的Channel的加入,相当于增加了水龙头(有阀门),虽然一个时刻也只能接一个水管的水,但依赖轮换策略,在水量不大的时候,各

Java NIO 系列教程

转载于http://www.iteye.com/magazines/132-Java-NIO Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API.本系列教程将有助于你学习和理解Java NIO.感谢并发编程网的翻译和投递. (关注ITeye官微,随时随地查看最新开发资讯.技术文章.) Java NIO提供了与标准IO不同的IO工作方式: Channels and Buffers(通道和缓冲区):标准的IO基于字节流和字符流

java NIO系列教程2

7.FileChannel Java NIO中的FileChannel是一个连接到文件的通道.可以通过文件通道读写文件. FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下. 打开FileChannel 在使用FileChannel之前,必须先打开它.但是,我们无法直接打开一个FileChannel,需要通过使用一个InputStream.OutputStream或RandomAccessFile来获取一个FileChannel实例.下面是通过RandomAccessFile打开