HashMap源码----JDK1.8

HashMap源码----JDK1.8

类注释:

  1. 基于hash表结构实现,hash表结构: 大小为16的数组内元素为一条链表

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YhcP22gb-1629270439131)(C:\Users\wty20200117\AppData\Roaming\Typora\typora-user-images\image-20210816172444665.png)]

  2. 可以放入空值,还可以放入空键(之前还以为只能是空值),空键怎么得到hoshcode值?(在hash方法中进行了key为null的处理,返回值为0) 放到hash表的哪里?放到数组索引为0的地方 第一个桶

    Hashtable这个是一个线程安全的HashMap,现在不常用或者不用,不允许空值,更多的使用JUC包下的CurrentHashMap,或者Collections.synchronized在每个方法上面加上锁。

  3. 不保证映射的顺序?

  4. 初始容量和负载因子(默认0.75),容量是哈希表中的桶数,初始容量就是哈希表创建时的容量。负载因子是衡量哈希表在其容量自动增加之前允许达到多满的指标。当哈希表中的条目数超过负载因子和当前容量的乘积时,重新哈希表(即重建内部数据结构),使哈希表具有大约两倍的桶数。

  5. 快速失败Fail-fast,一般是用于检测并发修改,迭代器可以快速失败。因为HashMap是非线程安全的,也就是说在多线程情况下,未来会不确定的发生失败。快速失败让你不用担心这个风险。

结构

  1. 继承关系

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NV9kBV2v-1629270439135)(C:\Users\wty20200117\AppData\Roaming\Typora\typora-user-images\image-20210816175114879.png)]

    AbstractMap:此类提供Map接口的骨架实现,以最大限度地减少实现此接口所需的工作(人家的描述就是清晰)

    Map接口,规定将键映射到值。

    Cloneable,是一个空的接口,只有实现了该接口的类调用Object 的 clone 方法不会抛出异常CloneNotSupportedException

    Serializable,空接口,标志此类可以序列化

成员变量

序列化用的,ArrayList,LinkedList也有,但是值不一样,treeMap值也不一样

serialVersionUID 是 Java 为每个序列化类产生的版本标识,可用来保证在反序列时,发送方发送的和接受方接收的是可兼容的对象

private static final long serialVersionUID = 362498820763181265L;

默认初始HashMap容量,必须为2的幂?为什么? 值为1<<4 二进制左移4位 16 也就是说元素个数大于12(16 * 0.75)时会扩容

目的:保证计算槽点位置tab[i = (n - 1) & hash] 的结果在 [0,Cap-1] 之间

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

最大容量,在两个带参数的构造函数隐式指定更高值时使用(?)。 必须是 2 的幂 <= 1<<30

static final int MAXIMUM_CAPACITY = 1 << 30;

默认负载因子

static final float DEFAULT_LOAD_FACTOR = 0.75f;

JDk1.8在这里与JDK1.7有区别,就是当一条链表上的结点数>=8时会转为红黑树【此时不算当前插入的节点数】

static final int TREEIFY_THRESHOLD = 8;
//链表结点树,从0开始计数,到>=7 也就是说结点数为8个和8个之上就会转红黑树,有前提

当一个桶内元素<=6时,会由红黑树转为链表

static final int UNTREEIFY_THRESHOLD = 6;

只有桶的总数>=64时才会有转红黑树。【此时不算当前插入的节点数】

static final int MIN_TREEIFY_CAPACITY = 64;

存放数据的数组,长度必须为2的幂

transient Node<K,V>[] table;

元素个数,整个HashMap元素个数,具体哪个桶内元素个数是由binCount作为局部变量计数

transient int size;

用于fail-fast

transient int modCount;

阈值,如果表数组尚未分配,此字段保存初始数组容量,或零表示 DEFAULT_INITIAL_CAPACITY

  1. 在resize方法中用于初始化数组容量
  2. 之后就是阈值,容量*负载因子
int threshold;

哈希表的负载因子

final float loadFactor;

链表结点

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;//当前key进过HashCode之后的哈希值,用于决定数据放到哪个桶
    final K key;//自定义类需要重写equals和hashcode方法,可以为null值
    V value;//存放的数据,可null
    Node<K,V> next;//链表下一个结点

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

    public final K getKey()        { return key; }
    public final V getValue()      { return value; }
    public final String toString() { return key + "=" + value; }

    public final int hashCode() {
        return Objects.hashCode(key) ^ Objects.hashCode(value);
    }

    public final V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public final boolean equals(Object o) {
        if (o == this)
            return true;
        if (o instanceof Map.Entry) {
            Map.Entry<?,?> e = (Map.Entry<?,?>)o;
            if (Objects.equals(key, e.getKey()) &&
                Objects.equals(value, e.getValue()))
                return true;
        }
        return false;
    }
}

