HashMap实现原理及源码分析

一、Map家族

在这里插入图片描述
其中实线箭头是继承,虚线箭头是接口实现。

二、HashMap操作元素

数组和链表是两种最基本的线性数据结构,在java中的代表就是java数组和引用变量,HashMap就是用数组和链表来存储数据的,HashMap中默认有一个长度为16的数组,数组的每个元素中存储一个链表的头结点,具体如下图:
图片来自
当执行Map map=new HashMap(); 时,map实例中初始化一个类型为Entry的空数组。

假如现在HashMap中的数据如上图所示,当再次向HashMap中put元素的时候,比如执行map.put(“key1”,”value1”); 会先计算出key1的hashCode,然后根据hashCode来计算存放该键值对(Entry)的数组的坐标位置,然后判断该位置是否已经存在元素,如果不存在,直接把新的元素保存在数组的对应位置即可,如果已经存在元素,判断存在元素与新元素key值是否相等,相等则用新的value替换就的value,否则需要把新元素的next指向旧的元素(当前链表的表头),然后把新元素保存在数组的对应位置,相当于新的元素来了之后插在最前面,把链表中的所有元素都向后挪一个位置,上图数组坐标为0的链表中Entry1插入的时候,就是把它自己的next指向Entry0,然后把自己存在数组中。

当根据key值获取HashMap中的元素时,会先计算key得hashCode,然后根据hashCode找出该元素存储链表所在的数组下标,遍历链表,找到key值相同的元素返回,这也是为什么HashMap在多次put相同key值不同value值时,之前的value会被最新的覆盖。

三、源码分析

1、jdk1.7
2、jdk1.8
  • 常量
//支持可序列化
private static final long serialVersionUID = 362498820763181265L;
//默认的初始化容量16,使用位移,2进制,因为二进制效率更高
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
//最大容量2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;
//加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//当桶(bucket)上的结点数大于这个值时链表会转成红黑树
static final int TREEIFY_THRESHOLD = 8;
//当桶(bucket)上的结点数小于这个值时红黑树转链表
static final int UNTREEIFY_THRESHOLD = 6;
//桶结构转化为红黑树的时候,table最小容量
static final int MIN_TREEIFY_CAPACITY = 64;

  1. HashMap有2个参数影响它的性能:初始容量和加载因子。

    加载因子:哈希表在其容量自动增加之前可以达到多满的一种尺度,它衡量的是一个散列表的空间的使用程度,加载因子越大表示散列表的装填程度越高,反之愈小。

    表示当map集合中存储的元素个数达到当前数组大小的75%则需要进行扩容。

  2. 源码中默认的负载因子是设置成(0.75)在时间和空间成本之间提供了一个很好的权衡。更高的值减少了空间开销,但是增加了查找成本。

  • 构造函数
    在这里插入图片描述
  1. HashMap():构建一个空的HashMap,默认加载因子0.75,其他的属性默认
public HashMap() {
	this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
 }

由于HashMap采用的是懒加载机制,也就是说在你执行new HashMap()的时候,构造方法并没有在构造出HashMap实例的同时也把HashMap实例里所需的数组给初始化出来,只有在第一次需要用到这个数组的时候才会去初始化它,就是在你往HashMap里面put元素的时候。

  1. HashMap(int):构建指定初始化容量的,默认加载因子0.75的HashMap
public HashMap(int initialCapacity) {
	//此处通过把第二个参数负载因子使用默认值0.75f,然后调用有两个参数的构造方法
	this(initialCapacity, DEFAULT_LOAD_FACTOR);
 }
  1. HashMap(int, float):构建指定的初始化容量、指定的加载因子
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
    	//如果初始化容量小于0,直接抛异常:非法初始化容量
        throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
    	//如果初始化容量大于最大的容量,则取最大的那个值
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
    	//如果负载因子小于等于0或者不是数字,则抛出异常
        throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
  }
  1. HashMap(Map<? extends K, ? extends V> m):有一个Map类型的参数的构造方法
    public HashMap(Map<? extends K, ? extends V> m) {
    	//将默认的负载因子赋值给成员变量loadFactor
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        //这个方法调用了HashMap的resize()扩容方法和putVal()存入数据方法
        putMapEntries(m, false);
    }
  • 内部结构
  1. transient Node<K,V>[] table:单向链表的一个结点,是HashMap的一个静态的内部类
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;//该key的hashCode
        final K key;//存储元素的key值
        V value;//存储元素的value值
        Node<K,V> next;//下一个Node节点的“指针”
		
        Node(int hash, K key, V value, Node<K,V> next) {//Node构造方法
            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; }
		
		//Node的哈希方法,是将键与值的哈希取一次异或,作为自身的哈希值
        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

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

		//首先判断参数中的对象o和自身地址是否相同,之后,再判断o是否是Map.Entry接口的实现类,若是的话,判断键与值是否分别相等,若相等,则返回true。
        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;
        }
    }

当链表长度达到阀值,链表转化为红黑树。此时数组和红黑树中的元素是TreeNode<K,V>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值