HashMap底层源码探究
一、什么是哈希表
1.定义:散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。 给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。 2.哈希冲突:然而万事无完美,如果两个不同的元素,通过哈希函数得出的实际存储地址相同怎么办?也就是说,当我们对某个元素进行哈希运算,得到一个存储地址,然后要进行插入的时候,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞。 那么哈希冲突如何解决呢?哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式,
二、HashMap的继承结构
从图中我们可以看到HashMap继承了 接口Cloneable,用于表明HashMap对象会重写java.lang.Object#clone()方法 接口Serializeble,用于表明HashMap对象可以被序列化 HashMap同时继承了抽象类AbstractMap与接口Map,AbstrctMap相当于一个辅助类,对Map中的方法提供了一个基本实现,减少了实现Map接口的工作量,Map的一些操作在这里面已经提供了默认实现,后面具体的子类如果没有特殊行为,可以直接使用AbstractMap提供的实现。
三、HashMap的属性及默认值
static final int DEFAULT_INITIAL_CAPACITY
= 1 << 4;
static final float DEFAULT_LOAD_FACTOR
= 0.75f;
static final Entry
<?,?>[] EMPTY_TABLE
= {};
transient int size
;
int threshold
;
ps:HashMap的扩容操作是一项很耗时的任务,所以如果能估算Map的容量,最好给它一个默认初始值,避免进行多次扩容。HashMap的线程是不安全的,多线程环境中推荐是ConcurrentHashMap。
四、构造函数
HashMap()提供了四个构造函数:
HashMap() 构造一bai个具有默认初始容量 (16) 和默认加载因子du (0.75) 的空 HashMap(int initialCapacity) 构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap HashMap(int initialCapacity, float loadFactor) 构造一个带指定初始容量和加载因子的空 HashMap HashMap(Map<? extends K,? extends V> m) 构造一个映射关系与指定 Map 相同的新 HashMap
五、构造方法
1.put方法
public V
put(K key
, V value
) {
return putVal(hash(key
), key
, value
, false, true);
}
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)
treeifyBin(tab
, hash
);
break;
}
if (e
.hash
== hash
&&
((k
= e
.key
) == key
|| (key
!= null
&& key
.equals(k
))))
break;
p
= e
;
}
}
if (e
!= null
) {
V oldValue
= e
.value
;
if (!onlyIfAbsent
|| oldValue
== null
)
e
.value
= value
;
afterNodeAccess(e
);
return oldValue
;
}
}
++modCount
;
if (++size
> threshold
)
resize();
afterNodeInsertion(evict
);
return null
;
}
2、get方法
public V
get(Object key
) {
Node
<K,V> e
;
return (e
= getNode(hash(key
), key
)) == null
? null
: e
.value
;
}
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 &&
(first
= tab
[(n
- 1) & hash
]) != null
) {
if (first
.hash
== hash
&&
((k
= first
.key
) == key
|| (key
!= null
&& key
.equals(k
))))
return first
;
if ((e
= first
.next
) != null
) {
if (first
instanceof TreeNode)
return ((TreeNode
<K,V>)first
).getTreeNode(hash
, key
);
do {
if (e
.hash
== hash
&&
((k
= e
.key
) == key
|| (key
!= null
&& key
.equals(k
))))
return e
;
} while ((e
= e
.next
) != null
);
}
}
return null
;
}
3、remove方法
public V
remove(Object key
) {
Node
<K,V> e
;
return (e
= removeNode(hash(key
), key
, null
, false, true)) == null
?
null
: e
.value
;
}
final Node
<K,V> removeNode(int hash
, Object key
, Object value
,
boolean matchValue
, boolean movable
) {
Node
<K,V>[] tab
; Node
<K,V> p
; int n
, index
;
if ((tab
= table
) != null
&& (n
= tab
.length
) > 0 &&
(p
= tab
[index
= (n
- 1) & hash
]) != null
) {
Node
<K,V> node
= null
, e
; K k
; V v
;
if (p
.hash
== hash
&&
((k
= p
.key
) == key
|| (key
!= null
&& key
.equals(k
))))
node
= p
;
else if ((e
= p
.next
) != null
) {
if (p
instanceof TreeNode)
node
= ((TreeNode
<K,V>)p
).getTreeNode(hash
, key
);
else {
do {
if (e
.hash
== hash
&&
((k
= e
.key
) == key
||
(key
!= null
&& key
.equals(k
)))) {
node
= e
;
break;
}
p
= e
;
} while ((e
= e
.next
) != null
);
}
}
if (node
!= null
&& (!matchValue
|| (v
= node
.value
) == value
||
(value
!= null
&& value
.equals(v
)))) {
if (node
instanceof TreeNode)
((TreeNode
<K,V>)node
).removeTreeNode(this, tab
, movable
);
else if (node
== p
)
tab
[index
] = node
.next
;
else
p
.next
= node
.next
;
++modCount
;
--size
;
afterNodeRemoval(node
);
return node
;
}
}
return null
;
}
六、特点总结与适用场景
允许key与value值都为null值,HashMap以null作为key时,总是存储在table数组的0号索引位置上底层数据结构为哈希表,采用链地址法(数组+链表的形式存储数据)不保证其内部元素的顺序,而且随着时间的推移,同一元素的位置也可能发生改变(resize(扩容)情况)HashMap的初始默认值16,其容量必须是2的指数大小HashMap扩容:按照原容量大小的2倍扩容:2*table.lengthHashMap是一个线程非安全的,在多线程的情况下存在安全问题,所以不能在多线程中使用,那么如何才能使HashMap成为安全的呢? 使用Collections.synchronizedMap(new HashMap(…));通过这种方式可以得到一个线程安全的map。
HashMap的使用场景
数据统计时,Map存储的数据是键值对,key是统计的数,value是存入的数据次数,这样就能很好的完成数据统计。 eg:
public class HashMapTest {
public static void main(String
[] args
) {
ArrayList
<Integer> list
= new ArrayList<>();
Random random
= new Random();
for (int i
= 0; i
< 100000; i
++) {
list
.add(random
.nextInt(1000)+1);
}
HashMap
<Integer, Integer> hashMap
= new HashMap<>();
Iterator
<Integer> iterator
= list
.iterator();
while (iterator
.hasNext()){
Integer key
= iterator
.next();
if (hashMap
.containsKey(key
)){
hashMap
.put(key
,hashMap
.get(key
)+1);
}
else {
hashMap
.put(key
,1);
}
Iterator
<Map
.Entry
<Integer, Integer>> iterator1
= hashMap
.entrySet().iterator();
while (iterator1
.hasNext()){
Map
.Entry
<Integer, Integer> next
= iterator1
.next();
Integer value
= next
.getValue();
Integer key1
= next
.getKey();
System
.out
.println(key
+" "+value
+" ");
}
System
.out
.println();
}
}
}