红黑树结点

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;
    TreeNode(int hash, K key, V val, Node<K,V> next) {
        super(hash, key, val, next);
    }

    /**
     * Returns root of tree containing this node.
     */
    final TreeNode<K,V> root() {
        for (TreeNode<K,V> r = this, p;;) {
            if ((p = r.parent) == null)
                return r;
            r = p;
        }
    }

构造方法

//我最常用的,全部都是默认,默认大小16,负载因子0.75
public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;//0.75f // all other fields defaulted 
 }
//指定大小
public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//指定大小和负载因子
public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)//严谨
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);//阈值,初始化必须为2的幂,扩容也是必须容量为2的幂
}
  //返回给定目标容量的最近二次幂,记得Leekcode有道题是这个算法来着。
  //比如cap=3  a+=b 等于 a=a+b a|=b 等于 a=a|b a等于a或b,二进制或运算
  //对于位运算,可参考学习https://blog.csdn.net/javazejian/article/details/51181320
  //System.out.println(-4>>1);-2 有符号右移  
  static final int tableSizeFor(int cap) {// cap=3  
        int n = cap - 1;//n = 2  
        n |= n >>> 1;// n = 10 | 01 11 3 
        n |= n >>> 2;// n = 11 | 00 11 3
        n |= n >>> 4;// n = 11 | 00 11 3
        n |= n >>> 8;// n = 11 | 00 11 3
        n |= n >>> 16;// n = 11 | 00 11 3
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;// n=4
    }

 public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
 }
## 方法

### hash方法

根据key生成一个hashcode值来决定将数据放入哪个桶中

(h = key.hashCode()):说明自定义类必须实现hashcode方法,object中hashcode方法是空的。

将hashCode值再右移16位是为了让算出来的hash更分散,以减少哈希冲突

key为null时 返回hashcode值为0 

得到hash值后怎么计算槽点,(length-1) & hash = hash % (length

//hash值换算桶下标 
//假设key是Integer值,则hashcode返回hash值就是int值 当前只有一个值 get(4)
计算 (length-1) & hash = (1-1) & 4 = 0  桶下标为0
计算 hash%(length-1) 4 % 0 = 0  桶下标为0  
//上面是一种错误的计算
只有一个值那么数组长度length也不会是1 无参构造出的对象length为16 指定则为最接近的2次幂 
计算 15 & 4 01111 & 00100 = 4
计算 4  % 15  = 4

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//比如说String类中hashCode方法
 public int hashCode() {
        int h = hash;//默认hash值为0
        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;
 }

从无参构造器开始

//当我们调用HashMap map = new HashMap()时候并未完成初始化,当第一次put时才进行的初始化
public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;//0.75f // all other fields defaulted 
}
 
//1.无参构造出的对象第一次put值时执行初始化操作  resize方法帮助我们进行第一次初始化
	ode<K,V>[] oldTab = table; oldTab = null
	int oldCap = 0;
	int oldThr = 0;
	newCap = DEFAULT_INITIAL_CAPACITY; = 初始化为默认容量16
	newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); = 12 0.75 * 16 
    threshold = 12 阈值超过则扩容
	Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 初始化
    table = newTab; 给到类属性
    返回大小为16的数组

常用方法

get方法

  1. hashmap中有值为null则返回null
  2. 如果通过k找不到元素则返回null
public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}
//hash值传进来是为了找到是哪个桶,key是为了找到桶内的具体位置
final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
	//1.判断table不为null,且长度大于0。2.经过hash索引换算出来的下标对应的桶不为null
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        //当到了这里 first已经取出了桶中的第一个结点,判断下要取的是不是就是这个
        //1.first的key的hash值和当前hash值相等
        //2.first结点的key和当前key相等(可以取出值为null) 或者 值不为null时使用equals进行判断
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first; //如果上面的条件都符合则直接返回first 没有hash冲突就只执行到这里了相当于数组取值
        if ((e = first.next) != null) {//桶内第一个值不为null,且桶内还有其它结点
            if (first instanceof TreeNode)//如果是红黑树结点
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);//逻辑复杂了再封装个方法
            do {
                //如果是链表则一个一个找,找到则返回结点,找不到返回null
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    //找不到返回
    return null;
}

put方法

