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,一经查实,立即删除!

相关文章

c语言cth三角函数表示,三角函数与双曲函数基本公式对照表

圆函数(三角函数)1.基本性质:sin tan cos x x x ,cos cot sin xx x 1sec cos x x ,1csc sin x x tan cot 1x x sin csc 1x x sec cos 1x x 22sin cos 1x x 221tan sec x x ,221cot csc x x 2.奇偶性:sin()sin x x -- cos()cos x x - tan()tan x x --3.…

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

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

为什么我们要做三份 Webpack 配置文件

时至今日,Webpack 已经成为前端工程必备的基础工具之一,不仅被广泛用于前端工程发布前的打包,还在开发中担当本地前端资源服务器(assets server)、模块热更新(hot module replacement)、API Pro…

使用maven插件构建docker镜像

为什么要用插件 主要还是自动化的考虑,如果额外使用Dockerfile进行镜像生成,可能会需要自己手动指定jar/war位置,并且打包和生成镜像间不同步,带来很多琐碎的工作。 插件选择 使用比较多的是spotify的插件:https://github.com/spo…

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搜索上…

CSS(四)

css元素溢出 当子元素的尺寸超过父元素的尺寸时,需要设置父元素显示溢出的子元素的方式,设置的方法是通过overflow属性来设置。 overflow的设置项: 1、visible 默认值。内容不会被修剪,会呈现在元素框之外。2、hidden 内容会被修…

mysql排名

转载自思心思危http://www.cnblogs.com/zengguowang/p/5541431.html 一、sql1{不管数据相同与否,排名依次排序(1,2,3,4,5,6,7.....)} SELECTobj.user_id,   obj.score,  rownum : rownum 1 AS rownum FROM(SELECT…

python中变量名后的逗号_深入浅析python变量加逗号,的含义

逗号,用于生成一个长度为1的元组>>> (1)1>>> (1,)(1,)>>> 1,(1,)因此需要将长度为1的元组中元素提取出来可以用,简化赋值操作>>> a(1,)>>> ba>>> b(1,)>>> b,a>>> b1最后print打印变量加,实现连续打印…

广告的显示和关闭

app或游戏的主页显示广告页面,实现方式: public class MainActivity extends Activity implements View.OnClickListener{private Button btnShowAd;private RelativeLayout layoutAd;Overrideprotected void onCreate(Bundle savedInstanceState) {supe…

android签到功能模块,基于android的课堂签到系统.doc

基于android的课堂签到系统本科毕业论文(设计)题 目 基于Android的课堂签到系统学生姓名 XXX指导教师 XX学 院 信息科学与工程学院专业班级 计算机科学与技术0908班完成时间 2013年5月 摘 要在大学课堂中,签到问题一直困扰着老师和同学们。传统课堂签到的手段大多是…

Java EE 7社区调查结果!

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

CSS(三)

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

一道关于运行顺序题

function foo(){   getName function(){console.log(1)}   return this } foo.getName function(){console.log(2)} foo.prototype.getName function(){console.log(3)} var getName function(){console.log(4)} function getName(){console.log(5)} foo.getName()//2 …

android+小米文件管理器源码,小米开源文件管理器MiCodeFileExplorer-源码研究(2)-2个单实例工具类...

从本篇开始,讲解net.micode.fileexplorer.util工具包中的类。这个包下的类,功能也比较单一和独立。很多代码的思想和实现,可以用于JavaWeb和Android等多种环境中。一、单实例活动管理器ActivitiesManager一个单实例的活动管理器,从…

移动优先的响应式布局

前面的话 随着移动互联网的兴起,不同设备的分辨率相差较大,如果在不同的设置上显示同一个页面,则用户体验差。响应式网页设计是一种方法,使得一个网站能够兼容多个终端,而不用为每个终端制作特定的版本。它使得一个网站…