SparseArray与ArrayMap在某些情况下代替HashMap

本文深入探讨HashMap的工作原理,包括其如何利用hashCode和equals方法保证键的唯一性。此外,还介绍了SparseArray和ArrayMap这两种在特定场景下可以替代HashMap的数据结构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

HashMap是java最常用的集合框架之一,我们总在不经意间利用到它。首先,回顾下HashMap的工作原理,HashMap作为哈希家族的一员(HashMap、HashTable、HashSet)肯定涉及到Object类中的hashCode()方法,该方法平时我们主动调用的比较少,主要的作用有以下几点:

1.同一对象多次调用hashCode()方法,必须返回相同的整数,前提上是该对象在进行比较时信息没有被修改;

2.如果根据equals()方法返回两个对象对比是相等的,那么两个对象调用hashCode()方法返回的整数值必须一致;

3.如果根据equals()方法返回两个对象对比不相等,那么两个对象调用hashCode()方法返回的整数也不相等(非必要条件);

当然,Object类中的另一个强相关方法,就是equals()方法。Object 类的 equals 方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true(x == y 具有值 true)。 当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

好了,回归到HashMap。HashMap是最常用的集合类框架之一,它实现了Map接口,所以存储的元素也是键值对映射的结构,并允许使用null值和null键,其内元素是无序的,如果要保证有序,可以使用LinkedHashMap。HashMap是线程不安全的,平时在使用HashMap时候,最常使用的就是其put(K,V)与get(K)方法,HashMap的健具有唯一性,相信大家都已了解,那么如何保证其健的唯一性呢?其实在我们调用HashMap的put(K,V)方法时,首先会调用K的hashCode()方法,通过计算hashCode,快速计算出该元素的存储位置(bucketIndex,计算公式:hash(key)%len),这里会存在一个问题,会存在哈希冲突,即当hashCode相等时,其equals()方法返回的不一定为true。这个就是哈希碰撞,当出现这种情况的时候,就是我们计算的hashCode相等,即出现了K会定位到同一个bucketIndex,如何取出bucketIndex位置处存储的元素,就是通过equals()方法来进行获取。也就是说,equals()方法是在出现哈希碰撞的时候,才进行调用的。HashMap是通过hashCode()与equals()方法来判断K值是否存在,若存在,则使用新的V值来替换旧的V值,并返回旧的V值。如果不存在,则将新的Entity<K,V>存放在bucketIndex位置上。下面给出图示:


来看一下HashMap的put()源码:

  1. public V put(K key, V value) {  
  2.     // 处理key为null,HashMap允许key和value为null  
  3.     if (key == null)  
  4.         return putForNullKey(value);  
  5.     // 得到key的哈希码  
  6.     int hash = hash(key);  
  7.     // 通过哈希码计算出bucketIndex  
  8.     int i = indexFor(hash, table.length);  
  9.     // 取出bucketIndex位置上的元素,并循环单链表,判断key是否已存在  
  10.     for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  11.         Object k;  
  12.         // 哈希码相同并且对象相同时  
  13.         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  14.             // 新值替换旧值,并返回旧值  
  15.             V oldValue = e.value;  
  16.             e.value = value;  
  17.             e.recordAccess(this);  
  18.             return oldValue;  
  19.         }  
  20.     }  
  21.   
  22.     // key不存在时,加入新元素  
  23.     modCount++;  
  24.     addEntry(hash, key, value, i);  
  25.     return null;  
通过源码发现两件事情:1.hashMap是通过hashCode来快速存取元素;2.当put的元素相同时,则新的元素会覆盖旧的元素,并返回旧的元素值。

回顾完hashMap的put过程,回归主题,SparseArray与ArrayMap在某些情况下代替HashMap的情况。通常,在数据量不大(千级范围内),1.当key为int时,或者key为long时,此时的hashMap可以被SparseArray(int)或者LongSparseArray来替代;2.当key为其它类型时,可以使用ArrayMap来替代HashMap。至于为什么?继续分析如下:

  1. /** 
  2.  * The default initial capacity - MUST be a power of two. 
  3.  */  
  4. static final int DEFAULT_INITIAL_CAPACITY = 16;// 默认初始容量为16,必须为2的幂  
  5.   
  6. /** 
  7.  * The maximum capacity, used if a higher value is implicitly specified 
  8.  * by either of the constructors with arguments. 
  9.  * MUST be a power of two <= 1<<30. 
  10.  */  
  11. static final int MAXIMUM_CAPACITY = 1 << 30;// 最大容量为2的30次方  
  12.   
  13. /** 
  14.  * The load factor used when none specified in constructor. 
  15.  */  
  16. static final float DEFAULT_LOAD_FACTOR = 0.75f;// 默认加载因子0.75  
  17.   
  18. /** 
  19.  * The table, resized as necessary. Length MUST Always be a power of two. 
  20.  */  
  21. transient Entry<K,V>[] table;// Entry数组,哈希表,长度必须为2的幂  
  22.   
  23. /** 
  24.  * The number of key-value mappings contained in this map. 
  25.  */  
  26. transient int size;// 已存元素的个数  
  27.   
  28. /** 
  29.  * The next size value at which to resize (capacity * load factor). 
  30.  * @serial 
  31.  */  
  32. int threshold;// 下次扩容的临界值,size>=threshold就会扩容  
  33.   
  34.   
  35. /** 
  36.  * The load factor for the hash table. 
  37.  * 
  38.  * @serial 
  39.  */  
  40. final float loadFactor;// 加载因子 
HashMap默认的存储大小是size为16的数组(更准确的说是拉链结构),当不断的向HashMap进行put元素时,当到达一个程度时,hashMap会进行扩容操作(HashMap中的数据量>容量*加载因子),扩容后的大小为原来大小的2倍(int newCapacity = oldCapacity * 2;),重点就在这,当hashMap进行扩容操作时,会不断进行hash运算,这样会非常的消耗内存,所以在Android中,hashMap还是比较耗内存的。

SparseArray比HashMap要更加的节省内存,它的内部通过两个数组来分别存储K,V,而且对数据还采取了压缩的方式来表示稀疏数组的数据,SparseArray是通过二分查找法来存储数据或者读取数据(int i = ContainerHelpers.binarySearch(mKeys, mSize, key)),在数据量小的情况下,其执行效率比hashMap要快很多。

ArrayMap是一个<key,value>映射的数据结构,它设计上更多的是考虑内存的优化,内部是使用两个数组进行数据存储,一个数组记录key的hash值,另外一个数组记录Value值,它和SparseArray一样,也会对key使用二分法进行从小到大排序,在添加、删除、查找数据的时候都是先使用二分查找法得到相应的index,然后通过index来进行添加、查找、删除等操作,所以,应用场景和SparseArray的一样,如果在数据量比较大的情况下,那么它的性能将退化至少50%。

总结:在小数据量的情况下,可以考虑利用SparseArray与ArrayMap来替代HashMap

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值