目录
一,环境配置
1.1 介绍
1.2 安装hiredis
1.3 安装redis-plus-plus
1.4 连接服务器
二,使用通用命令
2.0 StringView,和OptionalString类型
2.1 set,get,
2.2 exists,del
2.3 keys
2.4 expire,ttl
2.5 type
三,使用string命令
3.1 string的set和get
3.2 set的NX 和 EX
3.3 mset,mget
3.4 getrange,setrange
3.5 incr,decr
编辑
四,使用list命令
4.1 lpush,lrange
4.2 rpush,lpop,rpop
4.3 blpop,brpop
4.4 llen
五,set命令
5.1 sadd,smembers
5.2 sismember,scard,spop
5.3 sinter,sinterstore
六,使用hash命令
6.1 hset,hget
6.2 hexists,hdel,hlen
6.3 hkeys,hvals
6.4 hmset,hmget
七,使用zset命令
7.1 zadd,zrange
7.2 zcard,zrem
7.3 zscore,zrank
八,小结
有很多操作Redis的第三方库,这里我们主要使用“redis-plus-plus”这个,因为这些库虽然很多,但大多数都是大同小异,网址为:sewenew/redis-plus-plus: Redis client written in C++ (github.com)
所以我们一共需要安装两个东西,我们可以直接用Linux的包管理器安装hiredis和redis-plus-plus,Ubuntu版本使用apt即可,Centos版本使用yum即可,关于这两个Linux前面也已经介绍过:Linux操作系统基础开发工具的使用——vim,gcc/g++,MakeFile,gdb,yum/apt_vim makefile-CSDN博客
①OK我们先来安装hiredis:
Ubuntu:
sudo apt install libhiredis-dev
Centos:
sudo yum install hiredis-devel.x86_64
②接下来我们安装redis-plus-plus的本体,这个安装比较麻烦,只能通过源码编译去安装了,这里只演示Ubuntu环境下的安装:
首先是下载源码:
sudo git clone https://github.com/sewenew/redis-plus-plus.git
关于cmake:
- redis-plus-plus是使用cmake作为构建工具的,cmake先当与是makefile的升级版
- makefile本身功能比较简陋,比较原始,写起来也比较麻烦,实际开发中很少会去手写makefile
- 所以我们一般通过程序来生成makefile,cmake就是一个生成makefile的工具
- cmake好比是C语言,makefile好比是汇编语言
sudo apt install cmake
下面是编译的具体步骤,下面的各种操作最好都加上sudo或者直接切换成root用户:
然后就可以直接使用make进行编译,需要的时间可能要一会儿,最后生成的动静态库如下:
后续写代码不一定能找到这里的库,所以推荐把这些库,拷贝到系统目录中,而且这步操作也不用我们自己搞,直接使用下面的命令即可:
make install
可以直接把内容拷贝到系统目录里了,包括链接等过程
很多C++中的库,都是需要编译安装的,而具体的操作大多类似
接下来就是使用redis-plus-plus连接服务器了,再使用ping命令,来检测一下连通性:
直接在自己认为的合适位置创建test目录,创建hello.cc源码,使用VSCode开始编写代码
首先要包含redis的入口头文件,一般放在下面目录中:
hello.cc代码如下:
#include //尖括号是在系统目录中搜索头文件,引号是在项目目录中搜索头文件 #include //sw和Redis没有直接关系,sw是作者名字的缩写,日常写代码的时候,尽量不要用缩写,容易误会 #include using std::cout; using std::endl; using sw::redis::Redis; // 使用Redis库提供的类,用这个类去创建对象 int main() { Redis redis("tcp://127.0.0.1:6379"); // 在构造函数中指定redis服务器的地址和端口,就是一个URL,唯一资源定位符 std::string result = redis.ping(); // 给Redis服务器发送ping请求,使用result接收,是字符串类型 cout << result << endl; return 0; }
注意:在使用makefile时需要引入库文件:
makefile如下:
hello:hello.cc g++ -o $@ $^ -std=c++17 /usr/local/lib/libredis++.a /usr/lib/x86_64-linux-gnu/libhiredis.a -pthread .PHONY:clean clean: rm hello
运行后输出PONG,这和Redis客户端的输出结果一样
表示连接成功,后续我们就是通过上面代码的Redis类里面的各种方法去操作Redis服务器的,而且那些方法和我们前面的Redis的命令是相配的
Redis远程字典服务器(2) —— 全局命令-CSDN博客
先看set函数的参数:
再看一下get函数的返回值:
#include #include using namespace std; using sw::redis::Redis; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); // 清空数据库,避免历史数据污染结果 redis.set("key1", "hello1"); // 返回值也和命令行一样,返回一个bool类型,后面的大部分命令的返回值都是和命令行一样的 redis.set("key2", "hello2"); redis.set("key3", "hello3"); auto value1 = redis.get("key1"); // 返回hello1 auto value4 = redis.get("key4"); // cout << "value1 = " << value1 << endl; //直接这样写会报错 // cout << "value4 = " << value4 << endl; //直接这样写会报错 // 次数value1和value4都是optional类型,但是sw::redis::Optional 不支持 << 运算符重载 // 此处我们也不需要给optional 再搞一个重载,可以把optional 当做只包含一个元素的容器,然后把这个元素取出来即可 cout << "value4 = " << value4.value() << endl; cout << "value1 = " << value1.value() << endl; return 0; }
编译能通过了,但是执行又报错了:
这是因为value4 是optional 的非法状态,那么optional此时就无法进行取值,会抛异常,导致程序崩溃,最后打印的结果是 Aborted,就是出发了Linux的SIGABRT信号,也就是6号信号
如何解决?可以使用try catch来捕获,但是实际上C++中不太经常用try catch,因为:
可以使用if判断来解决,因为optional可以隐式类型转换成bool类型的
#include #include using namespace std; using sw::redis::Redis; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); // 清空数据库,避免历史数据污染结果 redis.set("key1", "hello1"); // 返回值也和命令行一样,返回一个bool类型,后面的大部分命令的返回值都是和命令行一样的 redis.set("key2", "hello2"); redis.set("key3", "hello3"); auto value1 = redis.get("key1"); // 返回hello1 auto value4 = redis.get("key4"); if (value1) { cout << "value1 = " << value1.value() << endl; } if (value4) { cout << "value4 = " << value4.value() << endl; } return 0; }
#include #include using namespace std; using sw::redis::Redis; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); redis.set("hello", "world"); cout << redis.exists("hello") << endl; redis.del("hello"); cout << redis.exists("hello") << endl; // 同时exists也可以同时判断多个key是否存在 redis.set("hello1", "world1"); redis.set("hello2", "world2"); cout << redis.exists({"hello1", "hello2", "hello3"}) << endl; // 返回的数字为存在的key的个数 return 0; }
keys这个操作和我们前面的操作还是有明显区别的,主要是体现在返回值上,因为keys返回的是“多个”值,先来看下keys的参数:
再创建一个头文件,方便打印容器的值:
util.hpp:
#pragma once #include #include #include template inline void printContainer(const T &container) { for (const auto &elem : container) { std::cout << elem << std::endl; } }
hello.cc:
#include "util.hpp" #include using namespace std; using sw::redis::Redis; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); redis.set("hello1", "world1"); redis.set("hello2", "world2"); redis.set("hello3", "world3"); vector result; auto it = std::back_inserter(result); // std::back_insert_iterator> it 这个就是插入迭代器 redis.keys("*", it); printContainer(result); return 0; }
下面我们深入了解一下插入迭代器:
STL中的五种迭代器类型:输入迭代器,输出迭代器,前向迭代器,双向迭代器,随机访问迭代器;而我们说的“插入迭代器”,也是一种“输出迭代器”
通常一个迭代器,主要是表示一个“位置”,插入迭代器则是一个“位置” + “动作”,库中提供的插入迭代器主要是三个:
我们一般不会直接去使用这几个迭代器去构造对象,因为构造函数写起来比较麻烦,所以我们一般会使用一些辅助的函数来进行构造,比如上面代码的 std::back_inserter(result); 这个就是辅助构造back_insert_iterator 的函数
所以对于插入迭代器来说,任何的*,++等操作都是啥也不干,它的核心操作就是赋值运算,也就是“ = ”,把另一个迭代器赋值给这个插入迭代器:
- 假设现在有两个迭代器,it 是插入迭代器,it2 是普通迭代器
- 当it = it2 时,就相当于 it 获取到 it2 的元素,然后按照 it 当前插入迭代器的“位置” 和 “动作”来进行执行插入操作
- 比如 it 是一个 back_insert_iterator ,就是把 it2 指向的元素插入到 it 指向的容器末尾
- 相当于调用了一次 push_back 尾插
问题:为什么不直接使用容器做参数?keys直接内部操作函数进行插入就可以了, 为啥还要通过迭代器绕一个大圈子呢?
解答:为了“解耦合”。再使用前,keys和容器两者都是互不可见的,此时双方都使用迭代器这样一个中间媒介进行交互,就可以形成“一对多”的情况,keys这个函数就可以搭配更多的容器来使用了,能提高代码的健壮性
补充:
#include "util.hpp" #include #include #include #include using namespace std; using sw::redis::Redis; using namespace std::chrono_literals; // 字面值常量的命名空间 int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); redis.set("hello", "world"); //redis.expire("hello", std::chrono::seconds(10)); // 可以使用库中的这个类型,也可以直接用设数字10,表示long long类型,但是为了可读性,建议使用库中的这个类型写 redis.expire("hello", 10s); //但是当c++11后就可以直接用字面值常量来代替上一条语句的类型了 for (int i = 10; i >= 0; i--) { cout << redis.ttl("hello") << endl; // sleep(1); std::this_thread::sleep_for(1s); // 字面值常量 } return 0; }
#include "util.hpp" #include using namespace std; using sw::redis::Redis; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); redis.set("hello1", "world1"); cout << redis.type("hello1") << endl; // 打印string redis.lpush("hello2", "222"); cout << redis.type("hello2") << endl; // 打印list redis.hset("hello3", "aaa", "111"); cout << redis.type("hello3") << endl; // 打印hash return 0; }
Redis远程字典服务器(4)—— string类型详解_redis的string类型-CSDN博客
用法其实和上面的差不多
#include "util.hpp" #include using namespace std; using sw::redis::Redis; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); redis.set("hello1", "world1"); auto value = redis.get("hello1"); if (value) { cout << value.value() << endl; } redis.set("hello1", "value1"); // 可以对key进行修改 value = redis.get("hello1"); if (value) { cout << value.value() << endl; } return 0; }
#include "util.hpp" #include using namespace std; using sw::redis::Redis; using namespace std::chrono_literals; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); // set 的重载版本中,没有单独提供NX 和 XX 的版本,必须搭配过期时间的版本来使用 redis.set("hello1", "world1", 0s, sw::redis::UpdateType::NOT_EXIST); auto value = redis.get("hello1"); if (value) { cout << value.value() << endl; } return 0; }
mget返回的是Optional类型,所以需要改一下util.hpp头文件:
#pragma once #include #include #include template inline void printContainer(const T &container) { for (const auto &elem : container) { std::cout << elem << std::endl; } } template inline void printContainerOptional(const T &container) { for (const auto &elem : container) { if (elem) // optional可能为无效值,所以需要先判断一下 { std::cout << elem.value() << std::endl; } else { std::cout << "元素无效" << std::endl; } } }
hello.cc:
#include "util.hpp" #include using namespace std; using sw::redis::Redis; using namespace std::chrono_literals; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); // 写法①:使用初始化列表一次性描述多个键值对 redis.mset({make_pair("key1", "111"), make_pair("key2", "222"), make_pair("key3", "333")}); // 写法②:先把多个键值对提前组织到一个容器中,以迭代器的形式告诉mset vector> keys = { {"key4", "444"}, {"key5", "555"}, {"key6", "666"}}; redis.mset(keys.begin(), keys.end()); // mget获取多个key的value vector result; auto it = std::back_inserter(result); redis.mget({"key1", "key2", "key3", "key4", "key5", "key6"}, it); printContainerOptional(result); return 0; }
#include "util.hpp" #include using namespace std; using sw::redis::Redis; using namespace std::chrono_literals; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); redis.set("key", "1234567890"); string result = redis.getrange("key", 2, 5); // getrange获取字符串的一部分,返回的是string,如果是空的话也返回空字符串 cout << result << endl; redis.setrange("key", 2, "abc"); result = redis.getrange("key", 0, -1); cout << result << endl; return 0; }
#include "util.hpp" #include using namespace std; using sw::redis::Redis; using namespace std::chrono_literals; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); redis.set("key", "10"); redis.incr("key"); string result = redis.getrange("key", 0, -1); cout << result << endl; redis.decr("key"); result = redis.getrange("key", 0, -1); cout << result << endl; return 0; }
注意:
- incr的decr得到的是long long类型,get得到的是 OptionalString 类型,我们一般使用long long类似更多一些,因为数字比较好操作,而OptionalString要进行计算还得手动转成数字
Redis远程字典服务器(6) —— list类型详解-CSDN博客
#include "util.hpp" #include using namespace std; using sw::redis::Redis; using namespace std::chrono_literals; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); // lpush插入元素 redis.lpush("key", "111"); // 插入单个元素 redis.lpush("key", {"222", "333", "444"}); // 初始化列表插入多个元素 vector values = {"555", "666", "777"}; redis.lpush("key", values.begin(), values.end()); // 使用容器迭代器插入 // lrange 获取列表中的元素 vector result; auto it = back_inserter(result); redis.lrange("key", 0, -1, it); printContainer(result); return 0; }
rpush和前面的lpush是一样的:
#include "util.hpp" #include using namespace std; using sw::redis::Redis; using namespace std::chrono_literals; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); // lpush插入元素 redis.rpush("key", {"1", "2", "3", "4"}); // 初始化列表插入多个元素 auto result = redis.rpop("key"); // 尾删 if (result) { cout << "rpop尾删的元素为:" << result.value() << endl; } result = redis.lpop("key"); // 头删 if (result) { cout << "lpop头删的元素为:" << result.value() << endl; } // lrange 获取列表中的元素 vector value; auto it = back_inserter(value); redis.lrange("key", 0, -1, it); printContainer(value); return 0; }
两个操作是一样的,所以我们只演示一个就好了,就是当列表为空时,就阻塞住,所以我们主要是来演示一下它的阻塞效果。
先来看下blpop的返回值类型:
#include "util.hpp" #include using namespace std; using sw::redis::Redis; using namespace std::chrono_literals; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); while (true) { auto result = redis.blpop({"key", "key2", "key3"}); // 最后面也可以加上超时时间 if (result) { std::cout << "key: " << result.value().first << endl; std::cout << "elem: " << result->second << endl; // 对于 std::optional 类型,也可以直接使用 " -> ",来访问它内部包含的元素的成员 } } return 0; }
#include "util.hpp" #include using namespace std; using sw::redis::Redis; using namespace std::chrono_literals; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); redis.lpush("key", {"111", "222", "333", "444"}); long long len = redis.llen("key"); cout << len << endl; return 0; }
Redis远程字典服务器(7)—— set类型详解_redis set add-CSDN博客
#include "util.hpp" #include #include using namespace std; using sw::redis::Redis; using namespace std::chrono_literals; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); redis.sadd("key", "111"); // 一次添加一个元素 redis.sadd("key", {"222", "333", "444"}); // 一次添加多个元素 std::set elems = {"555", "666", "777"}; redis.sadd("key", elems.begin(), elems.end()); // 用容器迭代器添加多个元素 // smembers获取set中的元素 set result; //会报错,原因见后面“注意” vector result; auto it = back_inserter(result); redis.smembers("key", it); printContainer(result); return 0; }
注意:
按一般情况来说,使用 set 来保存 smembers 的返回值会更好,但是将vector换成set之后,会报错 :
#include "util.hpp" #include #include using namespace std; using sw::redis::Redis; using namespace std::chrono_literals; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); redis.sadd("key", "111"); // 一次添加一个元素 redis.sadd("key", {"222", "333", "444"}); // 一次添加多个元素 std::set elems = {"555", "666", "777"}; redis.sadd("key", elems.begin(), elems.end()); // 用容器迭代器添加多个元素 // smembers获取set中的元素 set result; // vector result; // 由于set里的元素顺序是固定的,指定一个end()还是begin(),都是可以的 auto it = inserter(result, result.end()); redis.smembers("key", it); printContainer(result); return 0; }
#include "util.hpp" #include #include using namespace std; using sw::redis::Redis; using namespace std::chrono_literals; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); redis.sadd("key", {"111", "222", "333", "444"}); // 一次添加多个元素 bool result1 = redis.sismember("key", "111"); // 判断值是否存在,返回值为bool cout << result1 << endl; auto result2 = redis.spop("key"); // 返回值也是OptionalString,返回被删除的元素,是随机删除 if (result2) { cout << result2.value() << endl; } long long result3 = redis.scard("key"); // 返回元素个数 cout << result3 << endl; return 0; }
求交集,并集,差集,这三个步骤其实都一样,所以只介绍一个就可以辣~
#include "util.hpp" #include #include using namespace std; using sw::redis::Redis; using namespace std::chrono_literals; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); redis.sadd("key1", {"111", "222", "333"}); redis.sadd("key2", {"222", "333", "444"}); set result1; auto it1 = std::inserter(result1, result1.end()); redis.sinter({"key1", "key2"}, it1); // 使用初始化列表一次搞进多个key,第二个参数是插入迭代器 printContainer(result1); cout << "----------" << endl; redis.sinterstore("key3", {"key1", "key2"}); set result2; auto it2 = inserter(result2, result2.end()); redis.smembers("key3", it2); printContainer(result2); return 0; }
Redis远程字典服务器(5) —— hash类型详解_hash使用-CSDN博客
#include "util.hpp" #include using namespace std; using sw::redis::Redis; using namespace std::chrono_literals; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); redis.hset("key", "f1", "111"); // 插入一个 redis.hset("key", make_pair("f2", "222")); // 使用pair插入 redis.hset("key", {make_pair("f3", "333"), make_pair("f4", "444")}); // 使用初始化列表插入多个,初始化列表里面每个元素都是pair类型 vector> fields = { make_pair("f5", "555"), make_pair("f6", "666")}; redis.hset("key", fields.begin(), fields.end()); // 通过容器迭代器插入 auto result = redis.hget("key", "f1"); if (result) { cout << result.value() << endl; } return 0; }
#include "util.hpp" #include using namespace std; using sw::redis::Redis; using namespace std::chrono_literals; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); redis.hset("key", "f1", "111"); redis.hset("key", "f2", "222"); redis.hset("key", "f3", "333"); bool result1 = redis.hexists("key", "f1"); // 判断field-value是否存在,返回值为bool cout << result1 << endl; long long result2 = redis.hdel("key", {"f1", "f2"}); // 删除field,可以一次删多个返回值为long long,表示删除的个数 cout << result2 << endl; long long result3 = redis.hlen("key"); cout << result3 << endl; return 0; }
#include "util.hpp" #include using namespace std; using sw::redis::Redis; using namespace std::chrono_literals; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); redis.hset("key", "f1", "111"); redis.hset("key", "f2", "222"); redis.hset("key", "f3", "333"); vector fields; auto it1 = back_inserter(fields); redis.hkeys("key", it1); // 获取hash类型的所有fields printContainer(fields); vector values; auto it2 = back_inserter(values); redis.hvals("key", it2); printContainer(values); return 0; }
#include "util.hpp" #include using namespace std; using sw::redis::Redis; using namespace std::chrono_literals; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); redis.hmset("key", {make_pair("f1", 111), make_pair("f2", 222)}); vector> pairs = {make_pair("f3", "333"), make_pair("f4", "444")}; redis.hmset("key", pairs.begin(), pairs.end()); vector values; auto it = back_inserter(values); redis.hmget("key", {"f1", "f2", "f3", "f4"}, it); printContainer(values); return 0; }
Redis远程字典服务器(8)—— zset类型详解_redis zset 性能-CSDN博客
注意:
zrange支持两种风格的插入迭代器:
- 只查询 member ,不带 score
- 查询 member,带上score
关键就是看插入迭代器指向的容器的类型:
- 指向的容器只有一个string,就是只查询 member
- 指向的容器包含的是pair,里面有string 和 double ,就是查询member带上score
#include "util.hpp" #include using namespace std; using sw::redis::Redis; using namespace std::chrono_literals; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); redis.zadd("key", "hello1", 99.5); // 一次添加一个 redis.zadd("key", {make_pair("hello2", 95), make_pair("hello3", 97)}); // 初始化列表一次添加多个 vector> members = { make_pair("hello4", 90), make_pair("hello5", 85)}; redis.zadd("key", members.begin(), members.end()); // 通过容器迭代器插入多个 vector members1; // 只查member auto it1 = back_inserter(members1); redis.zrange("key", 0, -1, it1); printContainer(members1); cout << "----------" << endl; vector> members2; // 查member带score auto it2 = back_inserter(members2); redis.zrange("key", 0, -1, it2); printContainerPair(members2); return 0; }
#include "util.hpp" #include using namespace std; using sw::redis::Redis; using namespace std::chrono_literals; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); redis.zadd("key", "hello1", 91); redis.zadd("key", "hello2", 92); redis.zadd("key", "hello3", 93); long long result = redis.zcard("key"); // 获取元素个数 cout << result << endl; redis.zrem("key", "hello1"); // 删除的三个重载和zadd一样,单个删除,初始化列表和容器迭代器删除多个 result = redis.zcard("key"); cout << result << endl; return 0; }
#include "util.hpp" #include using namespace std; using sw::redis::Redis; using namespace std::chrono_literals; int main() { Redis redis("tcp://127.0.0.1:6379"); redis.flushall(); redis.zadd("key", "hello1", 91); redis.zadd("key", "hello2", 92); redis.zadd("key", "hello3", 93); auto score = redis.zscore("key", "hello1"); // 查询分数 // zscore的返回值是OptionalDouble,这个其实和OptionalString是差不多的,都可以表示无效值 if (score) { cout << score.value() << endl; } auto rank = redis.zrank("key", "hello3"); // 查询元素的排序,或者是下标 // 返回值是OptionalLongLong,也是老样子 if (rank) { cout << rank.value() << endl; } return 0; }
下一篇:Linux线程管理