【原创】掌握对 ByteBuffer 的操控感

作者:星巴刻

? ? ? ? 作为 Java Nio 的一个基础部分,其提供的 java.nio.ByteBuffer 不易被正确使用简直让人无语,无人愿意为它辩白。ByteBuffer 本质只是 byte 数组的封装,但是与 byte 数组相比起来,要理解好,需要耗费点脑力。本文尝试用一种新的结构来解释 ByteBuffer,用以加速正确、轻松地掌握 ByteBuffer 的使用,希望通过本文的铺垫再去看 ByteBuffer 的注释、源代码以及应用代码,能掌握一种操控感。

一、工作区

? ? ? ? ByteBuffer 虽然是 byte 数组的封装,但是应用程序如何使用数组是受约束的,极少直接通过指定数组下标的方式使用 ByteBuffer。

? ? ? ? 在此引入「工作区」的概念,用来助力理解。

? ? ? ? 工作区是一个两边伸缩变动长度的区域,它的最左边是始点(用 position 表示),右边是它的终点(用 limit 表示)。现在请用你的右手掌挡住工作区的终点(limit 处),左手掌从工作区的左边 position 处往右压。这个过程中,右边将保持静止(淡定的静止),随着左手掌往右动,有节奏地一动一停地往右,就像脉冲一样的节奏往右。这样,整个工作区将越来越小,position 位置渐渐地向 limit 点靠拢,直至两只手掌合在一起,此时 position 点和 limit 点重合。

? ? ? ? 要是觉得这个动作有点幼稚,那就对了,说明完全掌握 ByteBuffer 其实也没有很大难度。

? ? ? ? 工作区的大小,可由 limit - position 来表示,这也就是 ByteBuffer.remaining() 的实现

工作区是 ByteBuffer 中最重要的要点(没有之一)

二、完成区 & 禁区

? ? ? ? 在工作区的左右两侧另外分别有 1 个区域:工作区的左侧是完成区,右侧是禁区,如下图。

? ? ? ? 这样整个 ByteBuffer 3 个区的结构就构建完毕了。这 3 个区先后顺序是固定的,但大小是变化的。3个区的宽度大小,最小可为 0,最大可为 capacity 大。3 个区看起来还是很乱,请不要被这个影响,一定要把注意力优先投资到两个手掌之间的工作区,这样已经足够。现在竖起双手掌,由于手是可以动的,所以左手表示的 position 以及右手表示的 limit 是可以变动的,特别是左手变动是最频繁。随着动作,工作区大小产生了变化,自然而然地也带动了左边完成区以及右边禁区的变化。


工作区两侧的完成区与禁区

三、读/写操作

在工作区上的读写操作

? ? ? ? 当对 ByteBuffer 进行操作时,所有操作都是在工作区上完成的!

? ? ? ? 进行 get() 时,每一次 get() 的调用,工作区中的 position 位置的字节被读出来,随后工作区的始点向右运动一个位置。随着不断地 get(),工作区的始点一点一点地往右运动,越变越小。// 手势做起来哈,右手掌不动,左手掌往右手掌的方向动,get 一次,动一次。尽量多做几遍

? ? ? ? 同理的,进行 put() 时,每一次 put() 调用,字节都写入到工作区的 position 位置中,随后工作区的始点向右运动一个位置。随着不断地 put(),工作区的始点一点一点地往右运动,越变越小。// put 和 get 的手势完全一样

? ? ? ? 一旦工作区大小变为 0 了,读写操作就不能再进行了,禁区是不可用于读写操作的。如果强行继续读取或写入,ByteBuffer 将分别抛出 BufferUnderflowException 或 BufferOverflowException 异常。

四、reset() 回到原先设置的 mark 处


reset 回到原先设置的 mark 处

? ? ? ? 随着 ByteBuffer 不断地工作,工作区始点逐渐往右运动,工作区越变越小。此时如果要重读刚才读取的内容,或者覆盖原先写入的内容,就可以调用 reset() 方法来满足这个需求,将工作区的始点拉回之前设置的 mark 点。

? ? ? ? reset() 操作必须和 mark() 操作结合使用。调用 mark() 时候,ByteBuffer 会把当时工作区的始点记录下来(用 mark 表示这个位置),

? ? ? ? 调用 reset() 方法并不会把 mark 标识清除,后续可以多次使用。如果之前没有 mark() 过或者 mark 标识被 rewind()、flip()、clear() 这些操作清理过,调用 reset() 没有意义,ByteBuffer 会抛出异常。此时如果要回到某个点,建议直接使用 ByteBuffer.position(int) 搞定,所谓调用 position(int) 的本质也就是应用程序自己来维护 mark 记录,这也是一个好办法。

