android Buffer的使用

  Buffer的意思就是缓冲区,它的作用就是在内存中预留出一定空间的内存大小,主要用来作为临时数据的存储,那么这部分内存区域,我们就称之为缓冲区,这样做的好处有俩个:

  1、减少实际的物理读写次数
  2、缓冲区在创建时就被分配内存,这块内存区域一直被重用,可以减少动态分配和回收内存的次数

  比如,现在有一批货需要从A地移到B地,有100个货物,B地虽然有很大的位置给A的货物放置,但是不知道A具体需要放多少东西,那只能一次给一个货物的位置,那么A拉货的时候就很烦了,因为B告诉A你每次只能拉一个东西过来,因为我不知道你要拉多少东西,我位置不能乱给,那么A就苦逼了,要拉100次,而且每次都要B重新给了货物的位置,那么最终的结果就是A和B都很烦,那现在有个方案了,A告诉B,我现在要50个货物的位置,你给我留着,我肯定能用上,B说,好啊,那你拉吧,这下A开心了,一次就拉了50个货物过去,结果只要拉俩次就把货物拉完了,A和B都很开心。

  那上面的例子就是能够反映出缓冲区的作用,预留货物的位置就可以称之为缓冲区。

  那么再来看Buffer,Buffer本身是抽象类,我们看它的子类。
它的子类有:

ByteBuffer,CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer

  那我们选ByteBuffer看,一看,我擦,还是抽象的,但是不要紧,ByteBuffer里面的一些方法基本上就可以让我们知道缓冲区是如何使用的。
那下面说说关于它的一些介绍。

Fields

属性 描述
Capacity 容量,即可以容纳的最大数据量;在缓冲区创建时被设定并且不能改变
Limit 表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作。且极限是可以修改的
Position 位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变改值,为下次读写作准备
Mark 标记,调用mark()来设置mark=position,再调用reset()可以让position恢复到标记的位置

实例化

  ByteBuffer类提供了4个静态工厂方法来获得ByteBuffer的实例:

方法 描述
allocate(int capacity) 从堆空间中分配一个容量大小为capacity的byte数组作为缓冲区的byte数据存储器
allocateDirect(int capacity) 是不使用JVM堆栈而是通过操作系统来创建内存块用作缓冲区,它与当前操作系统能够更好的耦合,因此能进一步提高I/O操作速度。但是分配直接缓冲区的系统开销很大,因此只有在缓冲区较大并长期存在,或者需要经常重用时,才使用这种缓冲区
wrap(byte[] array) 这个缓冲区的数据会存放在byte数组中,bytes数组或buff缓冲区任何一方中数据的改动都会影响另一方。其实ByteBuffer底层本来就有一个bytes数组负责来保存buffer缓冲区中的数据,通过allocate方法系统会帮你构造一个byte数组
wrap(byte[] array,int offset, int length) 在上一个方法的基础上可以指定偏移量和长度,这个offset也就是包装后byteBuffer的position,而length呢就是limit-position的大小,从而我们可以得到limit的位置为length+position(offset)

Methods

方法 描述
limit(), limit(10)等 其中读取和设置这4个属性的方法的命名和jQuery中的val(),val(10)类似,一个负责get,一个负责set
reset() 把position设置成mark的值,相当于之前做过一个标记,现在要退回到之前标记的地方
clear() position = 0;limit = capacity;mark = -1; 有点初始化的味道,但是并不影响底层byte数组的内容
flip() limit = position;position = 0;mark = -1; 翻转,也就是让flip之后的position到limit这块区域变成之前的0到position这块,翻转就是将一个处于存数据状态的缓冲区变为一个处于准备取数据的状态
rewind() 把position设为0,mark设为-1,不改变limit的值
remaining() return limit - position; 返回limit和position之间相对位置差
hasRemaining() return position < limit返回是否还有未读内容
compact() 把从position到limit中的内容移到0到limit-position的区域内,position和limit的取值也分别变成limit-position、capacity。如果先将positon设置到limit,再compact,那么相当于clear()
get() 相对读,从position位置读取一个byte,并将position+1,为下次读写作准备
get(int index) 绝对读,读取byteBuffer底层的bytes中下标为index的byte,不改变position
get(byte[] dst, int offset, int length) 从position位置开始相对读,读length个byte,并写入dst下标从offset到offset+length的区域
put(byte b) 相对写,向position的位置写入一个byte,并将postion+1,为下次读写作准备
put(int index, byte b) 绝对写,向byteBuffer底层的bytes中下标为index的位置插入byte b,不改变position
put(ByteBuffer src) 用相对写,把src中可读的部分(也就是position到limit)写入此byteBuffer
put(byte[] src, int offset, int length) 从src数组中的offset到offset+length区域读取数据并使用相对写写入此byteBuffer

  那说了这么多方法的使用后,我们可以去看一看这个预留内存到底是怎么安排的,查看byteBuffer,我们可以发现有一个变量hb,是final byte[] hb,其余的变量都是其余基本数据类型,应该不是我们想要的,那么围绕这个查找,在put方法里面我们可以查看这样一段代码

