Redis的八种数据类型介绍

news/2024/7/7 1:21:12 标签: redis, 数据库, 缓存

Redis 是一个高性能的键值存储,它支持多种丰富的数据类型。每种数据类型都有其特定的用途和底层实现。下面我将介绍 Redis 支持的主要数据类型及其背后的数据结构。
本人这里还有几篇详细的Redis用法文章,可以用来进阶康康!

1. 字符串 (String)

数据结构

字符串是 Redis 中最基本的数据类型,可以是文本、数字、二进制数据等。它在 Redis 内部是以动态字符串 (SDS, Simple Dynamic String) 实现的。SDS 除了字符串本身,还有额外的属性来缓存长度。

  • 简单动态字符串 (SDS)
    • length:当前已使用的字符串长度。
    • free:未使用的预分配空间长度。
    • buf:实际存储字符串的缓冲区。
用法示例
Jedis jedis = new Jedis("localhost");
jedis.set("key", "value");  // 设置键值对
String value = jedis.get("key");  // 获取值
System.out.println(value);
jedis.close();

2. 哈希 (Hash)

数据结构

哈希是键值对的集合,适合表示对象的属性。最常用的是用来存储对象。内部实现主要有两种:

  • 压缩列表 (ziplist):当哈希数量较少且每个键值对的长度较短时会使用。
  • 哈希表 (hashtable):当哈希数量增多或其中某个键或值变长后,自动转为哈希表。
用法示例
Jedis jedis = new Jedis("localhost");
jedis.hset("user:1000", "name", "John");
jedis.hset("user:1000", "age", "30");
String name = jedis.hget("user:1000", "name");
System.out.println(name);
jedis.close();

3. 列表 (List)

数据结构

列表是一组有序的字符串,可以从列表的头部或尾部添加或删除元素。内部实现有以下两种:

  • 压缩列表 (ziplist):元素数量较少且每个元素较短时使用。
  • 双向链表 (linkedlist):元素数量多时,会自动转为双向链表。
用法示例
Jedis jedis = new Jedis("localhost");
jedis.lpush("tasks", "Task1");  // 从头部插入
jedis.rpush("tasks", "Task2");  // 从尾部插入
String task = jedis.lpop("tasks");  // 从头部弹出
System.out.println(task);
jedis.close();

4. 集合 (Set)

数据结构

集合是无序且唯一的字符串集合。内部采用哈希表 (hashtable) 实现,哈希表的键是集合的值,值是 null。

用法示例
Jedis jedis = new Jedis("localhost");
jedis.sadd("tags", "java");
jedis.sadd("tags", "redis");
jedis.sadd("tags", "java");  // 不会重复添加
Set<String> tags = jedis.smembers("tags");
System.out.println(tags);
jedis.close();

5. 有序集合 (Sorted Set)

数据结构

有序集合类似集合,但每个元素都会关联一个分数(score),元素按分数排序。内部使用一种结构:跳表 (skiplist) 和哈希表 (hashtable) 结合。

  • 跳表 (skiplist):快速实现范围查询和排序。
  • 哈希表 (hashtable):快速定位元素。
用法示例
Jedis jedis = new Jedis("localhost");
jedis.zadd("leaderboard", 100, "Player1");
jedis.zadd("leaderboard", 200, "Player2");
Set<String> topPlayers = jedis.zrange("leaderboard", 0, -1);  // 按分数排序获取元素
System.out.println(topPlayers);
jedis.close();

6. 位图 (Bitmap)

数据结构

位图将字符串值视为一个位数组,可以进行位操作。内部存储采用字符串,最大长度可达 512 MB。

  • 字节数组 (byte array):实际存储位图数据的结构,是一个连续的内存区域。
用法示例
Jedis jedis = new Jedis("localhost");
jedis.setbit("user:active", 1, true);  // 设置第2位为1
boolean isActive = jedis.getbit("user:active", 1);
System.out.println(isActive);
jedis.close();

7. HyperLogLog

数据结构

HyperLogLog 是基数估算数据结构,用于估算不重复元素的数量。利用概率算法实现,误差率约 0.81%。用于大规模数据去重计数。

  • 稀疏矩阵 (Sparse Representation):当元素少时,用压缩方法存储。
  • 密集矩阵 (Dense Representation):当元素多到一定程度时,转为密集表示。
用法示例
Jedis jedis = new Jedis("localhost");
jedis.pfadd("unique_visitors", "user1");
jedis.pfadd("unique_visitors", "user2");
jedis.pfadd("unique_visitors", "user1");
long uvCount = jedis.pfcount("unique_visitors");
System.out.println(uvCount);
jedis.close();

8. 地理空间 (Geospatial)

数据结构

