详尽分享缓冲区(Buffer)

简介: 详尽分享缓冲区(Buffer)

什么是缓冲区?

定义:

缓冲区就是固定数量数据的容器,其作用是一个存储器,或者分段运输区,在这里数据可被存储并在之后用于检索。缓冲区像上一篇文章I/O 模型那样被写满和释放,对于每个非布尔原始数据类型都有一个缓冲区类,尽管缓冲区作用于它们存储的原始数据类型//代码效果参考:http://www.zidongmutanji.com/zsjx/462269.html

,但缓冲区十分倾向于处理字节,非字节缓冲区可以在后台从字节或者到字节的转换,这取决于缓冲区如何创建的。所有的缓冲区都是Buffer抽象类的子类。

缓冲区与通道的关系

缓冲区的工作与通道紧密联系。通道是I/O传输发生时的入口,而缓冲区是这些数据传输的来源或者目标。也就是说数据总是从缓冲区写入到通道中,或者从通道读取数据到缓冲区。

继承结构(家谱)

缓冲区的四个属性

容量(capacity)

指缓冲区能够容纳的数据元素的最大数量,这一容量在缓冲区创建时被设定,且不能够被改变。

上界(limit)

指缓冲区的第一个不能被读写的元素数组下标索引,也可以认为缓冲区中实际元素的数量。

位置(position)

指下一个要被读或写的元素索引,该值会随着get()或put()调用自动更新。

标记(mark)

指一个备忘位置,调用mark()来设定mark = position,调用reset()来设定position = mark,标记未设定//代码效果参考:http://www.zidongmutanji.com/bxxx/466034.html

前是未定义的(也就是说调用mark()方法的话,mark值将存储当前position的值,等下次调用reset()方法时,会设定position的值为之前的标记值)。

这四个属性总是遵循以下的关系:0 <= mark <= position <= limit <= capacity。

缓冲区的比较

  通过查看ByteBuffer的equals源码就能了解到是如何比较的:

1 public boolean equals(Object ob) {

2 if (this == ob)

3 return true;

4 if (!(ob instanceof ByteBuffer))

5 return false;

6 ByteBuffer that = (ByteBuffer)ob;

7 if (this.remaining() != that.remaining())

8 return false;

9 int p = this.position();

10 for (int i = this.limit() - 1, j = that.limit() - 1; i >= p; i--, j--)

11 if (!equals(this.get(i), that.get(j)))

12 return false;

13 return true;

14 }

1.两个对象类型相同。包含不同数据类型的buffer永远不会相等,而且buffer对象绝不会等于非buffer对象。

2.两个对象都剩余同样数量的元素。buffer的容量不需要相同,而且缓冲区中剩余数据的索引也不必相同。但每个缓冲区中剩余元素的数目(从位置到上界)必须相同。

3.在每个缓冲区中应被get()方法返回的剩余数据序列必须一致。

批量移动数据

缓冲区设计的目的就是为了高效的传输数据。一次移动一个数据元素,这种方式并不高效,Buffer API提供了向缓冲区内外批量移动数据元素的函数:

public abstract class CharBuffer

extends Buffer

implements Comparable, Appendable, CharSequence, Readable

{

...

public CharBuffer get(char【】 dst){...}

public CharBuffer get(char【】 dst, int offset, int length){...}

public final CharBuffer put(char【】 src){...}

public CharBuffer put(char【】 src, int offset, int length){...}

public CharBuffer put(CharBuffer src){...}

public final CharBuffer put(String src){...}

public CharBuffer put(String src, int start, int end){...}

...

}

其实这种批量移动的合成效果和上一篇文章的循环底层实现上是一样的,但是这些方法可能高效很多,因为这种缓冲区实现能够利用本地代码或其他的优化来移动数据。

非直接缓冲区

非直接缓冲区通过allocate(int capacity)创建:

static ByteBuffer allocate(int capacity)

我们之前说过NIO是利用通道来连接硬件磁盘或者应用程序的,缓冲区充当中间件来存储数据并通过缓冲区进行双向的数据传输。(借图,侵删)

通过allocate(int capacity)创建的缓冲区是在JVM内存中创建的,由于磁盘的存取是由操作系统进行管理的,所以对于磁盘来说存取的数据只能来源于内核空间,而JVM内存缓冲区是在用户空间的,所以造成了数据会在操作系统和JVM之间来回拷贝,所以对NIO性能也有影响;同时用户空间的缓冲区在JVM内,销毁来说还很容易,但也占用了JVM内存的开销,所以对JVM的性能也有一定的影响。

非直接缓冲区的写入步骤:

  1.创建一个临时的直接的ByteBuffer缓冲区对象;

  2.将非直接缓冲区的数据复制到创建的临时缓冲区中;

  3.在临时缓冲区中执行底层的I/O操作;

  4.操作完成后,临时缓冲区的数据成为无用的等待被回收的数据。

直接缓冲区

直接缓冲区通过allocateDirect(int capacity)创建:

static ByteBuffer allocateDirect(int capacity)

直接缓冲区不再通过内核空间和用户空间复制来传递数据,而是在物理内存中申请了一块空间,这块空间能映射到内核空间和用户空间,所以应用程序和磁盘之间的数据存取可以通过这块直接申请的物理空间进行。(借图,侵删)

由于创建的缓冲区是在物理内存中,会少一次的复制过程,这使得JVM可以进行很高效的I/O操作,但由于使用的内存是由操作系统分配的,绕过了JVM的内存,所以直接缓冲区的建立和销毁需要比在JVM上建立和销毁缓冲区需要更大的开销。

