HashMap源代码阅读理解
前言:
这个实现跟数组和链表相关。应用了他们各自的特点,进行优化。
数组的优势是,方便查找,但是新增超过加载因子的阈值的话(这部分是hashmap初始化的时候赋值进去的,如果不赋值,则会给默认值),就需要重新分配。链表查找比较麻烦,顺子链子一直找,找到位置才截止,有多少就得找多少。
数组的时间复杂度为O(1),链表的时间复杂度为O(n)。因为数组是根据下标来找的,而数组的大小是确定的。而链表的大小是不确定的,是根据有多少元素进行多少次查找,找到为止。
于是就会有人说hash碰撞,什么是hash碰撞,根据key(这里的key是根据散列值计算出来的)找数组的时候,发现数组的下标已经有值了,有值的情况下,会走链表的对比。所以会有碰撞的情况下,时间复杂度为O(n)的说法。
文章比较枯燥,很多都是个人见解。如果哪里错了,望指出来共同进步。谢谢大家!
正文:
先看下构造函数部分,用来初始化的部分,其实初始化也很重要。容量和加载因子最好设置在合适的范围。过小的话,会不断的resize。过大的话又显得太过于浪费。因此根据需求来初始化一个合适的值,显得还是很重要的
无初始化参数
1 public HashMap() { 2 this.loadFactor = DEFAULT_LOAD_FACTOR; // 加载因子默认0.75f 3 }
根据m初始化
1 public HashMap(Map<? extends K, ? extends V> m) {//初始化并加入m的内容 2 this.loadFactor = DEFAULT_LOAD_FACTOR; // 加载因子默认0.75f 3 putMapEntries(m, false);//@1 4 }
1 public HashMap(int initialCapacity, float loadFactor) { //初始化hashmap的容量和加载因子 2 if (initialCapacity < 0) 3 throw new IllegalArgumentException("Illegal initial capacity: " + 4 initialCapacity); 5 if (initialCapacity > MAXIMUM_CAPACITY) 6 initialCapacity = MAXIMUM_CAPACITY; 7 if (loadFactor <= 0 || Float.isNaN(loadFactor)) 8 throw new IllegalArgumentException("Illegal load factor: " + 9 loadFactor); 10 this.loadFactor = loadFactor; 11 this.threshold = tableSizeFor(initialCapacity); //根据容量和加载因子得到阈值 @2 12 }
看完了初始化,再来看看put
1 public V put(K key, V value) { 2 return putVal(hash(key), key, value, false, true);//@3 @4 3 }
再来看看取值
1 public V get(Object key) { 2 Node<K,V> e; 3 return (e = getNode(hash(key), key)) == null ? null : e.value;//@5 4 }
--------------------------------------------------------------------------------方法说明--------------------------------------------------------------------------------------
1 /** 2 * @1把m赋值到初始化的hashMap里 3 */ 4 final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) { 5 int s = m.size(); 6 if (s > 0) { 7 if (table == null) { //初始化的时候,哈希桶是空的,所以走这段逻辑 8 float ft = ((float)s / loadFactor) + 1.0F; //如果我们传的m的size是6,6/0.75 + 1 = 9 为什么+1 我个人的理解就是先+1,看看是否超过了阈值,如果超过了阈值,就扩容,不加1的话,如果正好等于阈值就不会扩容。如果我们这一步不加1,那么tableSizeFor(8)求出来的就是8,那么正好等于阈值。但是没进行扩容。 9 int t = ((ft < (float)MAXIMUM_CAPACITY) ?(int)ft : MAXIMUM_CAPACITY);//MAXIMUM_CAPACITY是最大的容量,为2的30次方,因此这里的t就是9 10 if (t > threshold)//threshold是容量阈值,超过这个阈值后就会resize() 这个时候是0 11 threshold = tableSizeFor(t);//求出阈值@2 12 } 13 else if (s > threshold) //如果不是初始化的时候,比较size是否大于阈值,大于则重新分配 14 resize(); 15 for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { 16 K key = e.getKey(); 17 V value = e.getValue(); 18 putVal(hash(key), key, value, false, evict); 19 } 20 } 21 }
1 /**@2 2 * 返回大于输入参数且最近的2的整数次幂的数 3 * 这里网上查的都是大于,但是我发现,如果传入的是2的幂次方,那么就会得出2的幂次方这个数,比如8 16等,所以我个人的理解是大于等于,不知道是不是我哪里理解错了 4 */ 5 static final int tableSizeFor(int cap) { 6 int n = cap - 1;//9-1=8 防止9是2的幂次方的数,如果9是2的幂次方 则会 7 n |= n >>> 1;//无符号右移,无论正负,高位补0 0000 1000|=0000 0100=0000 1100=12 8 n |= n >>> 2;//0000 1100|=0000 0011=0000 1111=15 9 n |= n >>> 4;//0000 1111|=0000 0000=0000 1111=15 10 n |= n >>> 8;//0000 1111|=0000 0000 =0000 1111=15 11 n |= n >>> 16;//0000 1111|=0000 0000=0000 1111 =15 12 return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;//return 16 13 }
1 /** 2 *@3 3 *java里的hashMap用的是数组和链表的结合,数组寻址比较容易(下标易找),但是插入和删除不方便(需要重新排列)。链表查找不方便(需要顺着链子找),但是插入和删除比较方便(把next和pre指针变动下就可以了) 4 *数组中存着指针,指向链表中的元素(哈希桶),而我们hash函数就负责定位到下标。 5 *从这里我们就可以看出 如果我们没有遇到哈希碰撞,那么时间复杂度就是O(1) 如果遇到碰撞了,时间复杂度就是O(n) 6 */ 7 static final int hash(Object key) {//hash叫做散列,就是把任意长度的输入,通过散列算法,变成固定长度的输出(散列值) 8 int h;// 散列会有碰撞,比如我现在只有0和1两个数字,要变成1位,那么输入的组合为00 01 10 11 输出的组合为0 1,所以不同的对象就会有相同的散列。这个时候同性相斥,就打起来了 9 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//返回散列值 10 }
1 //**@4 2 * Implements Map.put and related methods 3 * 4 * @param hash 散列值 5 * @param key hashMap的key 6 * @param value 需要put的hashMap的value 7 * @param onlyIfAbsent true存在key的话,就忽略不覆盖,false,相同key,覆盖原来值 8 * @param evict if false, the table is in creation mode. 9 * @return previous value, or null if none 10 */ 11 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 12 boolean evict) { 13 Node<K,V>[] tab; Node<K,V> p; int n, i; 14 if ((tab = table) == null || (n = tab.length) == 0)//如果哈希桶是空的,分配空间 15 n = (tab = resize()).length;//重新分配后的length 16 if ((p = tab[i = (n - 1) & hash]) == null)//根据最大的索引值和hash来求得下标,看是否会有碰撞 17 tab[i] = newNode(hash, key, value, null);//没有碰撞,把值放进去 18 else {//如果发生了碰撞 19 Node<K,V> e; K k; 20 if (p.hash == hash && 21 ((k = p.key) == key || (key != null && key.equals(k))))//如果跟放在链表第一个的对象相同的key和相同的value,则赋值给e 22 e = p; 23 else if (p instanceof TreeNode)//如果值是treenode类型,则走treenode的puttreevalue方法 24 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); 25 else { 26 for (int binCount = 0; ; ++binCount) { 27 if ((e = p.next) == null) {//走链表,看看下个元素是否为空,为空则放到下个元素里面去,注意,走到最后e是空的 28 p.next = newNode(hash, key, value, null); 29 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 30 treeifyBin(tab, hash);//如果大于8,就从链表转变为红黑二叉树 31 break; 32 } 33 if (e.hash == hash && 34 ((k = e.key) == key || (key != null && key.equals(k))))//如果遇到相同的key和value(代表放进去了)则break 35 break; 36 p = e;//走链表,一个个顺下去 37 } 38 } 39 if (e != null) { //这个key相应的value已经存在情况下,上面if else第一种情况 40 V oldValue = e.value; 41 if (!onlyIfAbsent || oldValue == null)//如果遇到相同的值,是否替换原值 42 e.value = value; 43 afterNodeAccess(e);//把e移到链表的最后一个 44 return oldValue; 45 } 46 } 47 ++modCount; 48 if (++size > threshold) 49 resize(); 50 afterNodeInsertion(evict); 51 return null; 52 }}
1 /**@5 2 * Implements Map.get and related methods 3 * 4 * @param hash hash for key 5 * @param key the key 6 * @return the node, or null if none 7 */ 8 final Node<K,V> getNode(int hash, Object key) { 9 Node<K,V>[] tab; Node<K,V> first, e; int n; K k; 10 if ((tab = table) != null && (n = tab.length) > 0 && 11 (first = tab[(n - 1) & hash]) != null) {//我们之前putvalue的方法讲过的,(n-1)&hash是用来计算hash桶的下标的,找到下标 12 if (first.hash == hash && 13 ((k = first.key) == key || (key != null && key.equals(k))))//这个putvalue也有相应的方法和解释 14 return first;//返回 15 if ((e = first.next) != null) {//查看下一个 16 if (first instanceof TreeNode)//如果不是linkedHashMap,而是TreeNode, 17 return ((TreeNode<K,V>)first).getTreeNode(hash, key);//返回TreddNode的计算方式的值 18 do {//顺着链子找到相应的对象,返回 19 if (e.hash == hash && 20 ((k = e.key) == key || (key != null && key.equals(k)))) 21 return e; 22 } while ((e = e.next) != null); 23 } 24 } 25 return null; 26 }