哈希表
是一种kv的数据结构,插入查找都是O(1)的
哈希函数
用来生成key的哈希值的函数,一般都是根据类成员的哈希值再相加
jdk中的HashMap
哈希表的一种实现,插入和查找都是依据 key的哈希函数生成的哈希值来确定位置的。冲突时使用的是拉链法
put
主要流程:
- 调用哈希函数获取哈希值,对数组长度取余,找到对应的桶。
如果桶中无元素,直接放入
如果桶中有元素,再看是否和当前元素和已有元素是否相同,
如果相同,则覆盖原有的值
如果不相同,则使用拉链法,在链表尾部插入一个节点或者把链表变成树
下面结合代码来解释:
1 | static final int hash(Object key) { |
get
主要流程是
- 先用哈希函数找到对应的桶,如果已经存在,再遍历链表或者树
下面结合代码来解释:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public 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 && // always check first node
((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;
}
扩容
主要流程
- 扩容大小为原来2倍,更新阈值为容量*loadFactor
- 找到在新数组中的位置,这个有个简单的数学原理
由于扩容是总是扩容2倍,一开始大小也是2的幂次,故大小总是为2的幂次
设当前大小为newsize,则hash % newsize = hash & (newsize-1)
; 因为比newsize低的位都会是余数的位。
更进一步,设某个桶在原数组中下标为x,在新数组中下标为y,原数组大小为oldsize,则x,y满足原因还是因为比数组大小低的位哪些会对余数产生贡献。1
2
3
4
5
6hash % oldsize=x
if (hash & oldsize ==1 ){
y = x + oldsize//分支1
}else{
y = x//分支2
}
根据上面原理,在拷贝原数组的元素时,只需要将桶后面的链表拆分成2部分,一部分属于分支1,一部分属于分支2,放入对应的桶中即可
下面结合代码来解释
1 | final Node<K,V>[] resize() { |