网站首页 > 教程文章 正文
作为一个职场搬了几年砖的码农,不管是面试别人还是被别人面试,hashmap在面试出现的几率可以说高达90%以上!
面试官们,如此反复的提及hashmap,它在软件开发中的重要性可见一斑。
那么,hashmap到底是个什么鬼?
hashmap在不同的jdk版本其实现方式也是不大一样的。
JDK1.7:基于数组(散列桶)+链表来实现。
transient Entry<K,V>[] table=(Entry<K,V>[])EMPTY_TABLE;
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
int hash;
...
}
使用一个Entry数组来存储数据,用key的hashcode取模来决定key会被放到数组里的位置,如果hashcode相同,或者hashcode取模后的结果相同(hash collision),那么这些key会被定位到Entry数组的同一个格子里,这些key会形成一个链表。
在hashcode特别差的情况下,比如所有key的hashcode都相同,这个链表可能会很长,那么put/get操作都可能需要遍历这个链表。
换句话说时间复杂度在最差情况下会退化到O(n)。
JDK1.8:基于数组(散列桶)+链表/红黑树来实现。
transient Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
...
}
使用一个Node数组来存储数据,但这个Node可能是链表结构,也可能是红黑树结构。
如果插入的key的hashcode相同,那么这些key也会被定位到Node数组的同一个格子里。
如果同一个格子里的key不超过8个,使用链表结构存储。
如果超过了8个,那么会调用treeifyBin函数,将链表转换为红黑树。
那么即使hashcode完全相同,由于红黑树的特点,查找某个特定元素,也只需要O(log n)的开销。
也就是说put/get的操作的时间复杂度最差只有O(log n)。
相同点
1. 默认初始容量都是16,默认负载因子都是0.75。数组的长度length都是2的次幂,扩容时都是2倍
2. 通过hash计算索引的方法相同(hash & length-1)
3. key为null的键值对都会放入table[0]中
4. 都是懒加载,初始时表为空,在插入第一个键值对时初始化
不同点
1. 结构不同,JDK1.8增加了红黑树优化结构
2. put方法的区别,JDK1.7中put时,添加到头节点;JDK1.8中添加到尾节点
3. 计算hash的方法不同,JDK1.8更优化
4. JDK1.7新链表的顺序倒置,JDK1.8新链表顺序不倒置
jdk1.8 put操作:
①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;
②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;
③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;
④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;
⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。
篇幅有限,感兴趣的童鞋可以看看hashmap源码是如何处理这些关键点:根据key获取哈希桶数组索引位置、put方法的详细执行、扩容机制。
下面分享一个面试常问的问题:HashMap是线程安全的么?如果不是,那如果既想用HashMap又想让它线程安全应该怎么做?
可以有如下几种方式来回答:
- 替换成Hashtable,Hashtable通过对整个表上锁实现线程安全,但是效率比较低
- 使用Collections类的synchronizedMap方法包装一下。方法如下:
- public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
- 返回由指定映射支持的同步(线程安全的)映射
- 使用ConcurrentHashMap,它使用分段锁来保证线程安全
- 前两种方式获得的线程安全的HashMap在读写数据的时候会对整个容器上锁
- ConcurrentHashMap是不需要对整个容器上锁的,它只需锁住要修改的部分
- 上一篇: 用40 张图全面了解 Redis数据结构,拿捏的死死的
- 下一篇: Java基础八股文背诵版
猜你喜欢
- 2025-01-03 Java基础八股文背诵版
- 2025-01-03 用40 张图全面了解 Redis数据结构,拿捏的死死的
- 2025-01-03 不怕面试再问HashMap,一次彻底地梳理(原理+手写实现)
- 2025-01-03 Redis:缓存被我写满了,该怎么办?
- 2025-01-03 HashMap 中这些设计,绝了
- 2025-01-03 Redis原理 - 对象的数据结构SDS、Inset、Dict、ZipList、QuickList
- 2025-01-03 《关于横扫一线厂的那些面试真题》滴滴Java岗(附答案)
- 2025-01-03 HashMap和Hashtable有什么区别?
- 2025-01-03 数据结构——Map和哈希表
- 2025-01-03 字节跳动这份面试题,你能打几分
- 最近发表
- 标签列表
-
- location.href (44)
- document.ready (36)
- git checkout -b (34)
- 跃点数 (35)
- 阿里云镜像地址 (33)
- qt qmessagebox (36)
- md5 sha1 (32)
- mybatis plus page (35)
- semaphore 使用详解 (32)
- update from 语句 (32)
- vue @scroll (38)
- 堆栈区别 (33)
- 在线子域名爆破 (32)
- 什么是容器 (33)
- sha1 md5 (33)
- navicat导出数据 (34)
- 阿里云acp考试 (33)
- 阿里云 nacos (34)
- redhat官网下载镜像 (36)
- srs服务器 (33)
- pico开发者 (33)
- https的端口号 (34)
- vscode更改主题 (35)
- 阿里云资源池 (34)
- os.path.join (33)