【问题标题】:SparseArray vs HashMapSparseArray 与 HashMap
【发布时间】:2014-10-23 00:20:16
【问题描述】:

我可以想到几个原因,为什么带有整数键的HashMaps 比SparseArrays 好得多:

  1. SparseArray 的 Android 文档说“它通常比传统的 HashMap 慢”。
  2. 如果您使用HashMaps 而不是SparseArrays 编写代码,您的代码将适用于地图的其他实现,并且您将能够使用为地图设计的所有Java API。
  3. 如果您使用HashMaps 而不是SparseArrays 编写代码,您的代码将在非android 项目中工作。
  4. 地图会覆盖equals()hashCode(),而SparseArray 不会。

然而,每当我尝试在 Android 项目中使用带有整数键的 HashMap 时,IntelliJ 都会告诉我应该改用 SparseArray。我觉得这真的很难理解。有人知道使用SparseArrays 的任何令人信服的理由吗?

【问题讨论】:

    标签: java android hashmap sparse-matrix


    【解决方案1】:

    SparseArray 可用于当键是原始类型时替换 HashMap。 不同的键/值类型有一些变体,尽管并非所有变体都是公开可用的。

    好处是:

    • 免分配
    • 没有拳击

    缺点:

    • 通常较慢,不适用于大型集合
    • 它们不适用于非 Android 项目

    HashMap 可以替换为:

    SparseArray          <Integer, Object>
    SparseBooleanArray   <Integer, Boolean>
    SparseIntArray       <Integer, Integer>
    SparseLongArray      <Integer, Long>
    LongSparseArray      <Long, Object>
    LongSparseLongArray  <Long, Long>   //this is not a public class                                 
                                        //but can be copied from  Android source code 
    

    在内存方面,这里是 SparseIntArrayHashMap&lt;Integer, Integer&gt; 的 1000 个元素的示例:

    SparseIntArray:

    class SparseIntArray {
        int[] keys;
        int[] values;
        int size;
    }
    

    类 = 12 + 3 * 4 = 24 个字节
    数组 = 20 + 1000 * 4 = 4024 字节
    总计 = 8,072 字节

    HashMap:

    class HashMap<K, V> {
        Entry<K, V>[] table;
        Entry<K, V> forNull;
        int size;
        int modCount;
        int threshold;
        Set<K> keys
        Set<Entry<K, V>> entries;
        Collection<V> values;
    }
    

    类 = 12 + 8 * 4 = 48 个字节
    条目 = 32 + 16 + 16 = 64 字节
    数组 = 20 + 1000 * 64 = 64024 字节
    总计 = 64,136 字节

    来源:Android Memories by Romain Guy 来自幻灯片 90。

    上面的数字是 JVM 在堆上分配的内存量(以字节为单位)。 它们可能因使用的特定 JVM 而异。

    java.lang.instrument 包包含一些对高级操作有用的方法,例如使用getObjectSize(Object objectToSize) 检查对象的大小。

    更多信息可从官方Oracle documentation获得。

    类 = 12 字节 +(n 个实例变量)* 4 字节
    数组 = 20 字节 +(n 个元素)*(元素大小)
    条目 = 32 字节 +(第一个元素大小)+(第二个元素大小)

    【讨论】:

    • 谁能指导我这些“12 + 3 * 4”和“20 + 1000 * 4”是从哪里来的?
    • @MarianPaździoch,他展示了一个演示文稿(speakerdeck.com/romainguy/android-memories),其中一个类占用 12 个字节 + 3 个 4 个字节的变量,一个数组(引用)占用 20 个字节(dlmalloc - 4,对象开销 - 8 , width&padding - 8).
    • 为了记录,SparseArray 的另一个主要缺点是,作为一个 Android 对象,它需要被模拟以进行单元测试。我现在尽可能使用 Java 自己的对象来简化测试。
    • @DavidG 你可以使用unmock plugin来模拟android依赖。
    • 即使你不是在做Android,将类复制到你的项目中并不难,它只依赖于其他3个类。 APL 许可证意味着可以这样做,无论您使用什么许可证。
    【解决方案2】:

    我来到这里只是想要一个如何使用SparseArray 的示例。这是对此的补充答案。

    创建一个稀疏数组

    SparseArray<String> sparseArray = new SparseArray<>();
    

    SparseArray 将整数映射到某个Object,因此您可以将上面示例中的String 替换为任何其他Object。如果您要将整数映射到整数,请使用SparseIntArray

    添加或更新项目

    使用put(或append)向数组中添加元素。

    sparseArray.put(10, "horse");
    sparseArray.put(3, "cow");
    sparseArray.put(1, "camel");
    sparseArray.put(99, "sheep");
    sparseArray.put(30, "goat");
    sparseArray.put(17, "pig");
    

    请注意,int 键不需要按顺序排列。这也可用于更改特定 int 键的值。

    删除项目

    使用remove(或delete)从数组中删除元素。

    sparseArray.remove(17); // "pig" removed
    

    int 参数是整数键。

    查找 int 键的值

    使用get 获取某个整数键的值。

    String someAnimal = sparseArray.get(99);  // "sheep"
    String anotherAnimal = sparseArray.get(200); // null
    

    如果您想避免因缺少密钥而收到null,您可以使用get(int key, E valueIfKeyNotFound)

    迭代项目

    您可以使用keyAtvalueAt 一些索引来循环遍历集合,因为SparseArray 维护一个与int 键不同的单独索引。

    int size = sparseArray.size();
    for (int i = 0; i < size; i++) {
    
        int key = sparseArray.keyAt(i);
        String value = sparseArray.valueAt(i);
    
        Log.i("TAG", "key: " + key + " value: " + value);
    }
    
    // key: 1 value: camel
    // key: 3 value: cow
    // key: 10 value: horse
    // key: 30 value: goat
    // key: 99 value: sheep
    

    请注意,键是按升序排列的,而不是按它们添加的顺序。

    【讨论】:

      【解决方案3】:

      然而,每当我尝试在 android 中使用带有整数键的 HashMap 项目中,intelliJ 告诉我应该使用 SparseArray。

      这只是来自这个稀疏数组的documentation 的警告:

      它旨在比使用 HashMap 更节省内存 将整数映射到对象

      SparseArray 比使用常规 HashMap 更内存效率,即不允许在数组中出现多个间隙,不像 HashMap。如果您不想担心设备的内存分配问题,您可以使用传统的 HashMap。

      【讨论】:

      • 关于节省内存的观点显然是有效的,但我一直不明白为什么android不能让 SparseArray 实现 Map 以便您获得内存高效的 Map 实现- 两全其美。
      • @PaulBoddington 还记得SparseArray 防止关键整数是Auto box,这是另一种操作和性价比。而不是 Map 它将原始整数自动装箱到Integer
      • 也是正确的,但是如果他们通过包含一个带有签名 put(int a, T t) 的 put 方法来重载 put 方法,那么您仍然可以将键值对放入没有键的映射中被自动装箱。我只是认为集合框架是如此强大(使用 Java 的最佳理由之一)以至于不利用它是疯狂的。
      • @PaulBoddington 集合基于对象而不是原始对象,因此它无法在集合 API 中工作
      【解决方案4】:

      经过一番谷歌搜索后,我尝试在已发布的答案中添加一些信息:

      Isaac Taylor 对 SparseArrays 和 Hashmaps 进行了性能比较。他说

      Hashmap 和 SparseArray 的数据结构非常相似 尺寸小于 1,000

      当大小增加到 10,000 标记时 [...] Hashmap 在添加对象时具有更高的性能,而 SparseArray 具有 检索对象时性能更高。 [...] 大小为 100,000 [...] Hashmap 会很快失去性能

      Edgblog 的比较表明,由于键更小(int vs Integer),SparseArray 需要的内存比 HashMap 少得多,而且

      HashMap.Entry 实例必须跟踪 键、值和下一个条目。另外它还需要存储 条目的哈希为 int。

      作为结论,如果您要在地图中存储大量数据,那么差异可能很重要。否则,请忽略警告。

      【讨论】:

        【解决方案5】:

        Java 中的稀疏数组是一种将键映射到值的数据结构。与 Map 的想法相同,但实现方式不同:

        1. Map 在内部表示为一个列表数组,其中这些列表中的每个元素都是一个键值对。键和值都是对象实例。

        2. 稀疏数组由两个数组组成:一个(基元)键数组和一个(对象)值数组。这些数组索引中可能存在间隙,因此称为“稀疏”数组。

        SparseArray 的主要优点在于它通过使用基元而不是对象作为键来节省内存。

        【讨论】:

          【解决方案6】:

          SparseArray 的 android 文档说“它通常是 比传统的 HashMap 慢”。

          是的,没错。但是当您只有 10 或 20 个项目时,性能差异应该是微不足道的。

          如果您使用 HashMaps 而不是 SparseArrays 编写代码,您的代码 将与 Map 的其他实现一起使用,您将能够 使用为地图设计的所有 Java API

          我认为大多数情况下我们只使用HashMap 来搜索与键关联的值,而SparseArray 非常擅长这一点。

          如果您使用 HashMaps 而不是 SparseArrays 编写代码,您的代码 将在非android项目中工作。

          SparseArray 的源代码相当简单易懂,因此您只需花费很少的精力将其移动到其他平台(通过简单的复制和粘贴)。

          Map 覆盖 equals() 和 hashCode() 而 SparseArray 没有

          我只能说,(对大多数开发人员)谁在乎?

          SparseArray 的另一个重要方面是它只使用一个数组来存储所有元素,而HashMap 使用Entry,因此SparseArrayHashMap 消耗的内存要少得多,请参阅this

          【讨论】:

            【解决方案7】:

            很遗憾编译器会发出警告。我猜 HashMap 已经被过度用于存储项目了。

            SparseArrays 有自己的位置。鉴于他们使用二进制搜索算法在数组中查找值,您必须考虑您在做什么。二分查找是 O(log n),而哈希查找是 O(1)。这并不一定意味着对于给定的数据集,二分查找速度较慢。但是,随着条目数量的增加,哈希表的功能开始发挥作用。因此,条目数较少的 cmets 可以等于并且可能比使用 HashMap 更好。

            HashMap 仅与散列一样好,并且还会受到负载因子的影响(我认为在以后的版本中它们会忽略负载因子,因此可以更好地优化)。他们还添加了一个二级哈希以确保哈希是好的。这也是 SparseArray 对于相对较少的条目(

            我建议如果你需要一个哈希表并且想要更好地使用原始整数(没有自动装箱)等的内存,试试 trove。 (http://trove.starlight-systems.com - LGPL 许可证)。 (与 trove 无关,就像他们的图书馆一样)

            通过简化的多 dex 构建,您甚至无需重新打包 trove 即可满足您的需求。 (trove 有很多类)

            【讨论】:

              猜你喜欢
              • 2020-05-03
              • 1970-01-01
              • 2019-02-20
              • 2016-05-24
              • 1970-01-01
              • 2011-12-21
              • 2015-03-31
              • 1970-01-01
              • 2016-08-01
              相关资源
              最近更新 更多