//放入当前键值对,如果之前有和key关联的值则替换,并返回之前值,如果没有返回null
public V put(K key, V value) {
    // int hash, K key, V value, boolean onlyIfAbsent,boolean evict
    // onlyIfAbsent=false,默认会执行覆盖操作,会更改现有值,如果旧值是null则必然会替换         
    return putVal(hash(key), key, value, false, true);
}
//简略:resize()方法是初始化或者加倍表大小
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;//p就是一个指针 用来遍历链表
    if ((tab = table) == null || (n = tab.length) == 0) //表示是第一次put值
        n = (tab = resize()).length; //进行初始化,如果是无参构造new出的对象,则返回默认大小为16的数组
    //无hash冲突,相当于数组放值
    if ((p = tab[i = (n - 1) & hash]) == null) //如果经过hash计算出的下标对应的桶为null,则直接加入进去
        tab[i] = newNode(hash, key, value, null); 
    else {
        Node<K,V> e; K k;//e就是一个临时存放指针用来存储p.next 
        //如果桶内第一个结点和当前key关联则替换掉第一个结点
        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);
        else {
            //hash冲突了,刚开始往链表添加元素
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {//如果进入此if则说明e为null(也就是说是链表的末尾)
                    //放到最后一个结点后面,尾插
                    p.next = newNode(hash, key, value, null);
                    //链表结点数>=8时转为红黑树
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                //如果遍历中途有和新增结点一样的则break
                //这个时候不执行p=e 则不会往后移动,每次for进来都会进入这个if条件然后break
                //这样可以保证e就是我们想要修改的结点,且binCount统计的也是当前链表结点数
                if (e.hash == hash && 
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key 不等于null则说明当前key之前就有了
            V oldValue = e.value;//用于返回当前key对应映射的旧value
            if (!onlyIfAbsent || oldValue == null)//put()调用 onlyIfAbsent为false 则默认会替换
                e.value = value;
            afterNodeAccess(e);//对于hashmap是个空方法,用于linkedHashMap的LRU策略,最近使用的Node,放在链表的最末尾
            //LRU 最近最少使用
            return oldValue;
        }
    }
    ++modCount;//记录hashmap的修改
    if (++size > threshold) //如果增加当前元素后总元素个数大于阈值,也就是说默认16时大于12 就是13就会扩容
        resize();
    afterNodeInsertion(evict);//在节点插入之后做些什么,HashMap此方法为空
    return null;
}

resize方法,初始化&扩容方法

//1.无参构造出的对象第一次put值时执行初始化操作
	Node<K,V>[] oldTab = table; oldTab = null
	int oldCap = 0;
	int oldThr = 0;
	newCap = DEFAULT_INITIAL_CAPACITY; = 初始化为默认容量16
	newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); = 12 0.75 * 16 
    threshold = 12 阈值超过则扩容
	Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; 初始化
    table = newTab; 给到类属性
    返回大小为16的数组
//2.假设现在要扩容,指定容量4,现在加第4个
    Node<K,V>[] oldTab = table;//现在oldTab应该有4个值了
	int oldCap = (oldTab == null) ? 0 : oldTab.length;//4
	int oldThr = threshold;//3
	newCap = oldCap << 1 //容量扩大2倍
    float ft = (float)newCap * loadFactor;//6
	newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);  //6 保证不会超出最大值
	threshold = newThr;//重新设置阈值
	Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
	table = newTab;//要返回table
	//接下来就是怎么扩容了 新的数组和旧的数组,数据重新散列
final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    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<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    //扩容策略
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {//遍历全部桶
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {//判断旧桶中有值则重新散列到新桶
                oldTab[j] = null;//释放内存,GC回收
                if (e.next == null)//旧桶没有发生散列冲突,就一个值
                    newTab[e.hash & (newCap - 1)] = e;//直接给到新桶
                else if (e instanceof TreeNode)//桶内是红黑树又是另外处理方式
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    Node<K,V> loHead = null, loTail = null;//桶下标不变
                    Node<K,V> hiHead = null, hiTail = null;//桶下标改变
                    //扩容前的下标计算是 hash & (len-1) || hash % length
                    //扩容后 hash & (newLen - 1) 就是 hash & (2*len-1)
                    //这种情况下,举例
                    //旧容量为4 扩容后为8
                    //之前的put(1,1) 和 put(5,1)是都在下标为1的位置
                    //现在重新计算下标就是put(1,1)还是下标1桶,但put(5,1)就成了下标5桶
                    //这个下标5 = 旧下标1 + 旧容量4 
                    //就是newTab[j + oldCap] = hiHead;  
                   
                    Node<K,V> next;
                    do {
                        
                        next = e.next;
                        //put(1,1) 001 & 100 == 0 是旧下标不需要改变下标
                        //put(4,1) 100 & 100 != 0 是需要改变桶的元素
                       	
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

treeifyBin方法,链表转红黑树&向树添加元素

链表一般不会轻易转化成红黑树,看源码是有一段描述。默认8是通过泊松分布算出来的,有8个元素的概率是0.00000006

final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)//如果hashmap中数据数量不够64只会扩容
        resize();
    else if ((e = tab[index = (n - 1) & hash]) != null) {//当前桶内必须得有元素
        TreeNode<K,V> hd = null, tl = null;
        do {
            TreeNode<K,V> p = replacementTreeNode(e, null);
            if (tl == null)
                hd = p;
            else {
                p.prev = tl;
                tl.next = p;
            }
            tl = p;
        } while ((e = e.next) != null);
        if ((tab[index] = hd) != null)
            hd.treeify(tab);
    }
}

remove方法

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;
    //和get 方法一样的开头
    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;
        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 {
                do {//遍布链表
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    p = e;
                } while ((e = e.next) != null);
            }
        }
        //要删了	
        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);
            else if (node == p)//是第一个结点,就让头结点为下一个结点
                tab[index] = node.next;
            else
                p.next = node.next;//不是也是让跳过当前结点
            ++modCount;
            --size;
            afterNodeRemoval(node);
            return node;
        }
    }
    return null;
}

