HashMap原理以及面试相关

一、什么是哈希表?

hashmap内部维护这一个散列Entry数组和一个线性链表,通过key的hashcode来存储和查找数据。而计算key的hashcode的函数称为哈希函数。其新增、查找的操作如下:

这里写图片描述
存储结构如下:
这里写图片描述
通过哈希函数计算出实际存储地址,在bucket中找到对应的位置进行的查询、新增操作。

二、HashMap常见面试问题

1.HashMap的工作原理
2.HashMap的键和值可以为Null吗?为什么?
3.如果两个对象的hashcode相同会发生什么?
4.如何获取hashcode相同的值对象?
5.如果HashMap大小超过负载因子(load factor:default 0.75)定义的容量,怎么办?
6.如果要调整HashMap的大小,会存在什么问题?
7.哪些数据类型适合作为HashMap的键,为什么?
8.可以用CocurentHashMap替代HashMap吗?

三、原理解析

1.HashMap工作原理

HashMap基于hashing原理,通过put()和get()方法存储和获取对象,当我们调用put函数存储键值对时,会先调用对象的hashCode()方法计算出key的hashcode,然后根据这个值找到bucket的位置来存储值对象。当调用get()方法获取对象的时候,也是先根据key来计算hashcode,然后找到bucket的位置来查找对应的值对象。

2.对于第二个面试题,先来看HashMap的put方法:

//put
public V put(K key, V value){
 //如果key为null,调用putForNullKey()方法写入null键的值
 //这就是为什么HashMap允许键为null的原因
 if (key == null){
    return putForNullKey(value);
}

//根据key的hashCode计算Hash值 
int hash = hash(key.hashCode());
//查找hash值在table中的索引
int i = indexFor(hash, table.length);
// 如果 i 索引处的 Entry 不为 null,通过循环不断遍历链表查找是否在链表中有相同key的Entry
//这就是HashMap处理hash碰撞的方法,用一个链表来解决
for (Entry<K,V> e = tablei; e != null; e = e.next) {
    Object k;
    //找到与插入的值的key和hash相同的Entry
    if (e.hash == hash && ((k = e.key) == key|| key.equals(k)){

        //key值相同时直接替换value值,跳出函数
    V oldValue = e.value;
      e.value = value;
    e.recordAccess(this);
  return oldValue;
        }
    }
// 如果 i 索引处的 Entry 为 null 或者key的hash值相同而key不同  ,则需要新增Entry
modCount++; 
// 将 key、value 添加到 i 索引处
addEntry(hash, key, value, i); 
return null; 
} 
//get
public V get(Object key) 
{ 
// 如果key是null调用 getForNullKey取出null的value 
if (key == null) 
return getForNullKey(); 
// 根据该key的hashCode值计算它的hash码 
int hash = hash(key.hashCode()); 
// 直接取出table数组中指定索引处的值, 
for (Entry<K,V> e = table[indexFor(hash, table.length)]; 
e != null; 
// 搜索该Entry链的下一个对象 
e = e.next) 
{ 
Object k; 
// 如果该Entry的key和hash与被搜索key相同 
if (e.hash == hash && ((k = e.key) == key 
|| key.equals(k))) 
return e.value; 
} 
return null; 
} 

3.如果两个对象的hashCode相同

存储时:它们会找到相同的bucket位置,发生hash碰撞,对每个bucket位置,会有一个维护链表,通过一个next指针指向下一个Entry节点,然后将值存储在这个链表中的next节点。
获取时,会用hashCode找到这个bucket位置,然后遍历这个链表,调用key.equal()方法,找到链表中正确的节点,然后返回要找的键值对对象。

4.HashMap扩容

HashMap的默认负载因子为0.75,这个负载因子就是用来计算当前的HashMap的容量,当map被填满了75%bucket的时候,将会调用resize()方法,创建原来HashMap两倍大小的bucket数组,并将原来的对象放入新的数组中,这个过程叫rehashing。

5.HashMap键的选择

建议使用String、Integer作为键,因为我们可以使用final修饰作为不可变对象,能够很好的防止hashCode重复导致发生hash碰撞,这样可以提高HashMap的性能,因为不用去遍历链表去查找元素。

6.ConcurrentHashMap

ConcurrentHashMap可以替换HashMap。要根据使用场景来选择,如果考虑多线程的问题,使用ConcurrentHashMap比较好,因为它仅仅根据同步级别对map的一部分进行上锁,效率要高一些。但是在不考虑多线程的应用场景中,HashMap会更好。

四、总结以及其他注意点

  • 选用String、Integer作为键会提高HashMap的效率。
  • 如果HashMap的大小超过负载因子定义的容量,会创建一个原来两倍大小的新bucket数组,将原来的对象放入新数组,这个过程影响效率且容易引发线程不安全问题,因为多个线程同时发现需要调整容量时,会出现条件竞争的现象,因为可能形成一个环形的链表,导致所有的next指向都不为null,进入死循环。
  • 多线程同时进行put操作也可能导致数据丢失。

参考: —— [ HashMap实现原理及源码分析 ]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值