JDK1.8中HashMap源码分析

一、分析一下hashmap原理

1、DEFAULT_INITIAL_CAPACITY = 1 << 4; 默认初始容量为16;

2、MAXIMUM_CAPACITY = 1 << 30; 最大容量为2^30次方;

3、DEFAULT_LOAD_FACTOR = 0.75f; 默认加载因子为0.75f;

4、TREEIFY_THRESHOLD = 8; 默认链表长度大于等于8时(并且数组长度不小于64)时转为红黑树;

5、UNTREEIFY_THRESHOLD = 6 默认小于等于6时从红黑树转为链表;

6、MIN_TREEIFY_CAPACITY = 64; 默认数组长度小于等于64。

7、size: 获取数组元素容量,modCount:记录操作的次数,threshold:阈值

二、开始分析put()方法

在这里插入图片描述
1、初始化前:table = null; threshold = 0;

 Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;

2、初始化后:newCap = DEFAULT_INITIAL_CAPACITY(默认初始化16); newThr = 12(即DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY)

int newCap, newThr = 0;
if (oldCap > 0) {				//判断容量是否大于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) // 初始容量设置为阈值
    newCap = oldThr;
else {               // 零初始阈值表示使用默认值
    newCap = DEFAULT_INITIAL_CAPACITY;		//默认容量初始化16
    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;

这时候新new了一个Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]将newCap值赋值到新的newTab的Node<K,V>上。(即newTab容量为16)

公式推理:table = newTab = newCap=16

三、开始分析get()方法

在这里插入图片描述
1、进入get方法会进行判断hash值是否相同,然后通过key获取hashCode值,并判断当前hashCode值是否为空。(不为空直接返回hashCode值)然后通过计算返回hash值。

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

通过返回hash值得到对应指针的元素的位置,然后判断hash值和hash所对应的键是否相同。若判断相同,则直接返回该元素所对应的值。否则会去在该节点下判断是否有下一个元素(如果有会判断是否是红黑树,若不是会进入循环并判断hash值和对应的键是否相同,会一直查找下一个,直到遍历到最后一个元素),没有返回为null。

if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // 总是检查第一个节点
                ((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);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
四、判断两个对象是否相同

1、演示:创建两个对象并put()方法到容器中

代码如下:

HashMap<Person, String> map = new HashMap<Person, String>();

		Person person = new Person();
		person.setUsername("mbql");
		person.setPassword("123");
		map.put(person, "aa");
		Person person1 = new Person();
		person1.setUsername("mbql");
		person1.setPassword("123");

接下来跟踪源码可知:

person:  static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); //hash = 357860815
    }
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

能否从容器中get()方法获取到该对象?

System.out.println(map.get(person1));

继续深入源码可知:

person1: static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); //hash = 1811037448
    }

public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            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);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

此时可知:

这两个对象的hash值并不相同,所以在进入第一个if条件时就不成立(即返回为null)

综述:获取到该对象为空

怎么解决hash值不同的问题?

1、在Person对象中重写hashCode(即可解决hash不一致问题)

继续源码分析:

put():

@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((password == null) ? 0 : password.hashCode());
		result = prime * result + ((username == null) ? 0 : username.hashCode());
		return result;
	}

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

get():

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); //hash = 4855413
    }

public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
 final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // 总是检查第一个节点
                ((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);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

分析源码可知:

重写了hashCode()方法,真的解决了hash不相同问题。但在进入上面的第二个if条件时,分析发现这个持有键的对象的key不是同一个对象(即返回的对象还是为null)

可以看一下在这判断里面的equals()方法怎么实现的?

 public boolean equals(Object obj) {
        return (this == obj);
    }

分析发现equals()方法用的是==号判断(即比对的是当前指针的内存地址)

2、怎么解决equals判断对象的方式?

在Person类中重写equals()方法(即可解决)

@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Person other = (Person) obj;
		if (password == null) {
			if (other.password != null)
				return false;
		} else if (!password.equals(other.password))
			return false;
		if (username == null) {
			if (other.username != null)
				return false;
		} else if (!username.equals(other.username))
			return false;
		return true;
	}

继续分析源码:

final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            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);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }

在此时可知:

进入到第二个if条件时,会进行判断equal()方法判断对象是否相同。由于我们上面重写了equals()方法,所以此时用equals()方法判断对象属性时是属于同一个对象(即返回为该对象) return first。

这样分析下来可知,在使用HashMap时重写hashCode()和equals()方法是很重要的(能够解决hashCode的冲突性)。

五、总结

最后,做个总结:

1、在使用hashmap集合时,尽量重写hashCode和equals()方法,避免在后期遇到对象找不到问题,也能在一定程度上解决hash冲突问题。

2、创建对象时,最好指定hashmap的容量,可以通过算法计算出所需的容量,这样能够解决程序资源的浪费问题。

3、日常开发中,在使用hashmap时尽量根据业务的需求来做,对业务上资源的遗留优化问题,能够提升程序在运行的性能问题。

本文觉得有帮助,记得给博主一个关注及点赞哦~~~

后期会继续更新相关文章

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

架构潜行之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值