说在前面:
-
哈希表的作用:
(1)实现快速查找
(2)实现缓存
(3)防止重复 -
前提:哈希函数和数组存值
(1)哈希函数,来确定该键所对应的确定数值(这个数值来标识数组的下标),且不能与其他建所生成的数值重复(不然就会出现哈希冲突)。但是世界上并不存在这种函数,只是尽量减少这种情况,如果出现就在确定下标的数组里面存储一个链表地址,这个链表上面是存储的这两个发生冲突的数值。这时候就会发现查询这个位置上的key时速度会降低。
(2)数组,也就是哈希表,这个数组是用来存储与key对应的数值,通过使用哈希函数计算出入的key值,得到确定的数组下标,于是就向这个下标处存值,查找数据时,通过计算输入的Key值,得到下标,直接去这个下标处的数组拿值,这也是快速查找的原因。 -
尽量减少出现哈希冲突
(1)选择更好的哈希函数:目前使用的哈希函数SHA(安全哈希算法),时FIPS公认的
(2)较小的填装因子:当前数组中元素的个数 / 数组长度=填装因子,尽量的减少了不同的Key得到相同的下标值。在最开始时设置临界填充因子,当元素数量达到,就新建一个更长的数组,将元素拷贝到新的数组里面。一般默认临界因子为0.7。 -
JDK中实现的哈希表HashMap
4.1 在Java中通过Entry[] 数组来表示哈希表,每一Key-Vlue则是存在Entry中,比较有趣的是Entry这个类其实是单链表结构,通过这个结构来解决哈希冲突的问题(拉链法),每一个Entry中除了Key-Value属性外,还有next(Entry类型)用来存放它下一个节点的Entry。在hashmap中,属性有Entry[],和阈值,数组大小,加载因子,还有modCount,实现呢fail-fast机制
4.2 HashMap的默认大小是16,填充因子是0.75,当目前填充的数量大于等于阀值(容量大小乘以填充因子)的时候,开始扩容。
4.3 HashMap 在JDK1.7的基础上进行升级,当链表长度超过8时候,链表变成红黑树(将链表转换成红⿊树前会判断,如果当前数组的⻓度⼩于 64,那么会选择先进⾏数组扩容,⽽不是转换为红⿊树) -
同样是哈希表的HashMap和HashTable,HashTable里面的Key和Value不能为Null,而HashMap却可以。可以知道如果使用map.get(null),这样很没意思,所以最开始出现的HashTable没有在get()方法中没有特殊设置。后来人们又觉得可以有且只有一个Key的值是Null,也就在HashMap中的get方法 中进行了设置。
HashTable中的get()
public synchronized V get(Object key) {
Entry tab[] = table;
int hash = key.hashCode();
// 计算索引值,
int index = (hash & 0x7FFFFFFF) % tab.length;
// 找到“key对应的Entry(链表)”,然后在链表中找出“哈希值”和“键值”与key都相等的元素
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return e.value;
}
}
return null;
}
HashMap中的设置
public V get(Object key) {
if (key == null)
return getForNullKey();
// 获取key的hash值
int hash = hash(key.hashCode());
// 在“该hash值对应的链表”上查找“键值等于key”的元素
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
// 获取“key为null”的元素的值
// HashMap将“key为null”的元素存储在table[0]位置!
private V getForNullKey() {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
- 解决Hash冲突的四个常见方法
(1)开放地址法:当发生冲突时,以当前地址P在(按照某个规律)做计算得到下一个地址,如果下一个地址还是冲突,再次用P得到另外一个地址,直到不再发生冲突。
(2)再哈希法:预设不同的哈希算法,如果这个算法得到的值冲突,用下一个算法计算。
(3)拉链法:如果发生冲突,就在这个位置后面创建一个链,值依次添加到这个链的结尾处
(4)建立公共溢出区:如果发生冲突,就将值放入溢出表中。
- 补充HashMap