HashMap详解以及常见面试题
强烈建议!!!阅读另外一篇用图演示流程的HashMap详解文章,不懂来敲我
点这里 >> 【多图预警,不懂来敲我】HashMap详解
一、概要
本文先会将hashmap的基本概念属性梳理一遍后,再从增删改查方法中每个步骤逐一解释说明。力求达到通熟易懂,逻辑清晰,便于加深印象。
二、概念
首先Hashmap的底层数据结构是由数组+链表组成的,是线程不安全,允许key和value为null。底层结构数组叫哈希桶,而桶内则是链表,链表中的节点Node存放着实际的元素。
Hashmap中获取元素时的主要流程步骤为,首先对key值进行hash算法得出hash值即哈希桶中的索引值,再找到对应的hash桶。如果存在,则通过(拉链法)链表从前往后比较value值是否相等,直到找到元素或下个节点为null时。
而增加元素或修改元素的主要流程步骤与获取相类似,不同在于当增加元素后,如果总元素size大于阈值时,会发生扩容。
在JDK8中,加强了hash算法的效率以及利用率,当桶内元素大于8和桶数组长度大于64时,将链表转换为红黑树,优化了扩容时的算法。
三、分解
1. hash算法
在上面概念中讲到,hash算法是计算key值对应哈希桶的位置即索引值。我们都知道数组在获取元素会比链表快,所以我们应该尽量让每个哈希桶只有一个元素,这样在查询时就只需要通过索引值找到对应的哈希桶内的值,而不需要再通过桶内的链表一个一个去查。所以hash算法的作用是为了让元素分散均匀,从而提高查询效率。那接下来通过代码来一步一步分析时如何让元素分布均匀的。
//这是根据key值获取value值的方法
public V get(Object key) {
Node<K,V> e;
//先调用hash(key)
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
//Hash算法如下
static final int hash(Object key) {
int h;
//第一步:先获取key中的hashCode值
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
//第二步 再与hashcode无符号向右移16位的值进行抑或
}
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 &&
//第三步 n是指数组长度,hash与(n-1)进行&与运算
(first = tab[(n - 1) & hash]) != null) {
//省略
...
...
}
return null;
}
首先第一步获取hashcode没什么问题,到第二步为什么会跟hashcode无符号右移16位的值进行抑或呢? 其实是将高位与低位进行与运算,减少碰撞机率。第三步取余运算,但在计算机运算中&肯定比%快,又因为h % n = h &(n - 1),所以最终将第二步得到的hash跟n-1进行与运算。n是table中的长度。
2、get方法,查询元素
查询get方法相对简单,只要明白hash算法后得到哈希桶的索引值,再对桶内的链表进行比较hash,key是否相等。下面是get方法中主要的代码
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
//如果表为null或长度为0,或者经过hash算法后得到的哈希桶为null,则直接返回null
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//如果链表中的第一个节点元素相等则直接返回该Node
if (first.hash == hash && // always check first node
((k = firs