直接与非直接缓冲区的要点

  ? 字节缓冲区要么是直接的,要么是非直接的。如果为直接字节缓冲区,则Java虚拟机会尽最大的努力直接在此缓冲区上执行本机I/O操作,也就是说,在每次调用操作系统基础的一个本机I/O操作前后,虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容到缓冲区)。

  ? 直接字节缓冲区可以通过调用此类的allocateDirect(int capacity)工厂方法来创建,此方法返回的缓冲区进行分配和取消分配所需要的成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响并不明显。所以,建议将直接缓冲区主要分配给那些易受基础系统的本机I/O操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处的时候分配它们。

  ? 直接字节缓冲区还可以通过FileChannel的map()方法将文件区域直接映射到内存中来创建。该方法返回MappedByteBuffer。Java平台的实现有助于通过JNI从本机代码创建直接字节缓冲区。如果以上这些缓冲区中的某个缓冲区实例指的是不可访问的内存区域,则试图访问该区域不会更改该缓冲区的内容,并且会在访问期间或者稍后的某个时间抛出不确定的异常。

  ? 字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用isDirect()方法来确定。提供此方法是为了能够在性能关键型代码中执行显式缓冲区管理。

直接缓冲区的缺点:

  1.不安全

  2.消耗更多,因为它不是在JVM中直接开辟空间,这部分的内存回收就只能依赖于垃圾回收机制,垃圾什么时候回收不受我们控制。

  3.数据写入物理内存缓冲区中,程序就丧失了对这些数据的管理,即什么时候这些数据最终写入磁盘只能由操作系统来决定,应用程序无法干涉。

缓冲区的选择:

  直接缓冲区在数据需要长时间存于内存,或者大数据量的操作时更加合适。

参考:

作者:Joe

出处:

努力了的才叫梦想,不努力的就是空想,努力并且坚持下去,毕竟这是我相信的力量

相关文章
|
Python 容器
在Python中,键值对(key-value pair)结构
在Python中,键值对(key-value pair)结构
502 1
|
5月前
|
机器学习/深度学习 人工智能 算法
智创 AI 新视界 -- 提升 AI 推理速度的高级方法(16 - 2)
本文深度聚焦提升 AI 推理速度,全面阐述模型压缩(低秩分解、参数量化)、硬件加速(GPU、TPU)及推理算法优化(剪枝感知推理、动态批处理)。结合图像识别等多领域案例与丰富代码示例,以生动形象且专业严谨的方式,为 AI 从业者提供极具价值的技术指南,助力突破 AI 推理速度瓶颈,实现系统性能跃升。
|
Rust
【一起学Rust · 项目实战】命令行IO项目minigrep——接收命令行参数与读取文件内容
【一起学Rust · 项目实战】命令行IO项目minigrep——接收命令行参数与读取文件内容
200 0
【一起学Rust · 项目实战】命令行IO项目minigrep——接收命令行参数与读取文件内容
|
4月前
|
搜索推荐 Linux iOS开发
qBittorrent:专业级磁力种子下载工具,高速稳定 + 全功能资源管理
qBittorrent 是一款免费、开源且无广告的 P2P BitTorrent 客户端,支持 Windows、Mac 和 Linux 系统。它功能强大,包含 DHT、Peer Exchange、加密等技术,支持下载优先级设置、RSS 订阅和远程控制。用户可通过 Torrent 文件或磁力链接下载资源,并能优化连接设置以提升速度。常见问题如“元数据下载”或 DHT 连接不佳时,建议使用热门种子或调整 trackers。
790 0
|
9月前
|
机器学习/深度学习 人工智能 算法
AI在体育分析与预测中的深度应用:变革体育界的智能力量
AI在体育分析与预测中的深度应用:变革体育界的智能力量
786 31
|
XML 移动开发 JavaScript
js中BOM和DOM总结(基础篇)
文章总结了JavaScript的BOM和DOM知识点,包括window、screen、location、history、navigator对象,以及消息框、计时器和cookie。同时,介绍了DOM的概念、节点获取和修改方法,以及事件处理。
js中BOM和DOM总结(基础篇)
|
11月前
|
机器学习/深度学习 人工智能 算法
【AI系统】关键设计指标
本文介绍了AI芯片设计中的关键指标与设计点,涵盖OPS、MACs、FLOPs等计算单位,以及精度、吞吐量、时延、能耗、成本和易用性等六大关键指标。文章还探讨了MACs和PE优化策略,以及通过算术强度和Roofline模型评估AI模型在特定芯片上的性能表现,为AI芯片的性能优化提供了理论依据和实践指导。
693 1
|
11月前
|
数据处理
《原子操作:程序世界里的“最小魔法单位”解析》
在计算机编程中,原子操作是解决并发和多线程问题的关键。它指在执行过程中不会被其他操作中断的操作,确保数据处理的完整性和一致性。本文深入探讨了原子操作的概念、重要性、与普通操作的区别、应用场景及局限性,帮助读者更好地理解和应用这一核心技术。
301 3
|
11月前
|
机器学习/深度学习 存储 大数据
在大数据时代,高维数据处理成为难题,主成分分析(PCA)作为一种有效的数据降维技术,通过线性变换将数据投影到新的坐标系
在大数据时代,高维数据处理成为难题,主成分分析(PCA)作为一种有效的数据降维技术,通过线性变换将数据投影到新的坐标系,保留最大方差信息,实现数据压缩、去噪及可视化。本文详解PCA原理、步骤及其Python实现,探讨其在图像压缩、特征提取等领域的应用,并指出使用时的注意事项,旨在帮助读者掌握这一强大工具。
585 4