------------------------------------------------------ 重要的数据结构 ------------------------------------------------------ // ******* 这个类是HashMap的一个内部类 // ******* 这个entry本质上就是一个单向链表 static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; // 正因为有这个成员变量,才使entry成为一个单向链表 final int hash; // 构造方法需要传入的参数有4个:hash值h、Key值k、Value值v、下一个entry节点n Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } public final K getKey() { return key; } public final V getValue() { return value; } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } // 判断两个entry是否相等,当它们的Key和Value都相等时返回true public final boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; Object k1 = getKey(); Object k2 = e.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = getValue(); Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; } return false; } public final int hashCode() { return (key==null ? 0 : key.hashCode()) ^ (value==null ? 0 : value.hashCode()); } public final String toString() { return getKey() + "=" + getValue(); } /** * This method is invoked whenever the value in an entry is * overwritten by an invocation of put(k,v) for a key k that's already * in the HashMap. */ void recordAccess(HashMap<K,V> m) { } /** * This method is invoked whenever the entry is * removed from the table. */ void recordRemoval(HashMap<K,V> m) { } } -------------------- // HashMap的结构(这里只写了它的属性,省略了它的方法) public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { // The default initial capacity - MUST be a power of two. // 默认的初始容量(entry数组的长度)是16,并且值必须是2的整数次幂 static final int DEFAULT_INITIAL_CAPACITY = 16; //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. // 最大的容量,当传入的容量过大时将使用这个值,并且必须是2的整数次幂且小于2的30次方 static final int MAXIMUM_CAPACITY = 1 << 30; The load factor used when none specified in constructor. // 默认的加载因子是0.75 static final float DEFAULT_LOAD_FACTOR = 0.75f; // The table, resized as necessary. Length MUST Always be a power of two. // (储存数据的)entry数组,长度必须是2的整数次幂 transient Entry[] table; // The number of key-value mappings contained in this map. // HashMap(即:entry数组)中已存在的键值对的数量 transient int size; // 阀值,当size的值大于或等于阀值时,需要调整HashMap的容量(threshold = capacity * load factor) int threshold; // The load factor for the hash table. // 加载因子的大小 // 说明:加载因子越大,对空间的利用越充分,但是查找的效率会降低(链表长度会越来越长); // 加载因子太小,那么表中的数据将过于稀疏(很多空间还没用,就开始扩容了),对空间造成严重浪费。 // 系统默认加载因子为0.75,这是一个比较理想的值,一般情况下我们是无需修改的。 final float loadFactor; // HashMap被改变的次数 transient int modCount; } ------------------------------------------------------ 存储对象相关的方法: ------------------------------------------------------ // 将key-value对添加到HashMap中,如果已存在,则新值替旧值,并返回旧值 public V put(K key, V value) { // 1 若key为null,则将该键值对添加到table[0]中。 if (key == null) return putForNullKey(value); // 2 若key不为null,则计算该key的哈希值,然后将key-value对添加到该哈希值对应的链表(Entry)中。 int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); // 2.1 若该key对应的键值对已经存在,则用新的value取代旧的value;返回了旧的value后,退出! 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; } } // 2.2若该key对应的键值对不存在,则将key-value对添加到table中 //(注:transient Entry[] table;// table是一个Entry数组,查看Entry的源码,会发现:Entry实质上是一个链表) modCount++; //将key-value添加到table[i]处,返回null后,退出。 addEntry(hash, key, value, i); return null; } -------------------- // putForNullKey()的作用是将key为null的键值对添加到table[0]位置 private V putForNullKey(V value) { // 若已经存在key为null的键值对,则替换并返回旧值 for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } // 如果不存在key为null的键值对,则直接添加到table[0]处! modCount++; addEntry(0, null, value, 0); return null; } -------------------- // 根据传入的hash值算出entry数组对应的下标,即:hash算法的实现 static int indexFor(int h, int length) { return h & (length-1); } 说明: 1, 除留余数法(取模法):用hash值除以关键字后得到的余数为哈希地址,即用hash值对关键字取模(MOD)的结果为哈希地址。 这是一种最简单,也是最常用的构造哈希函数的方法,Hashtable就是采用这种方法实现的(用hash值对entry数组的length取模),这种方法基本能保证元素在哈希表中散列的比较均匀,但取模会用到除法运算,效率很低。 除留余数法对关键字的选取很重要,由经验得知:一般情况下,选关键字为质数或不包含小于20的质因数的合数。 2, HashMap中则通过h&(length-1)的方法来代替取模,同样实现了均匀的散列,但效率要高很多。 哈希表的容量(entry数组的length)必须是2的整数次幂的原因: 首先,length为2的整数次幂的话,h&(length-1)就相当于对length取模,这样便保证了散列的均匀,同时也提升了效率; 其次,length为2的整数次幂的话,为偶数,这样length-1为奇数,奇数的最后一位是1,这样便保证了h&(length-1)的最后一位可能为0,也可能为1(这取决于hash值),即:与后的结果可能为偶数,也可能为奇数,这样便可以保证散列的均匀性。 如果length为奇数的话,很明显length-1为偶数,它的最后一位是0,这样h&(length-1)的最后一位肯定为0,即只能为偶数,这样任何hash值都只会被散列到数组的偶数下标位置上,这便浪费了近一半的空间。 -------------------- // 添加一个entry void addEntry(int hash, K key, V value, int bucketIndex) { // table[bucketIndex]即put(K key, V value)方法中的table[i],这里只是把下标的变量名由i换成bucketIndex罢了 // ******* 把已有的链表备份一下 Entry<K,V> e = table[bucketIndex]; // 把新添加进来的entry放到链表的头部,即:把旧的链表(Entry)赋给新添加进来的Entry的 Entry<K,V> next;字段 table[bucketIndex] = new Entry<>(hash, key, value, e); // 如果entry数组的实际大小(已放入entry的数量) 大于或等于阈值threshold,则 // 将会创建一个是原来entry数组(即table)大小两倍的entry数组,并将原来的entry对象放入新的entry数组中。 if (size++ >= threshold) resize(2 * table.length); } -------------------- // 重新调整entry数组的length,并将原来的entry对象放入新的entry数组中 void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } // 新建一个entry数组 Entry[] newTable = new Entry[newCapacity]; // 将原来的entry对象放入新的entry数组中 transfer(newTable); table = newTable; threshold = (int)(newCapacity * loadFactor); } 说明: 扩容是一个相当耗时的操作,因为它需要重新计算这些元素在新的数组中的位置并进行复制处理。 因此,我们在用HashMap的时,最好能提前预估下HashMap中元素的个数,这样有助于提高HashMap的性能。 -------------------- // 将原来的entry对象放入新的entry数组中 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); } } } ------------------------------------------------------ 获取对象相关的方法: ------------------------------------------------------ // 获取key对应的对象的值 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; // 判断key是否相等。注:如果hash值相同,就调用key.equals()方法比较 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } // 没有找的则返回null return null; } -------------------- // 获取key为null的对象的值 // HashMap将key为null的元素存储在table[0]位置,但不一定是该链表的第一个位置,因为有可能用其他key的hash值算出的bucket位置也为0! private V getForNullKey() { for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) return e.value; } return null; }
原文地址:http://blog.csdn.net/wodewutai17quiet/article/details/46044311