[Redis数据结构|Java实现] 二:字典(Map)
[Redis数据结构|Java实现] 二:字典(Map)
概念:Redis中的字典,底层是使用hashtable实现,一个哈希表里面可以有多个 哈希表节点,每个哈希表节点都保存着一个键值对。
实现思路:
主要考虑以下几个问题:
-
哈希表怎么实现?
-
哈希表节点怎么定义?
-
Map的常规操作怎么实现?
- put实现
- get实现
- replace实现
-
怎么实现扩容?
哈希表实现:数组+链表
哈希表底层直接用数组+链表的经典实现方式即可。
为什么这样实现呢?
哈希表的实现主要要考虑以下两个方面?
- 计算好哈希值之后,就可以知道需要存储到对应桶的位置,那么如何快速定位到需要存储到的桶的位置。
- 对于哈希值同样的数据,怎么存储?(怎么解决哈希冲突)
解决方案:
针对第一个问题:
使用数组来存储桶,则计算好索引的位置之后,直接获取数组指定的位置即可,时间复杂度为O(1)。
第二个问题:
哈希值同样的数据,直接用链表存储即可。
哈希表节点定义
首先哈希表节点需要存储键值对,所以肯定需要保存key,value。
其次还需要保存它的下一个节点,所以肯定需要一个 next指针。(c里面叫指针)
所以可以定义成以下结构:
private static class Node{
String key;
Node next;
String val;
public Node(String key,String val){
this.key = key;
this.next = null;
this.val = val;
}
}
Map的常规操作实现
put实现
put的实现思路:put(key,val),首先计算key的hashcode,然后利用hashcode对数组的长度取模,得到桶的下标,然后去遍历这个桶里面的元素,如果没有同样的(equal判断),就加到这个桶保存的链表的尾部即可。
/**
* 这里设置,如果已经存在同样的键,则添加时直接失败,需要替换的话,直接调用replace函数
* @param key
* @param val
* @return
*/
public boolean put(String key,String val){
int index = key.hashCode()%buf.length;
Node node = new Node(key,val);
if(buf[index] == null){
buf[index] = node;
used++;
}else {
//使用链表存储
if(isContain(buf[index],key))
return false;
addOnLast(buf[index], node);
}
keyNumber++;
return true;
}
get实现(不要去遍历所有节点)
get的实现思路:get(key),首先计算key的hashcode,然后利用hashcode对数组的长度取模,得到桶的下标,然后去遍历这个桶里面的元素,如果没有同样的(equal判断),直接返回null,否则返回对应节点的val即可。
public String get(String key){
int index = key.hashCode()%buf.length;
if(buf[index] == null)
return null;
Node root = buf[index];
while (root != null){
if(root.key.equals(key))
return root.val;
root = root.next;
}
return null;
}
扩容实现
首先,为什么需要扩容?
因为单哈希冲突比较多时,一个桶中存储的链表可能会很长,这时候即使定位到了桶的位置,也需要遍历这个很长的链表,这会造成比较大的开销。
Redis里面扩容实现
Redis里面字典的扩容实现是比较独特的,它通过使用两个hashtable(buf,buf2),然后进行扩容。
具体操作:先计算负载因子,当负载因子满足一定值时,则执行扩容操作。
具体扩容:给buf2分配内存,将buf2当成buf,然后将原先buf中的数据重新添加即可,最后把buf设置为buf2,方便下一次扩容。
/**
*获取负载因子
*/
private int getLoadFactor(){
return used/buf.length;
}
/**
* 扩容操作
*/
public void rehash(){
if(getLoadFactor() == 0){
return;
}
buf2 = new Node[buf.length*2];//将其容量加倍
Node[] temp = buf;
buf = buf2;
this.used = 0;
this.keyNumber = 0;
buf2 = null;
for(int i=0;i< temp.length;i++){
Node root = temp[i];
if(root != null){
//统计
while (root != null){
put(root.key,root.val);
root = root.next;
}
}
}
}
完整代码:
package redis.Map;
/**
* Redis中字典的实现
*/
public class RedisMap {
private static final int DEFAULT_SIZE = 5;
private int used;
private int keyNumber;
//buf2用来做扩容操作
private Node[] buf,buf2;
public RedisMap(){
buf = new Node[DEFAULT_SIZE];
}
public RedisMap(int capacity