Java ByteBuffer –速成课程

以我的经验,当开发人员第一次遇到java.nio.ByteBuffer时,会引起混乱和细微的错误,因为如何正确使用它尚不明显。 在我对API文档感到满意之前,需要反复阅读API文档和一些经验以实现一些微妙之处。 这篇文章是关于如何正确使用它们的短暂崩溃,希望可以为其他人节省一些麻烦。

由于所有这些都是基于推断(而不是基于明确的文档),并且是基于经验,因此我不能断言这些信息必定是权威的。 我欢迎您提出反馈意见,以指出错误或其他观点。 我也欢迎提出其他陷阱/最佳做法的建议。

我确实假定读者将阅读与本文相关的API文档。 我不会穷尽所有您可以使用ByteBuffer进行的操作。

ByteBuffer抽象

可以将ByteBuffer看作提供了一些(未定义的)底层字节存储的视图 。 字节缓冲区的两种最常见的具体类型是由字节数组支持的字节缓冲区和由直接(脱离堆,本机)字节缓冲区支持的字节缓冲区。 在两种情况下,都可以使用相同的接口读取和写入缓冲区的内容。

ByteBuffer的API的某些部分特定于某些类型的字节缓冲区。 例如,字节缓冲区可以是只读的 ,将用法限制为方法的子集。 array()方法仅适用于由字节数组支持的字节缓冲区(可以使用hasArray()进行测试),并且通常仅在完全知道自己在做什么的情况下使用 。 一个常见的错误是使用array()将ByteBuffer“转换”为字节数组。 这不仅仅适用于字节数组支持的缓冲区,而且很容易成为错误的来源,因为根据缓冲区的创建方式,返回数组的开头可能与字节缓冲区的开头相对应, 也可能不对应。 结果往往是一个细微的错误,其中代码的行为根据字节缓冲区和创建它的代码的实现细节而有所不同。

ByteBuffer可以通过调用repeat()复制自身。 这实际上并不复制基础字节 ,而只是创建一个指向相同基础存储的新ByteBuffer实例。 可以使用slice()创建表示另一个ByteBuffer的子集的ByteBuffer。

与字节数组的主要区别

  • ByteBuffer具有关于hashCode() / equals()的值语义,因此可以更方便地在容器中使用。
  • ByteBuffer通过实例化新的ByteBuffer,提供了将字节缓冲区的子集作为值传递而不复制字节的功能。
  • NIO API大量使用了ByteBuffer:s。
  • ByteBuffer中的字节可能驻留在Java堆之外。
  • ByteBuffer的状态超出了字节本身,这有利于进行相对的I / O操作(但有一些警告,请参见下文)。
  • ByteBuffer提供了用于读取和写入各种原始类型(如整数和long)的方法(并且可以按不同的字节顺序进行操作)。

ByteBuffer的关键属性

ByteBuffer的以下三个属性至关重要(我在每个属性上引用了API文档):

  • 缓冲区的容量是它包含的元素数量。 缓冲区的容量永远不会为负,也不会改变。
  • 缓冲区的限制是不应读取或写入的第一个元素的索引。 缓冲区的限制永远不会为负,也永远不会大于缓冲区的容量。
  • 缓冲区的位置是下一个要读取或写入的元素的索引。 缓冲区的位置永远不会为负,也不会大于其限制。

这是一个示例ByteBuffer的可视化示例,在示例中,ByteBuffer由字节数组支持,并且ByteBuffer的值是单词“ test”(单击以放大):

ByteByffer_example

该ByteBuffer等于(在equals()的意义上) 等于其在[ positionlimit )之间内容相同的任何其他ByteBuffer。

假设上面显示的字节缓冲区是bb ,我们这样做:

final ByteBuffer other = bb.duplicate();
other.position(bb.position() + 4);

现在,我们将有两个ByteBuffer实例都引用相同的基础字节数组,但是它们的内容将有所不同( 其他将为空):

ByteBuffer_after_duplicate_example

字节缓冲区的缓冲区/流对偶

有两种访问字节缓冲区内容的方法- 绝对访问和相对访问。 例如,假设我有一个ByteBuffer,我知道它包含两个整数。 为了使用绝对定位提取整数,可以这样做:

int first = bb.getInt(0)
int second = bb.getInt(4)

或者,可以使用相对定位提取它们:

int first = bb.getInt();
int second = bb.getInt();

