哈希表笔记(四)Redis对比Java总结

一、基础结构对比

数据结构定义

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;

主要区别与设计思路

  1. 多态实现方式

    • Java:使用泛型和继承实现多态
    • Redis:使用函数指针(dictType)实现不同类型的操作
  2. 哈希表数量

    • Java:仅一个哈希表数组
    • Redis:两个哈希表,用于渐进式rehash

    设计原因:Redis作为数据库需要保持稳定响应时间,一次性rehash可能导致较长停顿

  3. 节点结构

    • 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值