HashMap详解

概述

我们可以看的JDK源码有很多,最被人熟知的当然就包括HashMap了,我当初学的时候什么都不懂,就只有印象那就是很麻烦,麻烦到在网上随便搜罗点文章,总结,就算是我会了。所以我就认认真真花费了1天的时间对我们最最熟悉不过的HashMap做了一点点整理,希望能帮助到我彻底解决掉梦魇。

HashMap 详解

属性

在这里插入图片描述

  1. 默认初始化容量 16
  2. 默认的加载因子 0.75f
  3. entrySet Entry的节点
  4. 加载因子
  5. 容量的最大值 1<<30
  6. 链表树化的数组长度:长度64
  7. modCount 由于HashMap是线程不安全的类,所以在操作HashMap中的数据时,会记录这个修改的次数,当使用迭代器遍历HashMap中的数据时,先把这个值赋给迭代器的expectedModCount,迭代的过程中比较这两个值,如果不相等直接抛异常,也就是源码注释中写的fail-fast机制
  8. 序列化ID
  9. table 存储 Node节点的数组
  10. threshold 扩容的阈值 阈值又是容量和加载因子的乘积 没有赋值的默认值
  11. TREEIFY_THRESHOLD 默认是8 链表长度>8 会生成红黑树
  12. UNTREEIFY_THRESHOLD 默认是6 链表长度<6 会变回红黑树

13.14 这里只有entrySet 没有keySet 和 values 是AbstractMap的属性

static class Node<K,V> implements Map.Entry<K,V>

1 hash

public static int hash(Object... values) {
    return Arrays.hashCode(values);
}
-->
public static int hashCode(Object a[]) {
        if (a == null)
            return 0;

        int result = 1;

        for (Object element : a)
            result = 31 * result + (element == null ? 0 : element.hashCode());

        return result;
    }
类比String 和 Object 再看HashMap的 hashcode
String的hashCode
public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}
Object

实现方式略有不同,其实在idea实现hashcode时,也提供给了几种方式去实现
在这里插入图片描述

public native int hashCode();

2 clear

遍历删除Node节点内容

//transient Node<K,V>[] table;
public void clear() {
    Node<K,V>[] tab;
    modCount++;
    if ((tab = table) != null && size > 0) {
        size = 0;
        for (int i = 0; i < tab.length; ++i)
            tab[i] = null;
    }
}

3 capacity

capacity 是 default的类型 且是 HashMap 不能调用到的,原因是default

4 containsKey

public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}
 // key和 hash(key)
 final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        //判断数组不为空 长度不为0 找到数组位置
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            /*
            判断第一个节点hash等于传进来的hash(key) &&
            第一节点的key=传进来的key ||
            first是数组某个位置上的Node节点
            
            */ 
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                //树节点 就按照树去查找
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                /*如果不是树 ,遍历链表每个元素 有则返回 从第二个才开始遍历
                  e是first.next的节点 第二节点
                  判断 hash值相同 key相同或者key.equels相同
                */
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

5 get方法

同理getNode 返回节点后 取出 节点的value值

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

*6 put方法

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

public V putIfAbsent(K key, V value) {
        return putVal(hash(key), key, value, true, true);
    }
//putMapEntries 方法 readObject 方法都用到了putVal
// 还有一个putAll方法 调用了putMapEntries
 public void putAll(Map<? extends K, ? extends V> m) {
        putMapEntries(m, true);
    }
//上边是put的所有的方法

-->
// hash(key) key value 
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
    	// 拿到Node数组 如果数组为空 那么扩容
        if ((tab = table) == null || (n = tab.length) == 0)
            //扩容看resize() n为新扩容的大小
            n = (tab = resize()).length;
    	// 在新扩容(可能不需要扩容)的实际位置 如果数组位置为空 直接赋值
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {//如果已经存在值
            //对e做赋值的过程
            Node<K,V> e; K k;
            // e为Node hash相同 key也相同 就赋值 这里针对的是存在 数组位置上的
            //Node就和你插入的key相同 就赋值给e
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //树的处理
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            // p是什么 某个位置上的Node(已经存在) 而且是存在值的(可能next为空)
            else {
                //开始循环 循环的是链表
                for (int binCount = 0; ; ++binCount) {
                    //如果是链表末尾,也就是没有next链表
                    if ((e = p.next) == null) {
                        //在末尾创建新节点并且赋值
                        p.next = newNode(hash, key, value, null);
                        /*如果链表长度>=7 那么就创建treeifyBin 在treeifyBin中
                        if (tab == null || (n = tab.length) < 									MIN_TREEIFY_CAPACITY)
         				   resize();		
         				   在判断数组长度<64 时 只会扩容
                        */
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //在循环过程中找到了相同的key 和 hash
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    //循环过程中 直接覆盖掉p  p永远是e的next节点
                    p = e;
                }
            }
            // 对e 赋值完成  判断是否是覆盖操作 对接到上一个break;之前 如果是覆盖			 //的话
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                //判断accessOrder才会执行
                afterNodeAccess(e);
                //覆盖操作 返回老的值
                return oldValue;
            }
        }
    	//设置修改频次
        ++modCount;
    	//如果达到扩容的阈值 那么会扩容 上面的老值就肯定不需要扩容了
        if (++size > threshold)
            resize();
    	//判断evict才会执行
        afterNodeInsertion(evict);
        return null;
    }