? ? ? ? 注意:reset 方法不是把缓冲区的字节设置为 0。

? ? ? ? 练习:如何用手势来模拟 reset() 操作呢?其实非常简单,保持右手掌不懂,左手掌向左稍微挪动几步。

五、rewind() 倒带重来

? ? ? ? 英文单词 rewind 有重倒的意思。调用 rewind() 就是把工作区的始点拉到 0 处,使得接下来的工作区从 ByteBuffer 的最开始处工作。这个有啥用呢?想来想去可能在「复读」这个场景比较有用:

? ? ? 当一个 ByteBuffer 要写到多个输出源的时候可以用得上:写入到第一个输出源后,完成区变大,工作区变小,通过调用 rewind() ,把工作区的始点拉到 ByteBuffer 最开始的地方,这样就可以重新从读取刚才已经读取的字节了。

? ? ? ? 在 ByteBuffer下 rewind() 就是 position(0)。所以,实际使用起来,直接使用 position(0) 可能更容易理解?另外一个区别点, position(int) 方法在 ByteBuffer 上,没在 Buffer 上。

? ? ? ? 练习:如何用手势来模拟 rewind() 操作呢?保持右手掌不懂,左手掌向左伸直移动到最大的可能就是了。// reset() 和 rewind() 在手势上的区分就是看左手伸的多少,到之前标记的是 reset(),伸到尽头的是 rewind()

四、flip() 翻转工作区

? ? ? ? 英文单词 flip 的意思有翻、转的意思,比如海狮在沙滩上玩耍翻来翻去,调皮的同学在地上做个腾空翻等等类似的意思。


? ? ? ? 把 flip 用在 ByteBuffer 上,主要是用来表达一个动机:对 ByteBuffer 完成写入的工作后,要开始从它里面读取信息。ByteBuffer要求,当对它从写入到读取的变化,需要应用程序来告知 ByteBuffer 提前做一些内部翻转工作,flip() 方法充当这个作用,由应用程序来调用。

flip() 使 Buffer 进行了一次内部翻转

? ? ? ? 现在深入到 flip() 内部。当程序不断把数据写到 ByteBuffer,完成区 将越来越大,充满了刚刚写入的数据,此时如果要将写入的数据读取出来,根据 ByteBuffer 的哲学,就需要先把这块完成区区域设置为 工作区 才能在这片区域上工作,按应用程序的预期完成任务。把完成区完全设置为工作区的操作工程中要注意 3 个细节就是:(1)新的工作区的终点就是原来完成区的终点、原来工作区的始点;(2)新的工作区的始点在最左边,因此新的工作区和旧的工作区大小没有任何关系,所以两者大小也不相等。(3) 旧的工作区变成现在新的工作区的右边了,所以它成为禁区的一部分。

? ? ? ? flip() 这个方法是 ByteBuffer 的关键方法,重点记住这个方法吧。

? ? ? ? 练习:竖起两手的手掌,两只手中间代表的是 flip() 之前的工作区。然后两只手掌一起往左运动运动。左手掌拉伸到最左边的尽头,右手掌变动原来左手掌的位置。

六、clear() 全部变为工作区


clear() 让 ByteBuffer 做好了最大准备

? ? ? ? clear() 把缓冲区全部变为工作区,工作区最大也不过如此了。clear() 操作是唯一一个把禁区变为工作区一部分的操作??杉?clear() 目的,就是让 ByteBuffer 有最大的工作空间去容纳一会进来的字节。显然,当 ByteBuffer 的信息全部被用来后,准备要从输入源中读出新的信息写入 ByteBuffer 时,要调用 clear()。

? ? ? ? 注意:clear 方法不是把缓冲区的字节设置为 0。

? ? ? ? 练习:竖起两手的手掌,然后分别向两边拉伸到尽头!

七、总结

? ? ? ? ? 用工作区的概念及其图解、手势的方式来理解 ByteBuffer,是本文的创新点。借助两手手掌模拟工作区,并演示 get、put、reset、rewind、position(i)、flip、clear 操作对手掌位置的影响可以有效地理解和记忆。这种办法对其他人有没有用我不清楚,反正自己是用上了,也轻松了许多。

? ? ? ? ? 如果以上有助于理解,接下来可以直接看下 java.nio.Buffer 的 Java Doc ,看看是否可以清晰一些,这个过程也是一次「思维加固」。

2017-11-23

原文地址:http://08643.cn/p/0343289302a8

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,100评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,308评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,718评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,275评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,376评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,454评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,464评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,248评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,686评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,974评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,150评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,817评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,484评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,140评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,374评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,012评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,041评论 2 351