SparseArray与HashMap


176

我可以想到几个HashMap具有整数键的SparseArrays 比s 更好的原因:

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

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

Answers:


234

SparseArrayHashMap当键是原始类型时可以用来替换。尽管不是所有的键/值类型都可以公开使用,但仍有一些变体。

好处是:

  • 免分配
  • 没有拳击

缺点:

  • 通常较慢,不适用于大型收藏
  • 他们将无法在非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 

就内存而言,这是SparseIntArrayvs HashMap<Integer, Integer>代表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字节

资料来源:Romain Guy的Android Memories(来自幻灯片90)。

上面的数字是JVM在堆上分配的内存量(以字节为单位)。它们可能会有所不同,具体取决于所使用的JVM。

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

可从Oracle官方文档中获得更多信息。

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


15
有人可以指导我这些“ 12 + 3 * 4”和“ 20 + 1000 * 4”的来源吗?
MarianPaździoch15年

5
@MarianPaździoch,他展示了一个演示文稿(Speakerdeck.com/romainguy/android-memories),其中一个类占用12个字节+ 3个变量(4个字节),一个数组(引用)占用20个字节(dlmalloc-4,对象开销-8,宽度和填充) -8)。
CoolMind

1
记录下来,SparseArray的另一个主要缺点是,作为Android对象,需要对它进行模拟以进行单元测试。现在,我尽可能使用Java自己的对象简化测试。
David G

@DavidG您可以只使用unmock插件来模拟android依赖项。
暴雪

1
即使您未使用Android,将类复制到项目中也不难,它仅取决于其他3个类。无论您使用什么许可证,APL许可证都可以做到这一点。
Yann TM

35

我来到这里只是想举例说明如何使用 SparseArray。这是对此的补充回答。

创建一个SparseArray

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

A 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参数是整数键。

整数键的查找值

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

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

get(int key, E valueIfKeyNotFound)如果要避免null丢失密钥,可以使用。

遍历项目

您可以使用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

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


17

但是,每当我尝试在android项目中使用带有整数键的HashMap时,intelliJ都会告诉我应该改用SparseArray。

这只是本文档中有关其稀疏数组的警告:

与使用HashMap将Integer映射到Object相比,它的内存使用效率更高。

SparseArray被制成存储器高效比使用常规的HashMap,即不允许该阵列不喜欢的HashMap内的多个间隙。如果您不想担心为设备分配的内存,则无需担心,可以使用传统的HashMap。


5
关于节省内存的观点显然是正确的,但是我从来没有理解过为什么android无法使SparseArray <T>实现Map <Integer,T>从而使您获得内存高效的Map实现-两全其美。
保罗·波丁顿

3
@PaulBoddington还记得SparseArray防止将键整数设为“自动”框,这是另一种操作和成本效益。而不是Map,它将自动将原始整数Integer
装箱

同样适用,但是如果他们通过在签名方法中放置一个带有签名put(int a,T t)的方法而使put方法超载,那么您仍然可以将键值对放入地图中,而无需自动装箱键。我只是认为Collections Framework如此强大(使用Java的最佳原因之一),以至于没有利用它是疯狂的。
保罗·波丁顿

6
@PaulBoddington集合基于不是原始对象的对象,因此无法在Collections API中使用
Rod_Algonquin 2014年

10

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

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

  2. 稀疏数组仅由两个数组组成:(原始)键的数组和(对象)值的数组。这些数组索引中可能会有间隙,因此称为“稀疏”数组。

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


10

谷歌搜索后,我尝试将一些信息添加到已发布的答案中:

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

Hashmap和SparseArray对于小于1000的数据结构大小非常相似

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

Edgblog的比较表明,由于键(int与Integer)较小,并且SparseArray需要的内存比HashMap小得多,因此它比HashMap少

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

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


4

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

是的,它是正确的。但是,当您只有10或20个项目时,性能差异应该很小。

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

我认为大多数情况下,我们通常只使用HashMap与键相关联的值来搜索,而SparseArray这确实非常有用。

如果您使用HashMaps而非SparseArrays编写代码,则您的代码将在非Android项目中运行。

SparseArray的源代码非常简单易懂,因此您只需花费很少的精力(通过简单的COPY&Paste)将其移动到其他平台即可。

地图会覆盖equals()和hashCode(),而SparseArray不会

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

的另一个重要方面SparseArray是,它仅使用一个数组来存储所有元素而HashMap使用Entry,所以SparseArray成本比一个显著较少的内存HashMap,看到


1

不幸的是,编译器会发出警告。我想HashMap已被过度使用来存储项目。

SparseArrays占有一席之地。假定他们使用二进制搜索算法在数组中查找值,则必须考虑您在做什么。二进制搜索为O(log n),而哈希查找为O(1)。这并不一定意味着对于给定的数据集,二进制搜索会变慢。但是,随着条目数量的增加,哈希表的功能将接管。因此,注释中的条目数较少,可能比使用HashMap更好。

HashMap不仅与哈希表一样好,而且会受到负载因子的影响(我认为在更高版本中,它们会忽略负载因子,因此可以更好地对其进行优化)。他们还添加了一个辅助哈希,以确保哈希良好。这也是SparseArray在相对较少的条目(<100)上工作得很好的原因。

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

使用简化的多dex构建,我们甚至不需要为您需要的东西重新打包。(trove有很多课程)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.