Redis 的地理空间扩展允许存储地理坐标,并提供地理范围查询、距离计算等功能。内部使用有序集合 (Sorted Set) 数据类型实现,利用 GeoHash 编码。

  • GeoHash 编码 (geohash):将地理坐标编码为字符串,保证地理位置靠近的两个编码也是靠近的。
  • 跳表 (skiplist) 和哈希表 (hashtable):与有序集合共享的实现,支持半径查询和排序。
用法示例
Jedis jedis = new Jedis("localhost");
jedis.geoadd("locations", 13.361389, 38.115556, "Palermo");
jedis.geoadd("locations", 15.087269, 37.502669, "Catania");

List<GeoCoordinate> coordinates = jedis.geopos("locations", "Palermo");
System.out.println(coordinates);

double distance = jedis.geodist("locations", "Palermo", "Catania", GeoUnit.KM);
System.out.println(distance);
jedis.close();

结论

Redis 提供了丰富的数据结构,每种数据结构都有其特定的应用场景和优势。在实践中,可以根据具体需求选择合适的数据类型,提高系统性能与效率。了解这些数据结构的底层实现有助于更好地理解和使用 Redis,优化数据存储和操作。

另(面试突出加分项):
跳表(SkipList)是一种用于有序集合的高效数据结构,支持快速的插入、删除和查找操作。它的时间复杂度为 O(log N),接近于平衡二叉树,但实现相对简单。跳表是 Redis 中 Sorted Set(有序集合)数据类型的主要底层实现之一。

在这里插入图片描述

跳表的数据结构

跳表由多层有序链表组成,下层链表包含所有元素,每一层链表是其下一层链表的抽样。跳表的最底层是一条完整的有序链表,从头到尾包含所有的元素。每一层往上减少一部分元素,通过跳过一些元素使操作更高效。

  • Level 0:包含所有节点,构成最底层链表。
  • Level 1:包含部分节点,跳过一些元素,形成较高层次的链表。
  • Level k:最高层链表,包含最少的元素。

跳表的节点

每个节点包含多个指针,每个指针指向该层中的下一个节点。节点结构如下:

  • score:用于排序的分数。
  • member:实际存储的值。
  • forward pointers:指向各层下一个节点的指针数组。

跳表的操作

跳表支持插入、删除和查找操作,具体步骤如下:

插入操作
  1. 从最高层开始,找到要插入的位置。
  2. 随机生成节点的高度(层数)。
  3. 将新节点插入到对应的每一层链表中。
删除操作
  1. 从最高层开始,找到要删除的节点。
  2. 更新指针,跳过该节点。
  3. 逐层删除该节点。
查找操作
  1. 从最高层开始,根据分数找到目标位置。
  2. 如果当前层找不到,降到下一层继续查找。
  3. 直到找到目标节点或确认节点不存在。

跳表在 Redis 中的实现

以下是 Redis 跳表的数据结构和主要操作的实现:

节点结构
typedef struct zskiplistNode {
    struct zrobj *obj; // 存储成员对象(value)
    double score;      // 分数,用于排序
    struct zskiplistNode *backward; // 后退指针
    struct zskiplistLevel {
        struct zskiplistNode *forward; // 前进指针
        unsigned int span;             // 跨度
    } level[]; // 层指针数组
} zskiplistNode;
跳表结构
typedef struct zskiplist {
    struct zskiplistNode *header, *tail; // 头尾指针
    unsigned long length;                // 节点数量
    int level;                           // 当前最大层数
} zskiplist;
插入操作
zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj) {
    // 临时存储每一层的前一个节点
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL];
    zskiplistNode *x;
    int i, level;
    
    x = zsl->header;
    for (i = zsl->level-1; i >= 0; i--) {
        while (x->level[i].forward && 
               (x->level[i].forward->score < score ||
               (x->level[i].forward->score == score &&
                compareStringObjects(x->level[i].forward->obj, obj) < 0))) {
            x = x->level[i].forward;
        }
        update[i] = x;
    }
    
    level = zslRandomLevel(); // 随机生成节点层数
    if (level > zsl->level) {
        for (i = zsl->level; i < level; i++) {
            update[i] = zsl->header;
        }
        zsl->level = level;
    }

    x = zslCreateNode(level, score, obj); // 创建新节点
    for (i = 0; i < level; i++) {
        x->level[i].forward = update[i]->level[i].forward;
        update[i]->level[i].forward = x;
    }
    x->backward = (update[0] == zsl->header ? NULL : update[0]);
    if (x->level[0].forward)
        x->level[0].forward->backward = x;
    else
        zsl->tail = x;
    zsl->length++;
    return x;
}
删除操作
int zslDelete(zskiplist *zsl, double score, robj *obj) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    int i;
    
    x = zsl->header;
    for (i = zsl->level-1; i >= 0; i--) {
        while (x->level[i].forward && 
               (x->level[i].forward->score < score ||
               (x->level[i].forward->score == score &&
                compareStringObjects(x->level[i].forward->obj, obj) < 0))) {
            x = x->level[i].forward;
        }
        update[i] = x;
    }

    x = x->level[0].forward;
    if (x && score == x->score && compareStringObjects(x->obj, obj) == 0) {
        for (i = 0; i < zsl->level; i++) {
            if (update[i]->level[i].forward == x) {
                update[i]->level[i].forward = x->level[i].forward;
            }
        }
        if (x->level[0].forward) {
            x->level[0].forward->backward = x->backward;
        } else {
            zsl->tail = x->backward;
        }
        while (zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL) {
            zsl->level--;
        }
        zsl->length--;
        zslFreeNode(x);
        return 1;
    }
    return 0;
}
查找操作
zskiplistNode *zslFind(zskiplist *zsl, double score, robj *obj) {
    zskiplistNode *x = zsl->header;
    int i;

    for (i = zsl->level-1; i >= 0; i--) {
        while (x->level[i].forward && 
               (x->level[i].forward->score < score ||
               (x->level[i].forward->score == score &&
                compareStringObjects(x->level[i].forward->obj, obj) < 0))) {
            x = x->level[i].forward;
        }
    }
    x = x->level[0].forward;
    if (x && score == x->score && compareStringObjects(x->obj, obj) == 0) {
        return x;
    }
    return NULL;
}