eg:
 			Integer put = map.put(4, 1);
           System.out.println(put);
           Integer put1 = map.put(4, 2);
           System.out.println(put1);

null
1

*7 resize方法

default方法,同样是HashMap才能调用到

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    //int threshold;  扩容的阈值
    int oldThr = threshold;
    // 初始化新容量 和 新阈值
    int newCap, newThr = 0;
    // 如果目前的数组长度>0
    if (oldCap > 0) {
        //如果数组长度大于 最大容量 那么就返回原先的数组
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 如果新数组扩容1倍 仍然小于最大容量 &&
        // 老数组长度大于默认长度(一般不会有问题)
        // 修改新阈值
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    // 如果老阈值>0 也就是在oldCap<=0 时 会将新阈值赋值(设置过阈值)
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    // 如果阈值是默认的话 使用这条链路 新容量和 新阈值都是在resize中创建的
    // 剩余用到默认容量DEFAULT_LOAD_FACTOR 都是构造器中的赋值
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    
    
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    //设置新的阈值
    threshold = newThr;
    @SuppressWarnings({"rawtypes","unchecked"})
    	// 设置新的Node大小 为新的容器newCap 大小
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    //切换了newTab 给到了 Table 
    table = newTab;
    
    // 下面全是是判断以内的  老数组有值
    if (oldTab != null) {
        //遍历老节点数组
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            // 如果老节点数组的某个位置 不为空
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                //某个数组位置就1个节点
                if (e.next == null)
                    /*newTab是新的数组 还未存放 在扩容情况下,如果老数组只有1个节					点,那么新的数组 &(数组.length - 1) 就一定还是一个*/
                    newTab[e.hash & (newCap - 1)] = e;
                //树节点的处理办法
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                
                // 剩余的解决办法 条件前提:有链表,不是树,目前正在遍历数组的Node
                else { // preserve order
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    //循环! 遍历的是链表 里面是链表的每个元素
                    do { 
                        next = e.next;
                        /*判断扩容后的16->32 最高为 是否为1 ,如果是1 那么是新
                        数组的位置,比如之前是数组5的位置(默认长度是16)
                        0101  -   00101    10101   
                        5		  5		   21 
                        加上扩容的大小就是新的链表通过hash(key)可能存放的位置
                        
                        */ 
                        if ((e.hash & oldCap) == 0) {
                            //尾节点为空 那么就证明从来没存放过 就将loHead赋值
                            if (loTail == null)
                                loHead = e;
                            /* 如果尾节点有值,那么此时有头节点,尾节点也被赋值,
                            尾节点的next就赋值此值 添加链表内容(也证明了链表是无
                            序的)
                            */
                            else
                                loTail.next = e;
                            /*尾节点必赋值,在第一次时,尾节点和头节点是相同的,
                            第二次以后尾节点就不断的变化 是链表的末尾
                            */
                            loTail = e;
                            /* 之后的循环loHead 就不进去了,先赋值循环出来的节点							给尾节点,重新赋值尾节点 loTail 此时头节点loHead是可						  以遍历到这个单链表的 ioTail不行  */
                        }
                        //对称和上述 赋值内容不同
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    //结论就是 链表内的值  16 - 32
                    16-1=  1111
                    32-1=  11111
                    /** 在4位&的情况下 和 5位& 只会重新分配给2个数组位置(原数组					位置和新的数组位置)
                    */
                    
                    //else的结尾 循环结束
                    // 放入了2个位置 放入的是head(完整的链表)
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

*8 remove iterator

private class Itr implements Iterator<E> 
public V remove(Object key) {
    Node<K,V> e;
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
        null : e.value;
}
-->
   
final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node<K,V>[] tab; Node<K,V> p; int n, index;
    	//删除的前提 有值 并且 对应的数组位置也有值
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
            Node<K,V> node = null, e; K k; V v;
            //如果数组上的Node节点是要删除的节点,那么就赋值node=p
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            //如果不是数组上的节点是删除对象,那么查找链表
            else if ((e = p.next) != null) {
                //是否为树
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
                    //非树的话,遍历链表 判断key和hash 赋值node
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        //p是链表的上一个e
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            //节点Node已经赋值 已经找到需要删除的Node
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                //树的删除
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                // 如果是node==p 那么是数组位置的Node 需要重新指向next的Node保证
                else if (node == p)
                    tab[index] = node.next;
                //如果是通过链表找到的Node 这个节点的下一个节点指向 上一个节点p的
                //下一个节点
                else
                    p.next = node.next;
                //修改操作频次  减少值的大小 返回节点
                ++modCount;
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

ConcurrentHashMap详解

put 方法

public V put(K key, V value) {
    return putVal(key, value, false);
}

/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    //取hash值
    int hash = spread(key.hashCode());
    int binCount = 0;
    //取Node数组到tab
    // Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; 修改sizeCtl
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0)
            //没有设置才需要初始化大小
            tab = initTable();
        //获取Node节点 
        // 传入tabAt Node数组 和 写入的数组位置 cas(Unsafe)的方式获取对应的Node
        //如果数组位置没有值进入
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            //U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
            // 比较并且交换hash算出来的某个数组位置的 Node
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }
        //ForwardingNode 特殊需求的Node  
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            //锁的对象是确定的某个Node节点
            /*根据上述判断条件,确定这个位置
            1 取hash  2如有必要设置初始化大小 3 Node数组位置为空直接复制
            4 MOVED  5 然后对具体的Node加锁(也像极了分段锁)
            5 做了覆盖 创建 等操作            
            */
            synchronized (f) { 
                //取出来某个位置的Node  和上次取出来判空的含义相同 像双端检索机制
                if (tabAt(tab, i) == f) {
                    //判断Node节点在数组上的那个Node 的hash值
                    if (fh >= 0) {
                        binCount = 1;
                        //遍历节点
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            //hash值相同 覆盖value 退出
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            //如果不相同 那么 取下一个节点,直到链表的最后一个
                            //节点 然后结束
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
                    // fh <0  并且是 树
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                       value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            } // 退出同步代码块
            //遍历算出来的是 链表的长度  是否生成链表 ,如果是覆盖 会返回旧值
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    //对于参数的设置 返回旧值就没有了
    addCount(1L, binCount);
    return null;
}


private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
    	//循环 条件是等待初始化  table没有内容
        while ((tab = table) == null || tab.length == 0) {
            //sc为-1 让出线程 线程会让出CPU执行权,让自己或者其它的线程运行
            if ((sc = sizeCtl) < 0)
                Thread.yield(); // lost initialization race; just spin
            // 将sc 置为-1 然后开始初始化,
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    if ((tab = table) == null || tab.length == 0) {
                        //确定初始化大小 设置了sizeCtl就用这个 
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = tab = nt;
                        sc = n - (n >>> 2);
                    }
                } finally {
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

ConcurrentHashMap概念

table:默认为null,初始化发生在第一次插入操作,默认大小为16的数组,用来存储Node节点数据,扩容时大小总是2的幂次方。
nextTable:默认为null,扩容时新生成的数组,其大小为原数组的两倍。
sizeCtl :默认为0,用来控制table的初始化和扩容操作,具体应用在后续会体现出来。

-1 代表table正在初始化
-N 表示有N-1个线程正在进行扩容操作
其余情况:
1、如果table未初始化,表示table需要初始化的大小。
2、如果table初始化完成,表示table的容量,默认是table大小的0.75倍,居然用这个公式算0.75(n - (n >>> 2))。

Node:保存key,value及key的hash值的数据结构。

ForwardingNode MOVED

final class ForwardingNode<K,V> extends Node<K,V> {
  final Node<K,V>[] nextTable;
  ForwardingNode(Node<K,V>[] tab) {
      super(MOVED, null, null, null);
      this.nextTable = tab;
  }
}

只有table发生扩容的时候,ForwardingNode才会发挥作用,作为一个占位符放在table中表示当前节点为null或则已经被移动

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值