if (this.hb != null && src.hb != null) {
      System.arraycopy(src.hb, src.position() + src.offset, hb, position() + offset, n);
} else {
      final Object srcObject = src.isDirect() ? src : src.hb;
      int srcOffset = src.position();
      if (!src.isDirect()) {
           srcOffset += src.offset;
      }
      final ByteBuffer dst = this;
      final Object dstObject = dst.isDirect() ? dst : dst.hb;
      int dstOffset = dst.position();
      if (!dst.isDirect()) {
           dstOffset += dst.offset;
       }
       Memory.memmove(dstObject, dstOffset, srcObject, srcOffset, n);
}

  在这里我们可以看到会对hb是否为空进行判断,如果不为空执行

System.arraycopy(src.hb, src.position() + src.offset, hb, position() + offset, n);

  如果为空,则执行下面长长的代码,一般的代码不必多说,看这一行代码

Memory.memmove(dstObject, dstOffset, srcObject, srcOffset, n);

  整个代码片段里面我们可以从这俩个方法,但是其实这俩个方法都是底层实现的,那这里我们解释下具体作用是什么。

System.arraycopy()

  首先说个知识前提,Java数组的复制操作可以分为深度复制和浅度复制,简单来说深度复制,可以将对象的值和对象的内容复制;浅复制是指对对象引用的复制。
System.arraycopy的函数原型是

public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length)

  其中,src表示源数组,srcPos表示源数组要复制的起始位置,desc表示目标数组,length表示要复制的长度,那么对于一维数组来说,这种复制属性值传递,修改副本不会影响原来的值。对于二维或者一维数组中存放的是对象时,复制结果是一维的引用变量传递给副本的一维数组,修改副本时,会影响原来的数组。这个各位看官可以自己去测试一下,这里就不再举例了。

Memory.memmove()

  memcpy和memmove()都是C语言中的库函数,在头文件string.h中,作用是拷贝一定长度的内存的内容,原型分别如下

void *memcpy(void *dst, const void *src, size_t count);
void *memmove(void *dst, const void *src, size_t count);

  他们的作用是一样的,唯一的区别是,当内存发生局部重叠的时候,memmove保证拷贝的结果是正确的,memcpy不保证拷贝的结果的正确。
我们看看这俩个方法的具体实现

void* my_memcpy(void* dst, const void* src, size_t n)
{
    char *tmp = (char*)dst;
    char *s_src = (char*)src;
    while(n--) {
        *tmp++ = *s_src++;
    }
    return dst;
}

  从实现中可以看出memcpy()是从内存左侧一个字节一个字节地将src中的内容拷贝到dest的内存中
再看看另一个方法

void* my_memmove(void* dst, const void* src, size_t n)
{
    char* s_dst;
    char* s_src;
    s_dst = (char*)dst;
    s_src = (char*)src;
    if(s_dst>s_src && (s_src+n>s_dst)) {      //内存覆盖的情形。
        s_dst = s_dst+n-1;
        s_src = s_src+n-1;
        while(n--) {
            *s_dst-- = *s_src--;
        }
    }else {
        while(n--) {
            *s_dst++ = *s_src++;
        }
    }
    return dst;
}

  那么我们再看看内存覆盖是什么情况



  从内存可能覆盖的情况来看,当使用memcpy的时候,这种实现方式导致了对于图中第二种内存重叠情形下,最后两个字节的拷贝值明显不是原先的值了,新的值是变成了src的最开始的2个字节了,而memmove是可以正常工作的。

  到这里关于Buffer的介绍就结束了,最后感谢以下文章的提供者。

  ByteBuffer常用方法详解
  System.arraycopy()方法详解
  memmove 和 memcpy的区别以及处理内存重叠问题

  喜欢技术研究的可以加入QQ群一起讨论:561176094

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

推荐阅读更多精彩内容

  • 转自 http://www.ibm.com/developerworks/cn/education/java/j-...
    抓兔子的猫阅读 2,298评论 0 22
  • Buffer java NIO库是在jdk1.4中引入的,NIO与IO之间的第一个区别在于,IO是面向流的,而NI...
    德彪阅读 2,198评论 0 3
  • # Java NIO # Java NIO属于非阻塞IO,这是与传统IO最本质的区别。传统IO包括socket和文...
    Teddy_b阅读 586评论 0 0
  • 以 Buffer 类开始我们对 java.nio 软件包的浏览历程。这些类是 java.nio 的构造基础。在本章...
    沉沦2014阅读 580评论 0 4
  • 为了提高性能,通常将顶点,颜色等值存放在java.nio 包中定义的Buffer类中。 例: ByteBuffer...
    长何阅读 640评论 0 0