Redis 几种常见的数据类型底层原理,前面跟小伙伴们已经扒了 String 和 List 了,今天我们再来看看 Set。
一、Java 里的 Set
从体验上来说,Redis 里边的 Set 有点像 Java 里的 Set,使用感觉差不多也是这个样子。
都是唯一,都是无序。
不过小伙伴们知道,我们 Java 里边的 Set,几种不同类型的 Set 底层都不约而同的指向了 Map。
HashSet
底层使用的是HashMap(实际上是一个专门的 IdentityHashMap),其中元素作为 key,对应的 value 通常是一个固定的对象(例如,PRESENT 对象),以此来实现元素的去重。
HashSet 提供了非常快的插入、删除和查找操作,平均时间复杂度为 O(1),但不保证元素的顺序。
TreeSet
- 底层使用的是 TreeMap,它是一个红黑树实现的有序映射。TreeSet 中的元素会被排序,排序依据可以是自然顺序(通过实现 Comparable 接口),也可以是用户提供的 Comparator。
- TreeSet 保证了元素的排序,同时也提供了有序集合的操作,如 headSet(), tailSet(), subSet() 等,时间复杂度通常为O(logn)。
LinkedHashSet
- 底层使用的是 LinkedHashMap,它结合了 HashMap 的快速查找特性和 LinkedHashMap 的有序性。
- LinkedHashSet 维护了元素的插入顺序,同时提供了快速的插入、删除和查找操作。时间复杂度通常为O(1)。
EnumSet
- 专为枚举类型的元素设计,底层使用位向量(bit vector)来存储元素,非常适合枚举类型的集合,提供了非常高效的存储和操作。
- EnumSet 在枚举类型元素的数量较少时非常高效,时间复杂度通常为O(1)。
当然这是 Java 里的,Redis 中和这个并不相同。
二、Redis 的 Set
Redis 中 Set 的底层要分情况来看。
整体上来说有两种:
- intset
- hashtable
具体使用哪种取决于集合中元素的数量和类型。
2.1 intset
当集合中的所有元素都是整数,并且集合的大小不超过 512 个元素时,Redis 会使用 intset 数据结构来存储集合。
intset 是一个无序的整数数组,它使用紧凑的方式存储整数元素,以节省内存。intset 的特点包括:
- 使用紧凑的存储方式,每个整数根据其大小占用最少的字节数(1 字节、2 字节、4 字节或 8 字节)。
- 支持快速的插入和删除操作。
- 不保证元素的顺序。
这里的 512 是咋来的呢?这个参数我们可以在 redis.conf 中进行配置,不配置的话默认是 512:
图片
来看个简单的例子。
所有元素都是整数,且元素个数不超过 512:
图片
有元素不是整数:
图片
可以看到,此时就是另外一种 hashtable 类型了。
具体到 intset 内部,又分为不同的编码格式。
- int16:这个是每两个字节表示一个整数,两个字节就是 16 位,表示的整数范围就是 (-2^15)~(2^15-1)
- int32:这个是每四个字节表示一个整数,四个字节就是 32 位,表示的整数范围就是 (-2^31)~(2^31-1)
- int64:这个是每八个字节表示一个整数,八个字节就是 64 位,表示的整数范围就是 (-2^63)~(2^63-1)
如果一开始使用的是 int16,后来存储的数据大小超了,那么 Redis 会自动将编码格式升级为 int32,并且将旧数据复制过来。
不过需要注意的是,编码格式可以从 int16 升级为 int32,但是并不会从 int32 降级为 int16,即使数据已经变为 1 了。
2.2 hashtable
当集合中的元素不是整数或者集合的大小超过了 512 时,Redis 会使用 hashtable(散列表)来存储集合。hashtable 是一种散列表实现,它可以高效地存储任意类型的数据,并提供快速的查找操作。hashtable 的特点包括:
- 支持任意类型的数据。
- 提供快速的插入、查找和删除操作。
- 不保证元素的顺序。
当集合的元素发生变化时,Redis 会自动转换数据结构以适应新的需求。例如,如果一个原本使用 intset 的集合添加了非整数元素,Redis 会将数据结构从 intset 转换为 hashtable。
Redis 如此煞费苦心,其实就一个目的,高效率的使用内存。