哈希表结合了顺序表和链表两者的优势,顺序表随机访问快,链表插入删除元素快。那么怎么将两者结合呢?
首先想要随机访问速度快,必须用顺序表,试想一下一个场景:
1.场景一
A往数组里填充了几个元素:4,2,66,89,1
将这个数组交给了B
B拿到数组后,想要获取元素:1,但是B不知道元素1具体在哪个索引下,所以B只能遍历数组,从而获取到了元素:1
时间复杂度 O(n) n为数组大小
2.场景二
B显然不愿意这样干,和A商量出了另一种方式:B先告诉A一个数字范围,他需要知道0-100之间的数字是否存在数组里
A初始化100长度的数组,然后将4,2,66,89,1索引下的值改为1,并将数组交给B
B获取到数组后,想要知道数组里是否有66,只需要判断下数组66索引下的值是否为1
时间复杂度 O(1)
3.场景三
现在又轮到A不乐意了,A觉得他为了几个数字,却要花销100个内存,于是又和B商量
最后,商量结果为:建立一个索引和数字之间的关系,哈希表就诞生了
搞明白了哈希表的结构后,理解它也十分简单,键值对中的key,代表了链表数组中的索引,通过hash算法获取索引,之后只需要O(1)的时间就可以获取到value,当然前提是该索引下的链表元素只有1个,如果存在多个元素,只需要遍历链表,对比key就可以获取到value。
存放元素也是同样道理,通过key获取到数组索引后,判断该索引下的链表是否为空,如果为空,直接存入,否则遍历链表,如果有key相同的,直接替换,没有key相同的放入链表头部
下面是一个简单的带有存放和获取的哈希表
class MyHashMap<K,V> {
//数据量
int size = 0;
//单链表数组
HashEntry<K,V>[] hashEntries;
//数组大小
int arrayLength;
public MyHashMap(int size) {
//初始化数组
hashEntries = new HashEntry[size];
arrayLength = hashEntries.length;
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public boolean containsKey(K key) {
return false;
}
public V get(K key) {
//获取hash值
int hash = Objects.hashCode(key);
//获取存放索引
int index = getIndex(hash);
//判断是否存在值
if(hashEntries[index] != null){
HashEntry ptr = hashEntries[index];
//轮询链表
while(ptr != null){
if(ptr.hashCode == hash && (key == ptr.key || key.equals(ptr.key))){//判断是否key相同
return (V) ptr.value;
}
ptr = ptr.next;
}
}
return null;
}
/**
* 放入键值对
*
* @param key
* @param value
* @return
*/
public V put(K key, V value) {
//获取hash值
int hash = Objects.hashCode(key);
//获取存放索引
int index = getIndex(hash);
//判断是否存在值
if (hashEntries[index] != null) {
HashEntry ptr = hashEntries[index];
HashEntry pre = null;
//轮询链表
while (ptr != null) {
if (ptr.hashCode == hash && (key == ptr.key || key.equals(ptr.key))) {//判断是否key相同
//相同,替换
HashEntry entry = new HashEntry(key, value, hash);
entry.next = ptr.next;
if (pre != null) {//前面的元素next指向新的元素
pre.next = entry;
}
return (V) ptr.value;
}
pre = ptr;
ptr = ptr.next;
}
}
//没有key重复的,放在链表头部,或者不存在直接存入
HashEntry entry = new HashEntry(key, value, hash);
entry.next = hashEntries[index];
hashEntries[index] = entry;
size++;
return null;
}
/**
* hash值转换索引
* @param hash
* @return
*/
private int getIndex(int hash){
return hash & (arrayLength - 1);
}
public V remove(K key) {
return null;
}
public void clear() {
}
public Set keySet() {
return null;
}
public Collection values() {
return null;
}
class HashEntry<K,V> {
K key;
V value;
//单链表
HashEntry next;
//hash值
int hashCode;
public HashEntry(K key, V value, int hashCode) {
this.key = key;
this.value = value;
this.hashCode = hashCode;
}
}
}
对于获取存放我们还可以做个LRU算法优化,使最近操作的都位于链表头
class MyHashMap<K, V> {
//数据量
int size = 0;
//单链表数组
HashEntry<K, V>[] hashEntries;
//数组大小
int arrayLength;
public MyHashMap(int size) {
//初始化数组
hashEntries = new HashEntry[size];
arrayLength = hashEntries.length;
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public boolean containsKey(K key) {
return false;
}
public V get(K key) {
//获取hash值
int hash = Objects.hashCode(key);
//获取存放索引
int index = getIndex(hash);
//判断是否存在值
if (hashEntries[index] != null) {
HashEntry ptr = hashEntries[index];
HashEntry pre = null;
//轮询链表
while (ptr != null) {
if (ptr.hashCode == hash && (key == ptr.key || key.equals(ptr.key))) {//判断是否key相同
if (pre != null) {
//删除之前的元素
removeLink(ptr, pre);
//放入链表头
ptr.next = hashEntries[index];
hashEntries[index] = ptr;
} else {//处在链表头部了
}
return (V) ptr.value;
}
pre = ptr;
ptr = ptr.next;
}
}
return null;
}
/**
* 放入键值对
*
* @param key
* @param value
* @return
*/
public V put(K key, V value) {
//获取hash值
int hash = Objects.hashCode(key);
//获取存放索引
int index = getIndex(hash);
//判断是否存在值
if (hashEntries[index] != null) {
HashEntry ptr = hashEntries[index];
HashEntry pre = null;
//轮询链表
while (ptr != null) {
if (ptr.hashCode == hash && (key == ptr.key || key.equals(ptr.key))) {//判断是否key相同
V oldValue = (V) ptr.value;
ptr.value = value;
if (pre != null) {
//删除之前的元素
removeLink(ptr, pre);
//放入链表头
ptr.next = hashEntries[index];
hashEntries[index] = ptr;
} else {//处在链表头部了
}
return oldValue;
}
pre = ptr;
ptr = ptr.next;
}
}
//没有key重复的,放在链表头部,或者不存在直接存入
HashEntry entry = new HashEntry(key, value, hash);
entry.next = hashEntries[index];
hashEntries[index] = entry;
size++;
return null;
}
/**
* hash值转换索引
*
* @param hash
* @return
*/
private int getIndex(int hash) {
return hash & (arrayLength - 1);
}
public V remove(K key) {
//获取hash值
int hash = Objects.hashCode(key);
//获取存放索引
int index = getIndex(hash);
//判断是否存在值
if (hashEntries[index] != null) {
HashEntry ptr = hashEntries[index];
HashEntry pre = null;
//轮询链表
while (ptr != null) {
if (ptr.hashCode == hash && (key == ptr.key || key.equals(ptr.key))) {//判断是否key相同
V oldValue = (V) ptr.value;
if (pre != null) {
//删除之前的元素
removeLink(ptr, pre);
}else{//没元素了
hashEntries[index] = null;
}
size--;
return oldValue;
}
pre = ptr;
ptr = ptr.next;
}
}
return null;
}
private void removeLink(HashEntry cur, HashEntry pre) {
if(pre == null || cur == null) return;
pre.next = cur.next;
}
public void clear() {
}
public Set keySet() {
return null;
}
public Collection values() {
return null;
}
class HashEntry<K, V> {
K key;
V value;
//单链表
HashEntry next;
//hash值
int hashCode;
public HashEntry(K key, V value, int hashCode) {
this.key = key;
this.value = value;
this.hashCode = hashCode;
}
}
}
简单的哈希表就到这边了,后续还要考虑扩容等功能的实现