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