8.2 Redis核心数据结构

面试重要程度:⭐⭐⭐⭐⭐

常见提问方式: Redis底层实现、跳跃表原理、压缩列表优化

预计阅读时间:45分钟

🎯 String、Hash、List、Set、ZSet底层实现

String类型底层实现

面试官: "Redis的String类型底层是如何实现的?"

必答要点:

SDS(Simple Dynamic String)结构:

// Redis 6.0+ SDS结构
struct sdshdr8 {
    uint8_t len;        // 已使用长度
    uint8_t alloc;      // 总分配长度
    unsigned char flags; // 类型标识
    char buf[];         // 字符数组
};

// SDS相比C字符串的优势:
/**
 * 1. O(1)获取字符串长度(len字段)
 * 2. 杜绝缓冲区溢出(alloc字段控制)
 * 3. 减少修改字符串时的内存重分配次数
 * 4. 二进制安全(可存储任意数据)
 * 5. 兼容部分C字符串函数
 */

String类型编码方式:

/**
 * String类型的三种编码
 */
public class RedisStringEncoding {
    
    /**
     * 1. REDIS_ENCODING_INT
     * 条件:字符串是整数且在long范围内
     * 优势:节省内存,整数运算更快
     */
    public void intEncoding() {
        // SET counter 100
        // 存储为long类型,占用8字节
        jedis.set("counter", "100");
        jedis.incr("counter");  // 直接进行整数运算
    }
    
    /**
     * 2. REDIS_ENCODING_EMBSTR  
     * 条件:字符串长度 <= 44字节(Redis 6.0)
     * 优势:RedisObject和SDS在连续内存中,减少内存碎片
     */
    public void embstrEncoding() {
        // SET name "张三"
        // RedisObject和SDS一次分配,提高缓存局部性
        jedis.set("name", "张三");
    }
    
    /**
     * 3. REDIS_ENCODING_RAW
     * 条件:字符串长度 > 44字节
     * 特点:RedisObject和SDS分别分配内存
     */
    public void rawEncoding() {
        // SET description "很长的描述信息..."
        String longStr = "很长的描述信息".repeat(20);
        jedis.set("description", longStr);
    }
}

Hash类型底层实现

Hash类型的两种编码:

/**
 * Hash类型编码转换
 */
public class RedisHashEncoding {
    
    /**
     * 1. REDIS_ENCODING_ZIPLIST(压缩列表)
     * 条件:
     * - 所有键值对的键和值字符串长度都小于64字节
     * - 键值对数量少于512个
     */
    public void ziplistEncoding() {
        // HSET user:1 name "张三" age "25" city "北京"
        Map<String, String> user = new HashMap<>();
        user.put("name", "张三");
        user.put("age", "25");
        user.put("city", "北京");
        jedis.hmset("user:1", user);
        
        // 内存布局紧凑,节省空间
        // 但查找时间复杂度为O(n)
    }
    
    /**
     * 2. REDIS_ENCODING_HT(哈希表)
     * 条件:不满足ziplist条件时自动转换
     */
    public void hashtableEncoding() {
        // 当键值对数量或长度超过阈值时转换
        Map<String, String> largeUser = new HashMap<>();
        for (int i = 0; i < 600; i++) {
            largeUser.put("field" + i, "value" + i);
        }
        jedis.hmset("user:large", largeUser);
        
        // 查找时间复杂度为O(1)
        // 但内存开销较大
    }
}

List类型底层实现

List类型编码演进:

/**
 * List类型编码(Redis版本演进)
 */
public class RedisListEncoding {
    
    /**
     * Redis 3.0之前:ZIPLIST + LINKEDLIST
     */
    public void oldEncoding() {
        // 小数据量:ZIPLIST(压缩列表)
        // 大数据量:LINKEDLIST(双向链表)
        
        // 问题:两种数据结构差异大,维护复杂
    }
    
    /**
     * Redis 3.2+:QUICKLIST
     * 结合了ziplist和linkedlist的优点
     */
    public void quicklistEncoding() {
        // LPUSH mylist "a" "b" "c"
        jedis.lpush("mylist", "a", "b", "c");
        
        /**
         * QuickList结构:
         * - 由多个ziplist节点组成的双向链表
         * - 每个节点是一个ziplist
         * - 平衡了内存使用和操作效率
         */
    }
}

QuickList结构详解:

// QuickList节点结构
typedef struct quicklistNode {
    struct quicklistNode *prev;    // 前驱节点
    struct quicklistNode *next;    // 后继节点
    unsigned char *zl;             // ziplist指针
    unsigned int sz;               // ziplist字节数
    unsigned int count : 16;       // ziplist中元素个数
    unsigned int encoding : 2;     // 编码方式
    unsigned int container : 2;    // 容器类型
    unsigned int recompress : 1;   // 是否压缩
    unsigned int attempted_compress : 1; // 压缩尝试标志
    unsigned int extra : 10;       // 预留字段
} quicklistNode;

// QuickList主结构
typedef struct quicklist {
    quicklistNode *head;           // 头节点
    quicklistNode *tail;           // 尾节点
    unsigned long count;           // 总元素数量
    unsigned long len;             // 节点数量
    int fill : QL_FILL_BITS;      // 每个节点的填充因子
    unsigned int compress : QL_COMP_BITS; // 压缩深度
} quicklist;

Set类型底层实现

/**
 * Set类型的两种编码
 */
public class RedisSetEncoding {
    
    /**
     * 1. REDIS_ENCODING_INTSET(整数集合)
     * 条件:
     * - 所有元素都是整数
     * - 元素数量不超过512个
     */
    public void intsetEncoding() {
        // SADD numbers 1 2 3 4 5
        jedis.sadd("numbers", "1", "2", "3", "4", "5");
        
        /**
         * IntSet特点:
         * - 有序存储(便于二分查找)
         * - 根据整数范围选择编码(int16/int32/int64)
         * - 内存紧凑,查找效率O(log n)
         */
    }
    
    /**
     * 2. REDIS_ENCODING_HT(哈希表)
     * 条件:不满足intset条件时转换
     */
    public void hashtableEncoding() {
        // SADD tags "java" "redis" "mysql" "spring"
        jedis.sadd("tags", "java", "redis", "mysql", "spring");
        
        // 查找、插入、删除都是O(1)
        // 但内存开销较大
    }
}

ZSet类型底层实现

面试官: "有序集合ZSet是如何实现的?跳跃表的原理是什么?"

ZSet的两种编码:

/**
 * ZSet类型编码
 */
public class RedisZSetEncoding {
    
    /**
     * 1. REDIS_ENCODING_ZIPLIST
     * 条件:
     * - 元素数量少于128个
     * - 所有member长度小于64字节
     */
    public void ziplistEncoding() {
        // ZADD leaderboard 100 "player1" 200 "player2"
        Map<String, Double> scores = new HashMap<>();
        score

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

Java面试圣经 文章被收录于专栏

Java面试圣经,带你练透java圣经

全部评论
坐标南京,OD岗位多多,欢迎来聊
点赞 回复 分享
发布于 08-23 15:17 贵州

相关推荐

感觉初筛都过不去,但是没挂我,我就先等着吧
投递华为技术有限公司等公司10个岗位
点赞 评论 收藏
分享
09-16 15:32
已编辑
苏州大学 Java
点赞 评论 收藏
分享
08-20 19:20
已编辑
大连理工大学 数据产品
站队站对牛:92优势大的很 年少不知道学习好 工作时 惨不忍睹
点赞 评论 收藏
分享
评论
1
2
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务