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圣经