第二种选择通常很方便,但是以对缓冲区产生副作用 (即更改它)为代价。 不是内容本身,而是ByteBuffers视图可以查看该内容。

这样,如果将ByteBuffer用作流,则其行为类似于流。

最佳做法和陷阱

flip()缓冲区

如果要通过重复写入来构建ByteBuffer,然后想将其赠送,则必须记住将它翻转() 。 例如,这是一种将字节数组复制到ByteBuffer的方法,并假设使用默认编码(请注意,此处使用的ByteBuffer.wrap()创建一个包装指定字节数组的ByteBuffer,而不是复制其中的内容放入新的ByteBuffer中):

public static ByteBuffer fromByteArray(byte[] bytes) {final ByteBuffer ret = ByteBuffer.wrap(new byte[bytes.length]);ret.put(bytes);ret.flip();return ret;
}

如果我们不翻转它,则返回的ByteBuffer 将为空,因为该位置等于limit

不要消耗缓冲区

除非特别打算这样做,否则在读取字节缓冲区时请注意不要“消耗”它。 例如,考虑采用默认编码方式,将ByteBuffer转换为String的此方法:

public static String toString(ByteBuffer bb) {final byte[] bytes = new byte[bb.remaining()];bb.duplicate().get(bytes);return new String(bytes);
}

不幸的是,没有提供进行字节数组的绝对定位读取的方法(但确实存在用于基元的绝对定位读取)。

注意在读取字节时使用了plicate() 。 如果我们不这样做,该函数将对输入ByteBuffer产生副作用 。 这样做的代价是仅为了一次调用get()的目的就额外分配了一个新的ByteBuffer。 您可以在get()之前记录ByteBuffer的位置,然后再将其还原,但这存在线程安全性问题(请参阅下一节)。

值得注意的是,这仅在您尝试将ByteBuffer:s视为值时适用。 如果您正在编写旨在对ByteBuffer产生副作用的代码,将它们更像流一样对待,那么您当然打算这样做,并且本节不适用。

不要改变缓冲区

在不是特定于特定用例的通用代码的情况下,(在我看来)对于执行(抽象)只读操作(例如读取字节缓冲区)的方法来说是一种好习惯。 ,不更改其输入。 这是比“不要消耗ByteByffer”更强的要求。 以上一节中的示例为例,但尝试避免额外分配ByteBuffer:

public static String toString(ByteBuffer bb) {final byte[] bytes = new byte[bb.remaining()];bb.mark();      // NOT RECOMMENDED, don't do thisbb.get(bytes);bb.reset();     // NOT RECOMMENDED, don't do thisreturn new String(bytes);
}

在这种情况下,我们在调用get()之前记录ByteBuffer的状态,然后再进行恢复(请参阅API文档中的mark()reset() )。 这种方法有两个问题。 第一个问题是上面的函数没有组成 。 一个ByteBuffer仅具有一个“标记”,并且您的(非常通用,不具有上下文意识) toString()方法不能安全地假定调用者并未出于自身目的尝试使用mark()和reset() 。 例如,假设以下调用者正在反序列化一个长度为前缀的字符串:

bb.mark();
int length = bb.getInt();
... sanity check length
final String str = ByteBufferUtils.toString(bb);
... do something
bb.reset(); // OOPS - reset() will now point 4 bytes off, because toString() modified the mark

(顺便说一句,这是一个非常人为且奇怪的示例,因为我发现很难提出一个使用mark() / reset()的实际代码示例,该代码通常在处理流中的缓冲区时使用,像派系一样,也感觉需要在所述缓冲区的其余部分上调用toString() 。我很想听听人们在这里提出了什么解决方案。例如,可以想象一个清晰的代码库中的策略在类似于toString()的面向值的上下文中允许mark() / reset() –但是即使您这样做了(它可能会无意中违反了它的味道),您仍然会遭受后面提到的突变问题。)

让我们看一下避免这种问题的toString()的替代版本:

public static String toString(ByteBuffer bb) {final byte[] bytes = new byte[bb.remaining()];bb.get(bytes);bb.position(bb.position() - bytes.length);     // NOT RECOMMENDED, don't do thisreturn new String(bytes);
}

在这种情况下,我们不修改标记,因此我们进行撰写。 但是,我们仍然致力于改变输入的“罪行”。 在多线程情况下,这是一个问题。 除非抽象隐含了该内容(例如,使用流或以类似流的方式使用ByteBuffer时),否则您不希望阅读暗示其变化的内容。 如果要传递的ByteBuffer视为一个值,将其放入容器中,共享它们,等等–除非保证两个线程永远不会同时使用同一个ByteBuffer,否则对它们进行突变将引入细微的错误。 通常,此类错误的结果是奇怪的值损坏或意外的BufferOverFlowException:s。

