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()源码:
- public V put(K key, V value) {
- // 处理key为null,HashMap允许key和value为null
- if (key == null)
- return putForNullKey(value);
- // 得到key的哈希码
- int hash = hash(key);
- // 通过哈希码计算出bucketIndex
- int i = indexFor(hash, table.length);
- // 取出bucketIndex位置上的元素,并循环单链表,判断key是否已存在
- for (Entry<K,V> e = table[i]; e != null; e = e.next) {
- Object k;
- // 哈希码相同并且对象相同时
- if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
- // 新值替换旧值,并返回旧值
- V oldValue = e.value;
- e.value = value;
- e.recordAccess(this);
- return oldValue;
- }
- }
- // key不存在时,加入新元素
- modCount++;
- addEntry(hash, key, value, i);
- return null;
- }
回顾完hashMap的put过程,回归主题,SparseArray与ArrayMap在某些情况下代替HashMap的情况。通常,在数据量不大(千级范围内),1.当key为int时,或者key为long时,此时的hashMap可以被SparseArray(int)或者LongSparseArray来替代;2.当key为其它类型时,可以使用ArrayMap来替代HashMap。至于为什么?继续分析如下:
- /**
- * The default initial capacity - MUST be a power of two.
- */
- static final int DEFAULT_INITIAL_CAPACITY = 16;// 默认初始容量为16,必须为2的幂
- /**
- * The maximum capacity, used if a higher value is implicitly specified
- * by either of the constructors with arguments.
- * MUST be a power of two <= 1<<30.
- */
- static final int MAXIMUM_CAPACITY = 1 << 30;// 最大容量为2的30次方
- /**
- * The load factor used when none specified in constructor.
- */
- static final float DEFAULT_LOAD_FACTOR = 0.75f;// 默认加载因子0.75
- /**
- * The table, resized as necessary. Length MUST Always be a power of two.
- */
- transient Entry<K,V>[] table;// Entry数组,哈希表,长度必须为2的幂
- /**
- * The number of key-value mappings contained in this map.
- */
- transient int size;// 已存元素的个数
- /**
- * The next size value at which to resize (capacity * load factor).
- * @serial
- */
- int threshold;// 下次扩容的临界值,size>=threshold就会扩容
- /**
- * The load factor for the hash table.
- *
- * @serial
- */
- final float loadFactor;// 加载因子
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