HashMap jdk1.8源码阅读

1、成员变量

     /**
     * 默认初始化容量,必须是2的倍数
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * 最大容量,如果任意一个带有参数的构造函数隐式指定更高的值,则使用该最大容量。
     * 必须是小于等于1<<30
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 默认负载因子为0.75
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * 树化阈值。链表长度大于等于8,并且数组长度大于64才会树化。
     * shrinkage.
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * 树退化为链表的阈值。当桶中树元素小于等于6时,树退化为链表
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * 最小树化容量。hashmap桶中链表长度大于8,整个hash表中键值对的个数小于64,仅仅进行扩容。
     * 因为这个时候扩容效率还是高于转化为红黑树的。
     * 只有当键值对个数大于64时,进行红黑树化。
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

2、构造函数


/**
     * 构造一个空HashMap,默认初始化容量和默认加载因子。
     * 默认初始化容量为16,默认加载因子为0.75
     */
    public HashMap() {
        //赋值默认加载因子,初始化容量在首次put扩容的时候设置。
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
/**
     * 构造一个指定初始化容量的空HashMap
     * 加载因子为默认加载因子0.75
     */
    public HashMap(int initialCapacity) {
        //调用指定容量和指定加载因子的构造函数,指定为默认加载因子。
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

 /**
     * 构造一个空的HashMap,指定初始化容量和初始化加载因子的构造方法
     *
     * @param  initialCapacity 初始化容量
     * @param  loadFactor      初始化加载因子
     */
    public HashMap(int initialCapacity, float loadFactor) {
        //如果初始化容量为负数,则抛出非法参数异常
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        //如果初始化容量大于hashmap最大容量,则初始化容量就是最大容量。
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //加载因子为负数或者不是float的数值。抛出非法参数异常。
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +

        //赋值加载因子。                                       loadFactor);
        this.loadFactor = loadFactor;
        //调用tableSizeFor方法获取大于等于指定初始化容量的最小2次幂。
        this.threshold = tableSizeFor(initialCapacity);
    }
  
 /**
     *
     * 返回指定目标容量的2次幂(大于等于指定容量的最小2次幂)。
     * 一个非0的32位整数不为0的时候,32位中至少有一个位置
     * 为1,5个位移操作,是将从最高位1开始,一直到最低位的所有
     * bit全部设置为1,然后再加1,从最高位1到最低位全部变0,进一位,就得到了大于等于指定容量的最小2次幂
     *
     * 先来分析有关n位操作部分:先来假设n的二进制为01xxx...xxx。接着
     *
     * 对n右移1位:001xx...xxx,再位或:011xx...xxx
     *
     * 对n右移2为:00011...xxx,再位或:01111...xxx
     *
     * 此时前面已经有四个1了,再右移4位且位或可得8个1
     *
     * 同理,有8个1,右移8位肯定会让后八位也为1。
     *
     * 综上可得,该算法让最高位的1后面的位全变为1。
     *
     * 最后再让结果n+1,即得到了2的整数次幂的值了。
     *
     */
    static final int tableSizeFor(int cap) {
        //int为32位
        //令找到的目标值大于或等于原来的值
        //例如二进制1000,十进制数值为8。如果不对它减1而直接操作,将得到答案10000,即16。
        //显然不是结果。减1后二进制为111,再进行操作则会得到原来的数值1000,即8。
        //如果给定的n已经是2的次幂,但是不进行-1操作的话,那么得到的值就是大于给定值的最小2的次幂值。
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        /**
         *
         假设实参cap的二进制为01xx…xx。 
         对cap右移1位:01xx…xx,移位之后或:011xx…xx,使得与最高位的1紧邻的右边一位为1, 
         对cap再右移2位:00011x..xx,移位之后或:01111x..xx,使得从最高位的1开始的四位也为1, 
         以此类推,int为32位,所以在右移16位后异或最多得到32个连续的1,保证从最高位的1到末尾全部为1。
         */
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

/**
     * 利用已存在的map构建HashMap,使用默认初始化容量和默认加载因子。
     */
    public HashMap(Map<? extends K, ? extends V> m) {
        //设置默认加载因子
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        //放值。
        putMapEntries(m, false);
    }

3、put方法

 /**
     *
     * 将指定值与该映射中的指定键相关联。 如果映射先前包含键的映射,则将替换旧*值。
     *
     * @param key 指定值将与之关联的键
     * @param value 与指定键关联的值
     */
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

/**
     *key的hashCode是一个int类型的数值,长度32位,将hashCode右移16位,
     * 然后把int数值的高16位和低16位进行异或操作(相应位上的数字不相同时,该位才取1,若相同,即为0),
     * 这样可以有效避免高16位不同,但是低16位相同的key的hash碰撞
     */
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
 /**
     * 实现Map.put和相关方法
     * 寻址算法:
     * &运算代替%运算,主要是为了提升运算效率,
     * 这样hashMap的长度必须是2的n次方这样才能满足
     * hash % 2^n = hash & (2^n - 1),这也是hashMap长度必须是2的n次方的原因
     * put放值
     */
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        //创建数组存储hash表引用
        Node<K,V>[] tab;
        //创建节点存储通过寻址寻到的位置当前节点
        Node<K,V> p;
        //n存储hash表长度,i存储当前寻址的hash表索引
        int n, i;
        //如果hash表为空
        if ((tab = table) == null || (n = tab.length) == 0)
            //初始化hash表,并将hash表长度赋值给n
            n = (tab = resize()).length;
        //通过hash寻址计算到的数组索引对应的节点,将节点赋值给p,并判断是否为空,如果为空直接创建新节点放到数组的该索引位置。
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
             到这里说明目标位置桶里已经有东西了
            //如果不为空,则说明,该桶位置已经存在链表或者红黑树了。
            //创建节点e,用于存储通过节点p获取到的
            Node<K,V> e; K k;
            //如果桶中已经有值,
            //判定当前桶中的key和当前要存储的key是否相等。
            //1.hash相同。
            //2.两者‘==’或者equals相等。

            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                //已存在当前key, e保存原有的键值对
                e = p;
            //要保存的桶已被占用,并且占用位置的key和当前key不一致。
            else if (p instanceof TreeNode)
                //存入红黑树
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //当前存储还是链表,遍历链表
                for (int binCount = 0; ; ++binCount) {
                    //如果遍历到链表尾部,还没有找到值,则将节点放置到尾部。
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //如果链表长度大于8个,就将链表转化为红黑树以提升查找性能。
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //链表中找到目标key则直接退出。
                    //退出时保存目标key的键值对
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //map中存在key
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                //旧值存在或者旧值为空,用新值覆盖旧值。
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                //直接返回
                return oldValue;
            }
        }
        ++modCount;
        //table不存在要新增的key,将新的key插入hash
        //新增值了,hash元素个数加1
        if (++size > threshold)
            //扩容。
            resize();
        afterNodeInsertion(evict);
        return null;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值