HashMap
1. HashMap底层数据结构是什么?
jdk1.7
- 底层为 数组(Entry)+链表组成,链表为解决hash冲突而存在;
- 采用头插法:一般情况下,新加入的键值对被查找访问的概率更高,头插法能够提升检索效率;
jdk1.8
- 底层为 数组(Node)+链表+红黑树,当链表过长时会影响hashmap性能;链表检索的时间复杂度为O(n),红黑树的时间复杂度为O(logn),引入红黑树,在一定条件下链表和红黑树进行转换,提升检索性能;
- 采用尾插法:尾插法可以统计链表长度,判断是否大于8,而且链表过长时会转化为红黑树
2. HashMap中,关键属性有哪些?
jdk1.7
/**
* The default initial capacity - MUST be a power of two.
*/
//默认初试容量,16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
// 最大容量 1<<30
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
*/
//扩容负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
jdk1.8
/**
* The default initial capacity - MUST be a power of two.
*/
//默认初试容量,16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
// 最大容量 1<<30
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* The load factor used when none specified in constructor.
*/
//扩容负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* The bin count threshold for using a tree rather than list for a
* bin. Bins are converted to trees when adding an element to a
* bin with at least this many nodes. The value must be greater
* than 2 and should be at least 8 to mesh with assumptions in
* tree removal about conversion back to plain bins upon
* shrinkage.
*/
//链表转红黑树,链表长度阈值;
static final int TREEIFY_THRESHOLD = 8;
/**
* The bin count threshold for untreeifying a (split) bin during a
* resize operation. Should be less than TREEIFY_THRESHOLD, and at
* most 6 to mesh with shrinkage detection under removal.
*/
//红黑树转链表阈值;
static final int UNTREEIFY_THRESHOLD = 6;
/**
* The smallest table capacity for which bins may be treeified.
* (Otherwise the table is resized if too many nodes in a bin.)
* Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
* between resizing and treeification thresholds.
*/
//链表转红黑树,数组的长度;当链表长度>TREEIFY_THRESHOLD,且数组长度>64,则链表转红黑树,否则先扩容;
static final int MIN_TREEIFY_CAPACITY = 64;
3.HashMap为什么线程不安全?
- 多线程下会形成死循环;
jdk1.7采用的是头插法,在多线程情况下扩容,可能会导致链表的环形结构,出现死循环;jdk1.8采用的尾插法,在扩容的情况下保持链表原本的顺序,不会出现环形链表问题;
多线程 在CAPACITY=2的HashMap中,resize之前,插入A.B.C三个数据,按照顺序排列;resize后,由于1.7按照头插法,可能导致图2的结果,最终导致A和B形成环形;
- 多线程的put可能导致元素丢失;
多线程同时put操作,如果计算出来的索引位置相同,可能造成前一个key被后一个key覆盖,导致元素丢失; - 多线程的put,get可能导致结果为null
先put,然后扩容,此时其他线程get操作可能结果为空;
4.链表为什么要转红黑树,条件和原因?
- 在jdk1.8以前,采用数组和链表的数据结构;大量的数据在一个hash桶中时,会导致链表的长度过大,检索时间复杂度从O(1)变成O(n),降低了hashMap的性能,引入红黑树,时间复杂度为O(logn),降低检索时间,提升性能;
- 数组长度大于64,且链表长度大于8时,链表转为红黑树;
- 当链表长度小于8时,如果转红黑树,红黑树会有左旋右旋的消耗,反而降低了效率;
5.HashMap怎么解决hash冲突?
当两个对象的hashcode相等时,调用equals方法判断,相等的话则进行值覆盖,不相等则采用链表+红黑树的方式解决
6.为什么HashMap的数组大小都是2次幂?
static int indexFor(int hashcode, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
return hashcode & (length-1);
}
拿length=16为例
当任意hashcode与(16-1)进行与运算时,index的值与hashcode相关
15的二进制是1111;
10111011000111110100 & 1111 十进制结果 是 4;
10111011000111110110 & 1111 十进制结果 是 6;
10111011000111110101 & 1111 十进制结果 是 5;
当hashcode均匀分布时,对应到hashmap中的hash桶也是均匀分布;
7.为什么负载因子为0.75
根据泊松分布,在负载因子默认为0.75的时候,单个hash槽内元素个数为8的概率小于百万分之一,所以将7作为一个分水岭,等于7的时候不转换,大于等于8的时候才进行转换,小于等于6的时候就化为链表。
8.怎么解决HashMap线程不安全问题?
- Collections.synchronizedMap(Map) (全是锁)
- HashTable (全是锁)
- ConcurrentHashMap
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律