网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
$sudo apt-get update
$sudo apt-get install redis-server
启动Redis服务端
在终端输入以下命令启动 Redis 服务端:
$redis-server
启动Redis客户端
在终端输入以下命令启动 Redis 客户端:
#若设置了密码,使用如下格式启动
$redis-cli -h [ip] -p [port] -a [password]
#简单格式
$redis-cli
输入后,返回提示如下:
redis 127.0.0.1:6379>
注意:127.0.0.1 是本地计算机的 IP 地址,6379 是运行 Redis 服务器的默认端口号。
验证是否成功安装
在终端输出PING
命令,返回如下输出:
redis 127.0.0.1:6379> ping
PONG
上述输出说明 Redis 已成功安装在计算机上。
Linux源码包安装
除了上述方法外,您也可以在 Redis 官网下载源码包进行安装,下载地址:https://redis.io/download。下载完成后执行以下命令:
#解压、编译
$ tar xzf redis-5.0.4.tar.gz
$ cd redis-5.0.4
$ make
源码包安装完成后,存放在 src 目录下,执行下述命令启动 Redis。
$ cd src
$ ./redis-server
$ ./redis-cli
$ redis> set name www.biancheng.net
OK
$ redis> get name
"www.biancheng.net"
三、Linux下使用C/C++ API接口连接Redis简单示例
1.hredis安装
在linux下安装好Redis之后,我们是通过什么方法来调用Redis呢。
redis支持不同的编程语言,但是调用了不同的redis包,例如:java对应jedis;php对应phpredis;C++对应的则是hredis。
因此我们需要安转hredis。
实际上hiredis是一个c的接口,同样使用apt-get安装hiredis,GitHub上有他的完整工程项目,点此转到。
sudo apt-cache search hiredis // 查看发现c语言开发库为libhiredis-dev
libhiredis-dbg - minimalistic C client library for Redis (debug)
libhiredis-dev - minimalistic C client library for Redis (development files)
libhiredis0.13 - minimalistic C client library for Redis
python-hiredis - redis protocol reader for Python 2.X using hiredis
sudo apt-get install libhiredis-dev //选择并安装
hiredis库目录的位置为默认的 /usr/lib/x86_64-linux-gnu/下,头文件在 /usr/include/hiredis 下,hiredis头文件中定义了Redis的连接的方式redisConnect()等方法,连接信息存储在上下文redisContext的结构体对象中,通过redisCommand()等方法进行具体的数据库存取指令操作并返回相关信息在redisReply的结构体对象中,不要忘了freeReplyObject(void *reply)释放redisReply连接响应对象,redisFree()函数释放redisContext上下文对象,具体的定义和方法可以看源代码。
2.代码测试
新建一个临时目录
创建新文件redis.h
#ifndef \_REDIS\_H\_
#define \_REDIS\_H\_
#include <iostream>
#include <string.h>
#include <string>
#include <stdio.h>
#include <hiredis/hiredis.h>
class Redis
{
public:
Redis(){}
~Redis()
{
this->_connect = NULL;
this->_reply = NULL;
}
bool connect(std::string host, int port)
{
this->_connect = redisConnect(host.c\_str(), port);
if(this->_connect != NULL && this->_connect->err)
{
printf("connect error: %s\n", this->_connect->errstr);
return 0;
}
return 1;
}
std::string get(std::string key)
{
this->_reply = (redisReply\*)redisCommand(this->_connect, "GET %s", key.c\_str());
std::string str = this->_reply->str;
freeReplyObject(this->_reply);
return str;
}
void set(std::string key, std::string value)
{
redisCommand(this->_connect, "SET %s %s", key.c\_str(), value.c\_str());
}
private:
redisContext\* _connect;
redisReply\* _reply;
};
#endif //\_REDIS\_H\_
创建redis.cpp
#include "redis.h"
int main()
{
Redis \*r = new Redis();
if(!r->connect("127.0.0.1", 6379))
{
printf("connect error!\n");
return 0;
}
r->set("name", "Andy");
printf("Get the name is %s\n", r->get("name").c\_str());
delete r;
return 0;
}
编写Makefile文件
redis: redis.cpp redis.h
g++ redis.cpp -o redis -L/usr/local/lib/ -lhiredis
clean:
rm redis.o redis
进行编译
make
或者命令行执行
g++ redis.cpp -o redis -L/usr/local/lib/ -lhiredis
运行如果出现找不到动态链接库
在/etc/ld.so.conf.d/目录下新建文件usr-libs.conf,内容是:/usr/local/lib
最后执行,结果如下
Get the name is Andy
四、Redis中键值对中字符串的实现,用char*还是结构体?
字符串在我们平时的应用开发中十分常见,比如我们要记录用户信息、商品信息、状态信息等等,这些都会用到字符串。
而对于 Redis 来说,键值对中的键是字符串,值有时也是字符串。我们在 Redis 中写入一条用户信息,记录了用户姓名、性别、所在城市等,这些都是字符串,如下所示:
SET user: id:100 {“name”: “zhangsan”, “gender”: “M”,“city”:"beijing"}
此外,Redis 实例和客户端交互的命令和数据,也都是用字符串表示的。
那么,既然字符串的使用如此广泛和关键,就使得我们在实现字符串时,需要尽量满足以下三个要求:
- 能支持丰富且高效的字符串操作,比如字符串追加、拷贝、比较、获取长度等;
- 能保存任意的二进制数据,比如图片等;
- 能尽可能地节省内存开销。
其实,如果你开发过 C 语言程序,你应该就知道,在 C 语言中可以使用 char* 字符数组来实现字符串。同时,C 语言标准库 string.h 中也定义了多种字符串的操作函数,比如字符串比较函数 strcmp、字符串长度计算函数 strlen、字符串追加函数 strcat 等,这样就便于开发者直接调用这些函数来完成字符串操作。
所以这样看起来,Redis 好像完全可以复用 C 语言中对字符串的实现呀?
但实际上,我们在使用 C 语言字符串时,经常需要手动检查和分配字符串空间,而这就会增加代码开发的工作量。而且,图片等数据还无法用字符串保存,也就限制了应用范围。
那么,从系统设计的角度来看,我们该如何设计实现字符串呢?
其实,Redis 设计了简单动态字符串(Simple Dynamic String,SDS)的结构,用来表示字符串。相比于 C 语言中的字符串实现,SDS 这种字符串的实现方式,会提升字符串的操作效率,并且可以用来保存二进制数据。
所以今天这节课,我就来给你介绍下 SDS 结构的设计思想和实现技巧,这样你就既可以掌握 char* 实现方法的不足和 SDS 的优势,还能学习到紧凑型内存结构的实现技巧。如果你要在自己的系统软件中实现字符串类型,就可以参考 Redis 的设计思想,来更好地提升操作效率,节省内存开销。
好,接下来,我们先来了解下为什么 Redis 没有复用 C 语言的字符串实现方法。
为什么 Redis 不用 char*?
实际上,要想解答这个问题,我们需要先知道 char* 字符串数组的结构特点,还有 Redis 对字符串的需求是什么,所以下面我们就来具体分析一下。
char* 的结构设计
首先,我们来看看 char* 字符数组的结构。
char字符数组的结构很简单,就是一块连续的内存空间,依次存放了字符串中的每一个字符。比如,下图显示的就是字符串“redis”的char数组结构。
从图中可以看到,字符数组的最后一个字符是“\0”,这个字符的作用是什么呢?其实,C 语言在对字符串进行操作时,char* 指针只是指向字符数组的起始位置,而字符数组的结尾位置就用“\0”表示,意思是指字符串的结束。
这样一来,C 语言标准库中字符串的操作函数,就会通过检查字符数组中是否有“\0”,来判断字符串是否结束。比如,strlen 函数就是一种字符串操作函数,它可以返回一个字符串的长度。这个函数会遍历字符数组中的每一个字符,并进行计数,直到检查的字符为“\0”。此时,strlen 函数会停止计数,返回已经统计到的字符个数。下图显示了 strlen 函数的执行流程:
我们再通过一段代码,来看下“\0”结束字符对字符串长度的影响。这里我创建了两个字符串变量 a 和 b,分别给它们赋值为“red\0is”和“redis\0”。然后,我用 strlen 函数计算这两个字符串长度,如下所示:
#include <stdio.h>
#include <string.h>
int main()
{
char \*a = "red\0is";
char \*b = "redis\0";
printf("%lu\n", strlen(a));
printf("%lu\n", strlen(b));
return 0;
}
当程序执行完这段代码后,输出的结果分别是 3 和 5,表示 a 和 b 的长度分别是 3 个字符和 5 个字符。这是因为 a 中在“red”这 3 个字符后,就有了结束字符“\0”,而 b 中的结束字符是在“redis”5 个字符后。
也就是说,char* 字符串以“\0”表示字符串的结束,其实会给我们保存数据带来一定的负面影响。如果我们要保存的数据中,本身就有“\0”,那么数据在“\0”处就会被截断,而这就不符合 Redis 希望能保存任意二进制数据的需求了。
操作函数复杂度
而除了 char* 字符数组结构的设计问题以外,使用“\0”作为字符串的结束字符,虽然可以让字符串操作函数判断字符串的结束位置,但它也会带来另一方面的负面影响,也就是会导致操作函数的复杂度增加。
我还是以 strlen 函数为例,该函数需要遍历字符数组中的每一个字符,才能得到字符串长度,所以这个操作函数的复杂度是 O(N)。
我们再来看另一个常用的操作函数:字符串追加函数 strcat。strcat 函数是将一个源字符串 src 追加到一个目标字符串的末尾。该函数的代码如下所示:
char \*strcat(char \*dest, const char \*src) {
//将目标字符串复制给tmp变量
char \*tmp = dest;
//用一个while循环遍历目标字符串,直到遇到“\0”跳出循环,指向目标字符串的末尾
while(\*dest)
dest++;
//将源字符串中的每个字符逐一赋值到目标字符串中,直到遇到结束字符
while((\*dest++ = \*src++) != '\0' )
return tmp;
}
从代码中可以看到,strcat 函数和 strlen 函数类似,复杂度都很高,也都需要先通过遍历字符串才能得到目标字符串的末尾。然后对于 strcat 函数来说,还要再遍历源字符串才能完成追加。另外,它在把源字符串追加到目标字符串末尾时,还需要确认目标字符串具有足够的可用空间,否则就无法追加。
所以,这就要求开发人员在调用 strcat 时,要保证目标字符串有足够的空间,不然就需要开发人员动态分配空间,从而增加了编程的复杂度。而操作函数的复杂度一旦增加,就会影响字符串的操作效率,这就不符合 Redis 对字符串高效操作的需求了。
好了,综合以上在 C 语言中使用 char* 实现字符串的两大不足之处以后,我们现在就需要找到新的实现字符串的方式了。所以接下来,我们就来学习下,Redis 是如何对字符串的实现进行设计考虑的。
SDS 的设计思想
因为 Redis 是使用 C 语言开发的,所以为了保证能尽量复用 C 标准库中的字符串操作函数,Redis 保留了使用字符数组来保存实际的数据。但是,和 C 语言仅用字符数组不同,Redis 还专门设计了 SDS(即简单动态字符串)的数据结构。下面我们一起来看看。
SDS 结构设计
首先,SDS 结构里包含了一个字符数组 buf[],用来保存实际数据。同时,SDS 结构里还包含了三个元数据,分别是字符数组现有长度 len、分配给字符数组的空间长度 alloc,以及 SDS 类型 flags。其中,Redis 给 len 和 alloc 这两个元数据定义了多种数据类型,进而可以用来表示不同类型的 SDS,稍后我会给你具体介绍。下图显示了 SDS 的结构,你可以先看下。
另外,如果你在 Redis 源码中查找过 SDS 的定义,那你可能会看到,Redis 使用 typedef 给 char* 类型定义了一个别名,这个别名就是 sds,如下所示:
typedef char \*sds;
其实,这是因为 SDS 本质还是字符数组,只是在字符数组基础上增加了额外的元数据。在 Redis 中需要用到字符数组时,就直接使用 sds 这个别名。
同时,在创建新的字符串时,Redis 会调用 SDS 创建函数 sdsnewlen。sdsnewlen 函数会新建 sds 类型变量(也就是 char* 类型变量),并新建 SDS 结构体,把 SDS 结构体中的数组 buf[] 赋给 sds 类型变量。最后,sdsnewlen 函数会把要创建的字符串拷贝给 sds 变量。下面的代码就显示了 sdsnewlen 函数的这个操作逻辑,你可以看下。
sds sdsnewlen(const void \*init, size\_t initlen) {
void \*sh; //指向SDS结构体的指针
sds s; //sds类型变量,即char\*字符数组
...
sh = s\_malloc(hdrlen+initlen+1); //新建SDS结构,并分配内存空间
...
s = (char\*)sh+hdrlen; //sds类型变量指向SDS结构体中的buf数组,sh指向SDS结构体起始位置,hdrlen是SDS结构体中元数据的长度
...
if (initlen && init)
memcpy(s, init, initlen); //将要传入的字符串拷贝给sds变量s
s[initlen] = '\0'; //变量s末尾增加\0,表示字符串结束
return s;
好了,了解了 SDS 结构的定义后,我们再来看看,相比传统 C 语言字符串,SDS 操作效率的改进之处。
SDS 操作效率
因为 SDS 结构中记录了字符数组已占用的空间和被分配的空间,这就比传统 C 语言实现的字符串能带来更高的操作效率。
我还是以字符串追加操作为例。Redis 中实现字符串追加的函数是 sds.c 文件中的 sdscatlen 函数。这个函数的参数一共有三个,分别是目标字符串 s、源字符串 t 和要追加的长度 len,源码如下所示:
sds sdscatlen(sds s, const void \*t, size\_t len) {
//获取目标字符串s的当前长度
size\_t curlen = sdslen(s);
//根据要追加的长度len和目标字符串s的现有长度,判断是否要增加新的空间
s = sdsMakeRoomFor(s,len);
if (s == NULL) return NULL;
//将源字符串t中len长度的数据拷贝到目标字符串结尾
memcpy(s+curlen, t, len);
//设置目标字符串的最新长度:拷贝前长度curlen加上拷贝长度
![img](https://img-blog.csdnimg.cn/img_convert/e9a28ac4a4fb3e2ca20608a62587dd3a.png)
![img](https://img-blog.csdnimg.cn/img_convert/d08cc498edc45823837d571501b37cb8.png)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
sMakeRoomFor(s,len);
if (s == NULL) return NULL;
//将源字符串t中len长度的数据拷贝到目标字符串结尾
memcpy(s+curlen, t, len);
//设置目标字符串的最新长度:拷贝前长度curlen加上拷贝长度
[外链图片转存中...(img-nCLsB3ep-1715841674150)]
[外链图片转存中...(img-IsufKFBg-1715841674150)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**