标签:style blog class c code java
JAVA源码解读---HashMap目录扩展的奥秘
摘要:为了探索JAVA1.7源码中HashMap类数据的组织方法与目录扩展方法,本文通过对JAVA1.7源码中HashMap类源码的阅读与分析,得出结论:hashmap中存储数据的数据结构采用的是链表数组,目录是个数组,数组的成员是链表。冲突解决方法:典型的链地址法,冲突后,在链表头部插入数据。目录扩展方法:已二倍的方式扩展,一直到目录的最大上限。目录扩展的触发条件:装载因子的方式触发。从java中hashmap的实现可以看出,桶数据的组织方式并不是一种非常高效的方式。对检索效率不利。同时,数据扩展简单的采用二倍的扩展方法,也只是使用了最为粗暴的扩展方式,扩展开销较大。
关键字:JAVA,HashMap,目录组织方式,目录扩展方法,目录触发条件
本文转自http://blog.csdn.net/daliaojie/article/details/26236979
散列是一种非常重要的数据结构,在JAVA与dotNet中都有相对应事先的类供调用。我们知道hashmap的容量是动态增长的,此篇博客分析了java中,hashmap中关于目录扩展的过程。
先看hashmap的成员变量:
static final int DEFAULT_INITIAL_CAPACITY = 16; static final int MAXIMUM_CAPACITY = 1 << 30; static final float DEFAULT_LOAD_FACTOR = 0.75f; transient Entry[] table; transient int size; int threshold; final float loadFactor; transient int modCount;
transient Entry[] table;
transient int size; int threshold; final float loadFactor;
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; final int hash;
直接看insert过程吧。
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); 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; } } modCount++; addEntry(hash, key, value, i); return null; }
put操作的过程:
将key进行散列并计算出目录中对应的位置。从而获取该位置处的链表。并对该链表进行遍历,遍历时,检查是否该key已经存在,如果存在就用将新的value替换掉旧的value。
如果不存在。就调用
addEntry(hash, key, value, i);进行插入。并且是在改目录位置处插入。
我们看插入操作做了些什么:
void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<>(hash, key, value, e); if (size++ >= threshold) resize(2 * table.length); }
方法的刚开始,获取了链表。
并头早了一个新的Entry,我们看对应的构造方法:
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; final int hash; /** * Creates new entry. */ Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; }
table[bucketIndex] = new Entry<>(hash, key, value, e);
我们继续往后看。
addEntry(int hash, K key, V value, int bucketIndex)
resize(2 * table.length);操作。我们看它对应的方法。
void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable); table = newTable; threshold = (int)(newCapacity * loadFactor); }
resize方法里,先对新的目录长度进行检测,以防止,超过目录的最大长度。
然后new了一个新长度的目录。
然后执行
transfer(newTable);源码如下:
/** * Transfers all entries from current table to newTable. */ void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; for (int j = 0; j < src.length; j++) { Entry<K,V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K,V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } } }
解读一下吧。
for()是对原目录的遍历控制。
然后do while是对目录位置处的链表进行遍历时的控制。
目录扩展后,便将就目录更换为新的目录。并更新了目录扩展的阀值。
我们看一下map的查询操作:
public V get(Object key) { if (key == null) return getForNullKey(); int hash = hash(key.hashCode()); for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } return null; }
做一下总结:
hashmap中存储数据的数据结构:链表数组。目录是个数组,数组的成员是链表。
冲突解决方法:典型的链地址法,冲突后,在链表头部插入数据。
目录扩展方法:已二倍的方式扩展,一直到目录的最大上限。
目录扩展的触发条件:装载因子的方式触发。
尾语:
从java中hashmap的实现可以看出,桶数据的组织方式并不是一种非常高效的方式。对检索效率不利。同时,数据扩展简单的采用二倍的扩展方法,也只是使用了最为粗暴的扩展方式。扩展开销较大。
JAVA源码解读---HashMap目录扩展的奥秘,布布扣,bubuko.com
标签:style blog class c code java
原文地址:http://blog.csdn.net/daliaojie/article/details/26236979