[原创]Android系统中常用JAVA类源码浅析之HashMap
2016-04-26 18:51
483 查看
由于是浅析,所以我只分析常用的接口,注意是Android系统中的JAVA类,可能和JDK的源码有区别。
首先从构造函数开始,
通过三个构造函数的源码,我们可以知道:
HashMap内部实际上使用HashMapEntry数组来实现的。
当调用new HashMap()时,会创建容量为2的HashMapEntry数组,并且threshold为-1。
当调用HashMap(int capacity)时,HashMap会将传入的capacity转换成最小的大于等于capacity的2的次方,比如:capacity=25,会转换成32。并且threshold为总容量的75%,threshold的作用是当entry的数量大于threshold时,进行扩容。
HashMap(int capacity, float loadFactory)实际上和HashMap(int capacity)是一样的,loadFactory参数未被使用(注意这是Android做的修改,实际上JDK中会使用这个参数)。
既然是HashMapEntry数组实现的,我们简单看下这个Entry什么样,
这里注意关注next属性,有一定经验的朋友肯定知道,这是单向链表的实现,所以实现HashMap的数组的每一项其实是一个单向链表的Head,继续往下看,
接下来我们分析下put(K key, V value)方法,
从put(K key, V value)的源码我们可以得到如下信息:
当添加key为null的value时,会用单独的HashMapEntry entryForNullKey对象来储存。
entry数组的索引是通过hash算出来的:int index = hash & (tab.length - 1)。
当发生碰撞时(也就是算出的index上已经存在entry了),会首先检查是否是同一个hash和key,如果是则更新value,然后直接将old value返回。
新创建的entry会被设置成对应index上的链表Head。
当entry数量大于threshold(capacity的75%)时,对数组进行扩容,扩大为原来的2倍,并重新计算原数组中所有entry的index,然后复制到新数组中。
分析完put后,其他如get、remove、containsKey等接口就大同小异了,在此直接略过。
接下来我们看下Set<K> keySet()接口:
从源码中可以得出如下结论:
keySet返回的Set对象实际上和HashMap是强关联的,对Set接口的调用,实际上操作的还是HashMap。
Set中的iterator实际上也是实现自HashIterator。
entrySet()、valueSet()和keySet()的实现原理一样。
知道HashMap的实现原理后,我们就可以知道他的优缺点了:
优点:读写效率高,接近数组的索引方式。
缺陷:会占用大量的无效内存,为了减少碰撞,Entry数组的容量只能是2的N次幂,并且当entry数大于总容量的75%时就会扩容两倍。
如有问题,欢迎指出!
转载请注明出处。
首先从构造函数开始,
/** * Min capacity (other than zero) for a HashMap. Must be a power of two * greater than 1 (and less than 1 << 30). */ private static final int MINIMUM_CAPACITY = 4; /** * Max capacity for a HashMap. Must be a power of two >= MINIMUM_CAPACITY. */ private static final int MAXIMUM_CAPACITY = 1 << 30; /** * An empty table shared by all zero-capacity maps (typically from default * constructor). It is never written to, and replaced on first put. Its size * is set to half the minimum, so that the first resize will create a * minimum-sized table. */ private static final Entry[] EMPTY_TABLE = new HashMapEntry[MINIMUM_CAPACITY >>> 1]; /** * The default load factor. Note that this implementation ignores the * load factor, but cannot do away with it entirely because it's * mentioned in the API. * * <p>Note that this constant has no impact on the behavior of the program, * but it is emitted as part of the serialized form. The load factor of * .75 is hardwired into the program, which uses cheap shifts in place of * expensive division. */ static final float DEFAULT_LOAD_FACTOR = .75F; /** * The hash table. If this hash map contains a mapping for null, it is * not represented this hash table. */ transient HashMapEntry<K, V>[] table; /** * The entry representing the null key, or null if there's no such mapping. */ transient HashMapEntry<K, V> entryForNullKey; /** * The number of mappings in this hash map. */ transient int size; /** * Incremented by "structural modifications" to allow (best effort) * detection of concurrent modification. */ transient int modCount; /** * The table is rehashed when its size exceeds this threshold. * The value of this field is generally .75 * capacity, except when * the capacity is zero, as described in the EMPTY_TABLE declaration * above. */ private transient int threshold; public HashMap() { table = (HashMapEntry<K, V>[]) EMPTY_TABLE; threshold = -1; // Forces first put invocation to replace EMPTY_TABLE } public HashMap(int capacity) { if (capacity < 0) { throw new IllegalArgumentException("Capacity: " + capacity); } if (capacity == 0) { @SuppressWarnings("unchecked") HashMapEntry<K, V>[] tab = (HashMapEntry<K, V>[]) EMPTY_TABLE; table = tab; threshold = -1; // Forces first put() to replace EMPTY_TABLE return; } if (capacity < MINIMUM_CAPACITY) { capacity = MINIMUM_CAPACITY; } else if (capacity > MAXIMUM_CAPACITY) { capacity = MAXIMUM_CAPACITY; } else { capacity = Collections.roundUpToPowerOfTwo(capacity); } makeTable(capacity); } public HashMap(int capacity, float loadFactor) { this(capacity); if (loadFactor <= 0 || Float.isNaN(loadFactor)) { throw new IllegalArgumentException("Load factor: " + loadFactor); } /* * Note that this implementation ignores loadFactor; it always uses * a load factor of 3/4. This simplifies the code and generally * improves performance. */ }
通过三个构造函数的源码,我们可以知道:
HashMap内部实际上使用HashMapEntry数组来实现的。
当调用new HashMap()时,会创建容量为2的HashMapEntry数组,并且threshold为-1。
当调用HashMap(int capacity)时,HashMap会将传入的capacity转换成最小的大于等于capacity的2的次方,比如:capacity=25,会转换成32。并且threshold为总容量的75%,threshold的作用是当entry的数量大于threshold时,进行扩容。
HashMap(int capacity, float loadFactory)实际上和HashMap(int capacity)是一样的,loadFactory参数未被使用(注意这是Android做的修改,实际上JDK中会使用这个参数)。
既然是HashMapEntry数组实现的,我们简单看下这个Entry什么样,
static class HashMapEntry<K, V> implements Entry<K, V> { final K key; V value; final int hash; HashMapEntry<K, V> next; HashMapEntry(K key, V value, int hash, HashMapEntry<K, V> next) { this.key = key; this.value = value; this.hash = hash; this.next = next; } }
这里注意关注next属性,有一定经验的朋友肯定知道,这是单向链表的实现,所以实现HashMap的数组的每一项其实是一个单向链表的Head,继续往下看,
接下来我们分析下put(K key, V value)方法,
void addNewEntryForNullKey(V value) { entryForNullKey = new HashMapEntry<K, V>(null, value, 0, null); } private V putValueForNullKey(V value) { HashMapEntry<K, V> entry = entryForNullKey; if (entry == null) { addNewEntryForNullKey(value); size++; modCount++; return null; } else { preModify(entry); V oldValue = entry.value; entry.value = value; return oldValue; } } private HashMapEntry<K, V>[] makeTable(int newCapacity) { @SuppressWarnings("unchecked") HashMapEntry<K, V>[] newTable = (HashMapEntry<K, V>[]) new HashMapEntry[newCapacity]; table = newTable; threshold = (newCapacity >> 1) + (newCapacity >> 2); // 3/4 capacity return newTable; } private HashMapEntry<K, V>[] doubleCapacity() { HashMapEntry<K, V>[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { return oldTable; } int newCapacity = oldCapacity * 2; HashMapEntry<K, V>[] newTable = makeTable(newCapacity); if (size == 0) { return newTable; } for (int j = 0; j < oldCapacity; j++) { /* * Rehash the bucket using the minimum number of field writes. * This is the most subtle and delicate code in the class. */ HashMapEntry<K, V> e = oldTable[j]; if (e == null) { continue; } int highBit = e.hash & oldCapacity; HashMapEntry<K, V> broken = null; newTable[j | highBit] = e; for (HashMapEntry<K, V> n = e.next; n != null; e = n, n = n.next) { int nextHighBit = n.hash & oldCapacity; if (nextHighBit != highBit) { if (broken == null) newTable[j | nextHighBit] = n; else broken.next = n; broken = e; highBit = nextHighBit; } } if (broken != null) broken.next = null; } return newTable; } @Override public V put(K key, V value) { if (key == null) { return putValueForNullKey(value); } int hash = Collections.secondaryHash(key); HashMapEntry<K, V>[] tab = table; int index = hash & (tab.length - 1); for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) { if (e.hash == hash && key.equals(e.key)) { preModify(e); V oldValue = e.value; e.value = value; return oldValue; } } // No entry for (non-null) key is present; create one modCount++; if (size++ > threshold) { tab = doubleCapacity(); index = hash & (tab.length - 1); } addNewEntry(key, value, hash, index); return null; } void addNewEntry(K key, V value, int hash, int index) { table[index] = new HashMapEntry<K, V>(key, value, hash, table[index]); }
从put(K key, V value)的源码我们可以得到如下信息:
当添加key为null的value时,会用单独的HashMapEntry entryForNullKey对象来储存。
entry数组的索引是通过hash算出来的:int index = hash & (tab.length - 1)。
当发生碰撞时(也就是算出的index上已经存在entry了),会首先检查是否是同一个hash和key,如果是则更新value,然后直接将old value返回。
新创建的entry会被设置成对应index上的链表Head。
当entry数量大于threshold(capacity的75%)时,对数组进行扩容,扩大为原来的2倍,并重新计算原数组中所有entry的index,然后复制到新数组中。
分析完put后,其他如get、remove、containsKey等接口就大同小异了,在此直接略过。
接下来我们看下Set<K> keySet()接口:
@Override public Set<K> keySet() { Set<K> ks = keySet; return (ks != null) ? ks : (keySet = new KeySet()); } private final class KeySet extends AbstractSet<K> { public Iterator<K> iterator() { return newKeyIterator(); } public int size() { return size; } public boolean isEmpty() { return size == 0; } public boolean contains(Object o) { return containsKey(o); } public boolean remove(Object o) { int oldSize = size; HashMap.this.remove(o); return size != oldSize; } public void clear() { HashMap.this.clear(); } } Iterator<K> newKeyIterator() { return new KeyIterator(); } private final class KeyIterator extends HashIterator implements Iterator<K> { public K next() { return nextEntry().key; } }
从源码中可以得出如下结论:
keySet返回的Set对象实际上和HashMap是强关联的,对Set接口的调用,实际上操作的还是HashMap。
Set中的iterator实际上也是实现自HashIterator。
entrySet()、valueSet()和keySet()的实现原理一样。
知道HashMap的实现原理后,我们就可以知道他的优缺点了:
优点:读写效率高,接近数组的索引方式。
缺陷:会占用大量的无效内存,为了减少碰撞,Entry数组的容量只能是2的N次幂,并且当entry数大于总容量的75%时就会扩容两倍。
如有问题,欢迎指出!
转载请注明出处。
相关文章推荐
- 如何为Android缓存数据到本地
- 解决Android TextView默认的padding问题
- Android进阶之Property Animator研究
- Android布局文件参数layout_alignTop的作用
- 加快Android编译速度
- Android模拟器修改hosts
- Android 监听ScrollView的滑动
- Android模拟器修改host
- (4.6.11.2)Android LayoutCast 初探
- android:ellipsize="end" + android:maxLines="" 失败的原因与解决方案
- Android绘制流程
- (4.6.11.1)加快Android编译速度:Buck,LayoutCast,JRebel,Jimulabs,Instant Run
- android 控件 下拉刷新 android-pulltorefresh
- 集成融云Android SDK实现在群聊/讨论组中@人的功能(三)
- Android Studio项目开发实用快捷键!!
- Android中关于保留小数点位数的处理
- Android学习路线总结,绝对干货
- Android学习路线总结,绝对干货
- Android SDK Manager更新报错——Download interrupted URL not found
- Android meta-data