不受此影响的版本出现在上面的“不要使用缓冲区”部分,该部分使用duplicate()构造一个临时的ByteBuffer实例,可以在其上安全调用get()

compareTo()受字节签名的约束

Java中的字节是有符号的 ,这与通常期望的相反。 但是,容易错过的是,这也会影响ByteBuffer.compareTo() 。 该方法的Java API文档显示为:

“通过按字典顺序比较剩余字节的序列来比较两个字节缓冲区,而不考虑每个序列在其相应缓冲区中的开始位置。”

快速阅读可能会使人相信结果通常是您期望的,但是当然,鉴于Java中字节的定义,情况并非如此。 结果是,包含最高位设置的值的字节缓冲区的顺序将与您期望的有所不同。

Google出色的Guava库具有UnsignedBytes帮助器 ,可减轻您的痛苦。

array()通常是使用错误的方法

通常,不要随便使用array() 。 为了正确使用它,您要么必须知道字节缓冲区是数组支持的事实 ,要么必须使用 hasArray() 对其进行测试,并且在两种情况下都有两个单独的代码路径。 此外,在使用它时, 必须使用arrayOffset()以确定ByteBuffer的第零个位置与字节数组相对应。

在典型的应用程序代码中,除非您真的知道自己在做什么并且特别需要它,否则您将不会使用array() 。 也就是说,在某些情况下它很有用。 例如,假设您实现的是UnsignedBytes.compare()的ByteBuffer版本(同样来自Guava )–您可能希望优化其中一个或两个参数都支持数组的情况,以避免不必要的复制和频繁调用。缓冲区。 对于这种通用且可能大量使用的方法,这种优化是有意义的。

参考: Java ByteBuffer –我们的JCG合作伙伴 Peter Schuller在(mod:world:scode)博客上的速成班 。

翻译自: https://www.javacodegeeks.com/2012/12/the-java-bytebuffer-a-crash-course.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/370133.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实现编辑功能有哪几个action_Web 应用的撤销重做实现

