第四章 散列(解决查找问题)
1、散列是一种用于以常数平均时间执行插入、删除和查找的技术。它解决的是数组的值的快速查找问题。
2、它解决的是查询的时候可以达到O(1)的时间复杂度。因为它底层是一个数组。所以可以随机的定位。
3、散列的底层是数组,但又不同于普通的数组。它牺牲了排序性,同样findMin\findMax这样的方法也不适用。它提高的是对值的查找效率。使得数值和索引下标可以进行改换。这个不同于普通数组。
一、hash结构
从上图我们可以看出,这种hash结构有两部分组成,数组和链表。(jdk1.7是基于数组+链表。jdk1.8是数组+红黑树)
每个数组元素里面存的是链表。
二、实现方式
从上图我们可以总结出以下流程:
还有几个关键字:
hashCode、hash、哈希冲突、存储
我们把流程进行分解:
1、得到key的hashCode()值
对于java每个类都默认实现了hashCode()方法如下:
- Object类的hashCode返回对象的内存地址经过处理后的结构,由于每个对象的内存地址都不一样,所以哈希码也不一样。
- String类的hashCode根据String类包含的字符串的内容,根据一种特殊算法返回哈希码,只要字符串所在的堆空间相同,返回的哈希码也相同。
- Integer类,返回的哈希码就是Integer对象里所包含的那个整数的数值
2、进行hash()得到索引下标
private int myHash(T x){
int hashVal = x.hashCode();
hashVal % = theLists.length; /theLists.length一定是一个素数,不了值更分散
if(hashVal <0)
hashVal += theLists.length;
return hashVal;
}
//得到根据hash取模得到索引位置
//private int hash(T key) {
// return (key.hashCode() & 0x7fffffff) % M;
// }
0x7fffffff 表示0后面31个1.
&按位与 目地是把hashCode的值去除符号。都表示正的。
最后取M的模,存入相应的地址中去。
取模是有技巧的,对于素数取模才能让索引更加分散。
3、存储数值
这个比较简单,根据索引下标,找到该索引内存储的LinkedList对象。进行add()
当然这里面也涉及到rehash的过程。也就是说,当hash结构存储的size==theLists.length时,进行rehash.重新开辟一块数组内存空间。对hash结构的所有元素重新进行hash存储。
一般是2倍的当前length,取合适的素数。
代码如下:
//添加元素
public void add(T key) {
int index = this.hash(key);
MyLinkedList myLinkedLists = theLists[index];
if (!myLinkedLists.contains(key)) {
myLinkedLists.add(key);
size++;
if (size > M) { //以空间换时间
this.resize(2 * theLists.length);
}
}
}
//扩容
private void resize(int newM) {
MyLinkedList<T>[] newLists = new MyLinkedList[newM];
//先初始化newLists的元素
for (int i = 0; i < newM; i++) {
newLists[i] = new MyLinkedList<T>();
}
//遍历所有元素,重新存储
for (int i = 0; i < theLists.length; i++) {
MyLinkedList<T> myLinkedList = theLists[i];
for (int j = 0; j < myLinkedList.size(); j++) {
T key = myLinkedList.get(0);
newLists[hash(key)].add(key);
}
}
this.M = newM;
this.theLists = newLists;
}
三、总结
ConcurrentHashMap:
https://blog.csdn.net/yy1098029419/article/details/79672753