文章目录
一、基础结构对比
数据结构定义
Java HashMap
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
// 底层数组
transient Node<K,V>[] table;
// 元素数量
transient int size;
// 修改次数,用于快速失败机制
transient int modCount;
// 扩容阈值
int threshold;
// 负载因子
final float loadFactor;
// 基本节点类型
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
// 红黑树节点
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent;
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev;
boolean red;
// ...
}
}
Redis字典
typedef struct dict {
dictType *type; // 哈希表类型,包含各种操作函数指针
void *privdata; // 私有数据
dictht ht[2]; // 两个哈希表,支持rehash
int rehashidx; // rehash索引,-1表示没有进行rehash
int iterators; // 安全迭代器计数
} dict;
typedef struct dictht {
dictEntry **table; // 哈希表数组
unsigned long size; // 哈希表大小
unsigned long sizemask; // 掩码,等于size-1
unsigned long used; // 哈希表中已有节点数量
} dictht;
typedef struct dictEntry {
void *key; // 键
union {
// 值可以是多种类型
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next; // 下一个节点
} dictEntry;
主要区别与设计思路
-
多态实现方式:
- Java:使用泛型和继承实现多态
- Redis:使用函数指针(dictType)实现不同类型的操作
-
哈希表数量:
- Java:仅一个哈希表数组
- Redis:两个哈希表,用于渐进式rehash
设计原因:Redis作为数据库需要保持稳定响应时间,一次性rehash可能导致较长停顿
-
节点结构:
- Java:有普通节点和树节点两种
- Redis:仅有一种节点类型
设计原因:Java HashMap追求在高冲突下的性能保证,而Redis通过控制负载因子和良好的哈希函数减少冲突
二、关键操作API对比
初始化
Java HashMap
// 默认构造函数
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // 0.75
}
// 指定初始容量
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
// 指定初始容量和负载因子
public HashMap(int initialCapacity, float loadFactor) {
// 参数检查
if (initialCapacity < 0) throw new IllegalArgumentException("...");
if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("...");
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity); // 计算为大于initialCapacity的最小2的幂
}
Redis字典
// 创建字典
dict *dictCreate(dictType *type, void *privDataPtr) {
dict *d = zmalloc(sizeof(*d));
_dictInit(d, type, privDataPtr);
return d;
}
// 初始化字典
int _dictInit(dict *d, dictType *type, void *privDataPtr) {
_dictReset(&d->ht[0]); // 重置第一个哈希表
_dictReset(&d->ht[1]); // 重置第二个哈希表
d->type = type;
d->privdata = privDataPtr;
d->rehashidx = -1; // 表示没有进行rehash
d->iterators = 0;
return DICT_OK;
}
差异分析:
- Java提供多个构造函数满足不同初始化需求,Redis采用工厂方法模式
- Java直接在构造时设定扩容阈值,Redis延迟到实际需要扩容时
- Java的负载因子作为实例变量存储,Redis使用全局变量控制
添加元素
Java HashMap
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 判断是否需要初始化表
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 计算索引并判断槽位是否为空
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
// 第一个节点就是要找的key
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 检查是否为树节点
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// 遍历链表
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 检查是否需要树化
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 更新已存在的key的值
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
// 增加修改计数
++modCount;
// 检查是否需要扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
Redis字典
int dictAdd(dict *d, void *key, void *val) {
dictEntry *entry = dictAddRaw(d, key);
if (!entry) return DICT_ERR;
dictSetVal(d, entry, val);
return DICT_OK;
}
dictEntry *dictAddRaw(dict *d, void *key) {
int index;
dictEntry *entry;
dictht *ht;
// 如果正在rehash,执行一步渐进式rehash
if (dictIsRehashing(d)) _dictRehashStep(d);
// 获取新元素的索引,如果key已存在则返回-1
if ((index = _dictKeyIndex(d, key)) == -1)
return NULL;
// 选择使用哪个哈希表(rehash时用ht[1],否则用ht[0])
ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
entry = zmalloc(sizeof(*entry));
// 头插法将新节点插入链表
entry->next = ht->table[index];
ht->table[index] = entry;
ht->used++;
// 设置键
dictSetKey(d, entry