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 }

 

posted @ 2019-01-20 20:56  愚智  阅读(268)  评论(0编辑  收藏  举报