SparesArray 源码


一、介绍

Android 平台,SparesArray 类可以替代 HashMap 作为数据存储,与 HashMap 数组+链表的数据结构不同,采用两个数组的方案。下面是源码中定义的两个数组,分别存储 key 和 value。其中,key 必须为 int 类型。

private int[] mKeys;
private Object[] mValues;

public SparseArray() {
    this(10);
}

无参构造方法,默认容量10。

public SparseArray(int initialCapacity) {
    if (initialCapacity == 0) {
        mKeys = EmptyArray.INT;
        mValues = EmptyArray.OBJECT;
    } else {
        mValues = ArrayUtils.newUnpaddedObjectArray(initialCapacity);
        mKeys = new int[mValues.length];
    }
    mSize = 0;
}

根据容量值,初始化两个数组,它们长度是一样。

二、数据存取

public void put(int key, E value) {
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
    //key已存在mKey中,直接更新mValue数组。
    if (i >= 0) {
        mValues[i] = value;
    } else {//key不存在mKey中
        i = ~i;//将最后查找的索引位置恢复,此位置对应新key的值正好符合数组排序。
        //key应放置的位置在size数量内,正好对应value处是DELETED。
        //一般情况下根据新key是根据排序插入的,此位置key已经有值了,value也会被占坑。
        //如果value被delete,就可以将key替换这个旧key,同时value也设置新的。
        if (i < mSize && mValues[i] == DELETED) {
            mKeys[i] = key;
            mValues[i] = value;
            return;
        }
        //下面的情况说明
        //key最大,应放在size处,0到size-1的值都比它小。
        //或者key值可以放到位置i,位置i处有其他value有用,非DELETED。
        if (mGarbage && mSize >= mKeys.length) {
            gc();
            i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
        }
         //key插入到合适位置i。mKeys后面的元素需要后移。自增mSize。
         //当遇到数组元素不够时,自动扩容
        mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
        mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
        mSize++;
    }
}

根据 key 值,在数组 mKeys 查找索引,二分查找算法,如果查值 >=0,同步更新数组 mValues 相同索引的 value 值即可。

static int binarySearch(int[] array, int size, int value) {
    int lo = 0;
    int hi = size - 1;

    while (lo <= hi) {
        final int mid = (lo + hi) >>> 1;
        final int midVal = array[mid];
        if (midVal < value) {
            lo = mid + 1;
        } else if (midVal > value) {
            hi = mid - 1;
        } else {
            return mid;  // 返回到value值在array的索引,
        }
    }
    return ~lo;  //未找到value,不可能返回一个正确的索引,因此返回负值
}

如果 key 不在数组中,情况比较复杂,源码的逻辑如下。
1,取反返回值,从上面 binarySearch 源码可知,当未找到时,返回最后查找的索引位置取反 (<0)。key 数组升序存储,该 key 值在数组排序应该插入的位置。
2,如果索引在 mSize 范围且这个位置的 value 已被删除( DELETED 状态)。那么,可以直接将 value 值放到数组 mValues 的此索引位置,并替换掉 key。如果不满足以上条件,说明这个位置存在有用的 key 和 value 值,无法替换。或者插入位置在末尾,即当 key >= mSize,应该插入 mSize 索引处,该索引目前 value 无值,也不能直接插入,防止已经达到 length,要走后面的逻辑,垃圾状态时清理数组重排,以及最后的强行插入,自动扩容。

mSize 是当前数组的元素个数,mKeys.length 返回的是数组大小。

3,GrowingArrayUtils() 方法,key 和 value 插入索引位置,此索引后面元素将后移,容量不足时,还会自动扩容,自增 mSize,表示数组又增加了一个元素。
垃圾清理,当有垃圾标志时,且 mSize>= 数组 length,说明数组已经无位置啦,有一个 gc 的过程,遍历一次,将带垃圾 Object 的 value 清理掉,重新放置数组元素,这个过程试图减小 mSize 使其小于 length,并重新查找该 key 的位置。
垃圾标志,在 removeAt() 方法,从 value 的数组删除某项 key 的 value 时,将该 value 位置值指向 DELETED,它是一个内部 Object 对象。

private static final Object DELETED = new Object();

public void removeAt(int index) {
    if (mValues[index] != DELETED) {
        mValues[index] = DELETED;
        mGarbage = true;
    }
}

数组查找数据

public E get(int key, E valueIfKeyNotFound) {
    int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
    if (i < 0 || mValues[i] == DELETED) {
        return valueIfKeyNotFound;
    } else {
        return (E) mValues[i];
    }
}

根据 key,二分查找算法,计算索引,返回数组 mValues 该索引 value 值。若查找的索引小于0,说明数组不存在 key 键或 value 数组索引值 DELETED,返回默认 valueIfKeyNotFound。

三、总结

SparesArray 用两个数组存储 key 和 value,保持相同索引,int 数组和 Object 数组。key 键是 int 基本数据类型,不需要 hash 计算,直接返回索引。

HashMap 的 key 键必须是引用类型,SparesArray 可以避免 key 的自动装箱,数据量不大时可以代替 HashMap,更省内存。

采用二分查找算法获取数据 value。


任重而道远

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,646评论 18 139
  • 本系列出于AWeiLoveAndroid的分享,在此感谢,再结合自身经验查漏补缺,完善答案。以成系统。 Java基...
    济公大将阅读 1,526评论 1 6
  • 一、基本数据类型 注释 单行注释:// 区域注释:/* */ 文档注释:/** */ 数值 对于byte类型而言...
    龙猫小爷阅读 4,257评论 0 16
  • 1 今天上午一个读者跑来加了微信然并没来撩我,却在朋友圈给我点了几个赞。 后来,她终于忍不住发来消息说,苏苏,我翻...
    北苏阅读 660评论 11 13
  • 陌上青碧渐次醒, 杨花初起柳花轻。 檐间燕子迎风舞, 栖上小鸟听雨鸣。
    五月慕晴阅读 332评论 3 4