HashMap源码初尝及应用

这篇博客详细介绍了HashMap的源码,包括继承关系、底层结构、初始化、常见API如put、get和remove的实现。重点讨论了JDK1.7和1.8中HashMap的hash算法,特别是扰动处理,以及为何数组长度要求为2的幂。此外,还分析了何时链表转化为红黑树以及为何选择特定的树化和链表化阈值。
摘要由CSDN通过智能技术生成

思维导图

思维导图是个好东西,哈哈

继承关系

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {

可以看出具有map的通性,也能够被克隆

底层结构

hashMap的hash算法

JDK1.7中

static int hash(int h) {
    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).

    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

扰动处理 = 9次扰动 = 4次位运算 + 5次异或运算

JDk1.8中

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);key.hashCode()为哈希算法,返回初始哈希值
    }

扰动处理 = 2次扰动 = 1次位运算 + 1次异或运算

key.hashCode()是Key自带的hashCode()方法,返回一个int类型的散列值。

32位带符号的int表值范围从-2147483648到2147483648,大概40亿的样子。

这么大的值里,一般是很难发生碰撞的,但是内存放不下这么长的数组,同时HashMap的初始容量只有16,

所以这样的散列值使用需要对数组的长度取模运算,得到余数才是索引值。

int index = (n - 1) & hash;
为什么HashMap的数组长度是2的整数幂?

以初始长度为16为举例,16-1 = 15,15的二进制数位是0000 0000 0000 0000 0000 1111,

再比如31的二进制是0000 0000 0000 0000 0001 1111,

可以看出一个基数二进制最后一位必然位1,当与一个hash值进行与运算时,最后一位可能是0也可能是1。

但偶数与一个hash值进行与运算最后一位必然为0,造成有些位置永远映射不上值。

相与下来高位全部为0,只保留低位,

但是这时,又出现了一个问题,即使散列函数很松散,但只取最后几位碰撞也会很严重。

这时候hash算法的价值就体现出来了,

扰动函数

hashCode右移16位,正好是32bit的一半,

与自己本身做异或操作(相同为0,不同为1),

也是为了混合哈希值的高位和低位,增加低位的随机性,

同时混合后的值也变相保持了高位的特征。

JDK1.7的底层结构

数组+链表

JDK1.8的底层结构

数组+链表+红黑树


先是数组加链表结构,当同一个hash值存放超过8个元素时,即当链表长度超过阈值(8)时,转换为红黑树结构,加快查询速度。

初始化

一些基本属性table、entrySet、size、modCount、threshold、loadFactor

/**
     * The table, initialized on first use, and resized as
     * necessary. When allocated, length is always a power of two.
     * (We also tolerate length zero in some operations to allow
     * bootstrapping mechanics that are currently not needed.)
     * 表格,在第一次使用时初始化,调整大小为必要的。当分配时,长度总是2的幂。(在某些操作中,我们也允许长度为零目前不需要的引导机制。)
     * 其定义为 Node<K,V>[],即用来存储 key-value 的节点对象。在 HashMap 中它有个专业的叫法 buckets ,中文叫作桶。
     */
    transient Node<K,V>[] table;

    /**
     * Holds cached entrySet(). Note that AbstractMap fields are used
     * for keySet() and values().
     * 保存缓存entrySet ()。注意,这里使用了AbstractMap字段表示keySet()和values()。
     * 
     */
    transient Set<Map.Entry<K,V>> entrySet;

    /**
     * The number of key-value mappings contained in this map.
     * 此映射中包含的键-值映射的数目
     * 容器中实际存放的node大小
     */
    transient int size;

    /**
     * The number of times this HashMap has been structurally modified
     * Structural modifications are those that change the number of mappings in
     * the HashMap or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the HashMap fail-fast.  (See ConcurrentModificationException).
     * 这个HashMap在结构上被修改的次数,结构修改是指改变映射的数量HashMap或修改其内部结构(例如,重复)。
     * 记录容器被修改的次数
     */
    transient int modCount;

    /**
     * The next size value at which to resize (capacity * load factor).
     * 下一个需要扩容的阈值
     * @serial
     */
    // (The javadoc description is true upon serialization.
    // Additionally, if the table array has not been allocated, this
    // field holds the initial array capacity, or zero signifying
    // DEFAULT_INITIAL_CAPACITY.)
    int threshold;

    /**
     * The load factor for the hash table.
     * 负载因子
     * @serial
     */
    final float loadFactor;

内部桶的源码

    /**
     * Basic hash bin node, used for most entries.  (See below for
     * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
     * 基本哈希bin节点,用于大多数条目。(见下文TreeNode子类,在LinkedHashMap中为它的Entry子类。)
     */
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        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);//重写hashcode方法
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
        //重写equals方法
        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;
        }
    }

无参构造方法

这是用得最多的构造方法

/**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     * 使用默认的初始容量构造一个空的HashMap,默认大小16,默认负载因子为0.75
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

指定大小的构造方法

默认负载因子是0.75

/**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and the default load factor (0.75).
     *
     * @param  initialCapacity the initial capacity.
     * @throws IllegalArgumentException if the initial capacity is negative. 当初始化大小为负数时,抛出异常
     */
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

指定大小和负载因子的构造方法

这种情况下需要算出阈值。

如果操出了map的最大值,即2的30次方,就将值设置为最大值,

如果负载因子为负或者不是数字类型,都抛出异常。

/**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and load factor.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    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);
    }

调用tableSizeFor方法获取大于该初始值的2的n次方值。

/**
     * Returns a power of two size for the given target capacity.
     */
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值