背景前不久,我参与开发了团队中的一个 web 应用,其中的一个页面操作如下图所示:GIF这个制作间页面有着类似 PPT 的交互:从左侧的工具栏中选择元素放入中间的画布、在画布中可以删除、操作(拖动、缩放、旋转等&#xff…

windows下如何安装pip以及如何查看pip是否已经安装成功?

最近刚学习python,发现很多关于安装以及查看pip是否安装成的例子都比较老,不太适合于现在(python 3.6 )因此,下一个入门级别的教程。 0:首先如何安装python我就不做介绍了。 1:如果安装的是pyth…

检查用户显示器的分辨率

检查用户显示器的分辨率 转载于:https://www.cnblogs.com/Renyi-Fan/p/8088012.html

android 字体 dpi,详解Android开发中常用的 DPI / DP / SP

Android的碎片化已经被喷了好多年,随着国内手机厂商的崛起,碎片化也越来越严重,根据OpenSignal的最新调查,2014年市面上有18796种不同的Android设备,作为开发者,一个无法回避的难题就是需要适配各种各样奇奇…

android studio闪退代码不报错_代码不报错,不代表真的没错

今天是生信星球陪你的第695天大神一句话,菜鸟跑半年。我不是大神,但我可以缩短你走弯路的半年~就像歌儿唱的那样,如果你不知道该往哪儿走,就留在这学点生信好不好~这里有豆豆和花花的学习历程,从新手到进阶&#xff0c…

Centos7操作系统部署指南

一、硬件环境: Dell R620 二、软件环境: Centos6.4 X86_64 KVM Windows7vnc 三、安装说明 操作系统更新之迅速,让作为新手的系统运维人员有点措手不及,相对于老手就胸有成竹。怎么讲?由于老手对技术方向把握的非常好&…

Eclipse插件中的SLF4J登录

一直都在使用Maven和纯Java库进行开发,我从没想过在开发Eclipse插件时发出一些日志语句可能会成为问题。 但是,在Eclipse开发人员的想象中,一切似乎总是在Eclipse环境中,而Eclipse宇宙之外则什么都没有。 如果您使用Google搜索上…

Java EE 7社区调查结果!

在JSR 342下可以继续进行Java EE 7的工作。一切进展顺利,Java EE 7现在处于“初稿审查”阶段。 在11月初, Oracle发布了一个有关即将推出的Java EE 7功能的小型社区调查 。 昨天结果公布了。 超过1,100名开发人员参加了调查,并且几乎对每个问…

CSS(三)

CSS盒子模型 盒子模型解释 元素在页面中显示成一个方块,类似一个盒子,CSS盒子模型就是使用现实中盒子来做比喻,帮助我们设置元素对应的样式。盒子模型示意图如下: 把元素叫做盒子,设置对应的样式分别为:盒…

南昌互联网行业协会筹办者祝真和华罡团队-2014年12月江西IDC排行榜

他出自军营,拥有一身正气。 他在南昌创业,立意卓越。 从站点開始、到微营销、到线上教育,全面开花。 他在朋友圈看到不对的内容,就会即时批评。 他对朋友,又是很的和蔼可亲。 他就是南昌华罡网…

我应该使用32位还是64位JVM?

这是我在企业软件开发生涯中多次遇到的问题。 我不得不每隔一段时间就提供有关配置特定新环境的建议。 而且,很多时候,手头的问题与“我应该使用32位或64位JVM”有关。 老实说,一开始我只是掷硬币。 而不是给出合理的答案。 (对不…

android studio点击图片,如何在Android Studio中的模拟器图库中添加图像?

如何在Android Studio中的模拟器图库中添加图像?我正在开发图像过滤器应用程序。 但是,如果我没有任何图像,就无法真正尝试。我知道我可以在电话中对其进行测试,但这并不相同,因为我需要错误消息和其他内容。我只想从A…

android移动应用基础教程源代码,Android移动应用基础教程 【程序活动单元Activity】...

本章目录一、Activity的生命周期1、生命周期状态2 、生命周期方法3、横竖屏切换时的生命周期二、Activity的创建配置和关闭1、Activity的创建2、配置Activity3、开启和关闭Activity三、Intent与IntentFilter1、Intent介绍1.1 意图的概念1.2 显式意图1.3 隐式意图2、IntentFilte…

Python中使用subplot在一张画布上显示多张图

subplot(arg1, arg2, arg3) arg1: 在垂直方向同时画几张图arg2: 在水平方向同时画几张图arg3: 当前命令修改的是第几张图 t np.arange(0,5,0.1) y1 np.sin(2*np.pi*t) y2 np.sin(2*np.pi*t) plt.subplot(211) plt.plot(t,y1,b-.) plt.subplot(212) plt.plot(t,y2,r--) plt.s…

Java 8:从PermGen到元空间

您可能已经知道,现在可以下载JDK 8 Early Access 。 这使Java开发人员可以尝试Java 8的一些新语言和运行时功能。这些功能之一是完全删除自Oracle自JDK 7发行以来就宣布的Permanent Generation(PermGen)空间。例如,自JDK 7起&…

浏览器缓存问题原理以及解决方案

浏览器缓存问题: 简单来说,浏览器缓存就是把一个已经请求过的Web资源(如html页面,图片,js,数据等)拷贝一份副本储存在浏览器中。缓存会根据进来的请求保存输出内容的副本。当下一个请求来到的时…

Scikit-Learn机器学习入门

现在最常用的数据分析的编程语言为R和Python。每种语言都有自己的特点,Python因为Scikit-Learn库赢得了优势。Scikit-Learn有完整的文档,并实现很多机器学习算法,而每种算法使用的接口几乎相同,可以非常快的测试其它学习算法。 Pa…

Apache Camel入门

在先前的博文中,我们了解了企业集成模式(EIP)。 现在,在这篇文章中,我们将研究实现这些模式的Apache Camel框架。 关于骆驼: Apache Camel是一个开放源代码项目,已有将近5年的历史,…

android studio smssdk,SMSSDK for Android 配置

1.集成之前先要申请Mob的appkey与appsecret2.在Mob官网下载最新SDK,解压后会看到以下目录结构:SMSSDK下存放的是短信SDK的全部内容。3.在android studio中加入SMS的第三方库AS版本的SMSSDK目录下包含以下内容:MobCommons.jar:Mob …

JavaScript总结(3)

第3章 获取用户的输入 <script>10 intAprompt("请输入第一个数字","");11 intBprompt("请输入第二个数字",27);默认是2712 document.write("你输入的第一个数字是"intA);13 document.write("<…