遍历hashMap

有可以遍历key的keySet(),有values.还有entrySet()返回键值对

public Set<K> keySet() {
    Set<K> ks = keySet;
    if (ks == null) {
        ks = new KeySet();
        keySet = ks;
    }
    return ks;
}
public Collection<V> values() {
        Collection<V> vs = values;
        if (vs == null) {
            vs = new Values();
            values = vs;
        }
        return vs;
}
 public Set<Map.Entry<K,V>> entrySet() {
        Set<Map.Entry<K,V>> es;
        return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
    }
final class KeySet extends AbstractSet<K>
final class Values extends AbstractCollection<V> 
final class EntrySet extends AbstractSet<Map.Entry<K,V>>
    
final class KeyIterator extends HashIterator
    implements Iterator<K> {
    public final K next() { return nextNode().key; }
}

final class ValueIterator extends HashIterator
    implements Iterator<V> {
    public final V next() { return nextNode().value; }
}

final class EntryIterator extends HashIterator
    implements Iterator<Map.Entry<K,V>> {
    public final Map.Entry<K,V> next() { return nextNode(); }
}
abstract class HashIterator {
    Node<K,V> next;        // next entry to return
    Node<K,V> current;     // current entry
    int expectedModCount;  // for fast-fail
    int index;             // current slot

    HashIterator() {
        expectedModCount = modCount;
        Node<K,V>[] t = table;
        current = next = null;
        index = 0;
        if (t != null && size > 0) { // advance to first entry
            do {} while (index < t.length && (next = t[index++]) == null);
        }
    }

    public final boolean hasNext() {
        return next != null;
    }

    final Node<K,V> nextNode() {
        Node<K,V>[] t;
        Node<K,V> e = next;
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        if (e == null)
            throw new NoSuchElementException();
        if ((next = (current = e).next) == null && (t = table) != null) {
            do {} while (index < t.length && (next = t[index++]) == null);
        }
        return e;
    }

使用

Iterator iterator = map.keySet().iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());
}

//1.返回一个ks
public Set<K> keySet() {
        Set<K> ks = keySet; //多态的体现
        if (ks == null) {
            ks = new KeySet();
            keySet = ks;
        }
        return ks;
}
//2.调用这个内部类的方法
 public final Iterator<K> iterator()     { return new KeyIterator(); }
//3.继承了HashIterator 当前方法只有着遍历当前map的key的next()方法
 final class KeyIterator extends HashIterator
        implements Iterator<K> {
        public final K next() { return nextNode().key; }//nextNode()方法
}
//4.走了这么多,我们都没接触到元素在哪里 我们哪什么去遍历
//元素在父类的构造方法中被初始化
 HashIterator() {
            expectedModCount = modCount;
            Node<K,V>[] t = table;//获取数据
            current = next = null;
            index = 0;
            if (t != null && size > 0) { // advance to first entry
                do {} while (index < t.length && (next = t[index++]) == null);//找到第一个不为null的桶
            }
 }
//遍历在这里,获取当前node的下一个结点
  final Node<K,V> nextNode() {
            Node<K,V>[] t;
            Node<K,V> e = next;//第一次调用该方法则从第一个不为null的桶开始
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
      		//每次调用该方法都会进行判断
      		//(next = (current = e).next) 还在移动结点,保证每次返回的e都是下一个结点
      		//如果下一个结点为null&&table!=null,顺带又重新初始化了t
            if ((next = (current = e).next) == null && (t = table) != null) {
                //换桶,找个不为null的桶.和构造器中一样但是index值会改变这就可以找到之后不为null的桶
                do {} while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }

之前的描述有问题,不是总的元素个数>64 而是桶的总树>64
static final int MIN_TREEIFY_CAPACITY = 64;


修改下,hashmap 链表转红黑树,是第9个元素的时候转。也就是不算当前节点,此刻的结点数>=8,源码的bincount = 0, bincount >= 7

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值