HashMap
HasMap
基于JDK1.8
HasMap 是最早出现是JDK1.2,到1.7的版本都没大的太大的变化,
JDK1.7 --- JDK1.8 有很大改动
* jdk1.7的存储结构是数组+链表*
jdk1.8 数组+链表+红黑树
HasMap 是非线程安全的,也就是说在多个线程对HasMap 中的某个元素进行crud操作时,是不能保证数据的一致性。
深入分析HasMap

首先是把元素一个个放在数组中,后面存放的数据元素越来越多,于是就出现了链表,对于数组中的每一个元素,都有一条链表来存储元素。
后面因为存储的元素越来越多,链表也越来越长。这样查找元素的时候效率不但没有提高反而下降了(链表不适合查找,适合增删)
数组:采用一段连续的存储单元来存储数据 ,对于指定下标的查找,时间复杂度为0(1);对于一般的插入删除操作,涉及到数组元素的移动,其平均复杂度为0(n);
查询快、插入慢
链表是 一种物理存储单元上非连续的、非顺序的存储结构,
插入快,查找慢
jdk1.8就把链表变成了一个适合查找的树形结构,就是红黑树。
**** 不是红黑树就就一定效率高,只有在链表长度不小于8,而且数组长度不小于64的时候才讲链表转换成红黑树 ****
那为什么不把整个链表编程红黑树?也可以说为什么等到链表的长度大于8时,才将链表转化为红黑树?
1 红黑树是一个特殊的平衡二叉树,查找的时间复杂度是 O(logn) ;而链表查找元素的时间复杂度为 O(n),远远大于红黑树的 O(logn)。红黑树的查询效率很高。
2 构造红黑树要比构造链表复杂的多,再在链表的节点不是很多的时候,从整体性能来看,数组+链表+红黑树的结构可能不一定比数组+链表的结构性能高。杀鸡用牛刀,过度张扬。
3 HasMap 频繁的扩容,就会造成底部的红黑树不断地进行拆分重组,非常耗时,即只有链表很长的时候才转换为红黑树。
HasMap的存储
放置元素put HasMap
public class Test {
/**
* 测试HasMap
*/
public static void main(String[] args) {
HashMap<String, Integer> hashMap = new HashMap<>();
//存储第一个元素
hashMap.put("Shang",18);
System.out.println(hashMap);
}
}
详解:
HashMap<String, Integer>
String----->键 Integer----->值 键值对

- 调用put方法传入键值对
- 使用hash算法计算hash值
int has = key.hasCode() key的hasCode得到一个哈希值
has & arr[length-1] 得到放置位置数组的下标 - 根据hash值确定存放位置判断是否和其他键值对位置冲突
- 若没有冲突,直接存放在数组中即可
- 若发生冲突,判断此时数据结构是什么
- 若此时数据结构为红黑树,直接插入红黑树就行
- 若此时数据结构是链表,插入后判断是否大于等于8
- 插入后不大于8,直接插入到链表的尾部
- 插入后大于8,先把数据结构变为红黑树,再插入
put源码
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
put内部调用的是putVal方法
putVal源码
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//第一部分
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//第二部分
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
//第三部分
else {
Node<K,V> e; K k;
//第三部分一小节
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//第三部分二小节
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//第三部分三小节
else {
for (int binCount = 0; ; ++binCount) {
//第三部分三小节一小小节
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
//第三部分三小节二小小节
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//第三部分三小节三小小节
p = e;
}
}
//第三部分三小节四小小节
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//第四部分
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
-
第一个参数,调用hash方法计算hash值
-
第二个参数key,即键值对的键
-
第三个参数是value
-
第四个onlyIfAbsent,也就是键相等时,不修改已存在的值
-
第五个 evict 要是为false,那么数组就处于创建模式中,所以一般为true
HashMap的构造方法
1
public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; }2
public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); }3
public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); }4 参数:initialCapacity 初始容量 参数:loadFactor 负载因子
public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) //如果初始容量小于0,抛出异常 throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); //如果初始容量超过最大容量 if (initialCapacity > MAXIMUM_CAPACITY) //则使用最大容量作为初始容量 initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) //如果负载容量小于等于0或者不是数字,则抛出异常 throw new IllegalArgumentException("Illegal load factor: " + loadFactor); //把负载因子赋值给成员变量loadFactor this.loadFactor = loadFactor; // 调用tableSizeFor方法计算出不小于initialCapacity的最小的幂的结果, // 并赋值给成员变量threshold this.threshold = tableSizeFor(initialCapacity); }
英语不好的人看的我真是一脸懵逼,不过好在大概意思还能明白。看第三行Poisson_distribution这不就是泊淞分布嘛。而且最关键的就是
当桶中元素到达8个的时候,概率已经变得非常小,也就是说用0.75作为加载因子,每个碰撞位置的链表长度超过8个是几乎不可能的。当桶中元素到达8个的时候,概率已经变得非常小,也就是说用0.75作为加载因子,每个碰撞位置的链表长度超过8个是几乎不可能的。
链表和数组
1 在访问方式上
数组可以随机访问其中的元素
链表则必须是顺序访问,不能随机访问
2 空间的使用上
链表可以随意扩大
数组则不能
数组:
优点:使用方便 ,查询效率 比链表高,内存为一连续的区域
缺点:大小固定,不适合动态存储,不方便动态添加
链表:
优点:可动态添加删除 大小可变 ,内存可能是不连续内存,链式存储。
缺点:只能通过顺次指针访问,查询效率低

浙公网安备 33010602011771号