跳表的优点

  1. 高效性:查找、插入、删除的平均时间复杂度为 O(log N)。
  2. 简单性:相较于平衡树,跳表的实现和维护更为简单。
  3. 灵活性:能够快速应对动态变化的数据,有较好的适应性。

在 Redis 中的应用

跳表主要用于 Redis 的有序集合 (Sorted Set) 数据类型。通过跳表,可以高效实现按分数排序的多种操作,如范围查询、排名查询等。它与哈希表结合使用,实现了元素的快速访问和分数范围内的高效查询。


http://www.niftyadmin.cn/n/5534760.html

相关文章

[SwiftUI 开发] 嵌套的ObservedObject中的更改不会更新UI

1. 发生问题的demo 业务逻辑代码 class Address: ObservableObject {Published var street "123 Apple Street"Published var city "Cupertino" }class User: ObservableObject {Published var name "Tim Cook"Published var address Addr…

【课程设计】基于python的一款简单的计算器

我们是大二本科生团队&#xff0c;主力两人耗时3天完成了这款计算器的制作。希望大家给我们多多引流&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 欢迎各位优秀的高考学子报考长安大学&#xff0c;报考长安大学电子信息工程专业。 欢迎有志于就…

【面试题】网络IO多路复用模型之异步事件

目录 异步事件模型的概念 工作流程&#xff1a; WSAEventSelect模型的优势和不足 代码&#xff1a; 异步事件模型的概念 WSAEventSelect模型是WindowsSockets提供的另外一个有用的异步I/O模型。该模型允许一个或多个套接字上接收以事件为基础的网络事件通知。Windows Sock…

UNIAPP_顶部导航栏右侧添加uni-icons图标,并绑定点击事件,自定义导航栏右侧图标

效果 1、导入插件 uni-icons插件&#xff1a;https://ext.dcloud.net.cn/plugin?nameuni-icons 复制 uniicons.ttf 文件到 static/fonts/ 下 仅需要那个uniicons.ttf文件&#xff0c;不引入插件、单独把那个文件下载到本地也是可以的 2、配置页面 "app-plus":…

中俄汽车产业链合作前景广阔,东方经济论坛助力双边合作与创新

随着中国汽车零部件企业的竞争力和创新能力不断增强&#xff0c;中国汽车及零部件行业在俄罗斯的市场份额和品牌影响力显著提升&#xff0c;中俄两国在汽车产业链上的合作展现出巨大的潜力和广阔的前景。2024年5月&#xff0c;俄罗斯乘用车新车销量达到12.8万辆&#xff0c;同比…

Elasticsearch环境搭建|ES单机|ES单节点模式启动|ES集群搭建|ES集群环境搭建

文章目录 版本选择单机ES安装与配置创建非root用户导入安装包安装包解压配置JDK环境变量配置single-node配置JVM参数后台启动|启动日志查看启动成功&#xff0c;访问终端访问浏览器访问 Kibana安装修改配置后台启动|启动日志查看浏览器访问 ES三节点集群搭建停止es服务域名配置…

SpringBoot设置自动跳转前端界面

一般情况下&#xff0c;我们的Application启动文件的内容为一行的运行代码&#xff0c;默认启动项目以后不会自动跳转到我们的前端页面 public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);} 这里我的可以通过设置文件的内容&#…

一文告诉你旅游类软文如何写更吸引人

又放暑假啦~仅限于除上班族以外的栋梁们&#xff01;面对这长达两个月的假期&#xff0c;总会听到有人感慨“世界这么大&#xff0c;我想出去看看”。生活和工作上的双重压力让大家总是期盼寻觅到释放之处&#xff0c;而旅游成为了人们心灵的一剂解药&#xff0c;相信不少人已经…