当前位置: 首页 > article >正文

C++编写Redis客户端

目录

安装redis-plus-plus库

​编辑

编译C++redis客户端

redis的通用命令使用 

get/set

exists

del

keys

expire /ttl

type

string类型核心操作 

set和get

set带有超时时间

set带有NX

string带有XX

mset

mget

getrange和setrange

incr和decr 

list类型核心操作

lpush和lrange

 lpop和rpop

brpop

llen

set类型核心操作

sadd和smembers

sismember

 scard

spop

sinter

sinterstore

hash类型核心操作

hset和hget

 hexists和hdel和hlen

hmset和hmget

hkeys hvals

zset类型核心操作

zadd和zrange

zscore 

zrank


安装redis-plus-plus库

C++操作redis的库有很多,这里使用redis-plus-plus,我们需要在githb上下载。

github地址:GitHub - sewenew/redis-plus-plus: Redis client written in C++

github在国内的时候有时候会抽风,网不好,怎么呢?fq当然可以打开github,但fq是违法行为。

那有没有合法的方式打开呢?我们可以下载Watt Toolkit软件,这玩意其实就是一个游戏加速器。

 打开之后点击网络加速,这里面可以对github进行加速。

打开github后往下面翻,可以看到下载,要下载redis-plus-plus要先安装hiredis。

 安装hiredis

ubuntu

apt install libhiredis-dev

 

 安装redis-plus-plus

下载redis-plus-plus源

git clone https://github.com/sewenew/redis-plus-plus.git

安装好源之后,按照下面的命令执行即可 

cd redis-plus-plus

mkdir build  // 创建一个 build、 目录是习惯用法,让编译临时生成的文件放在改目录下

cd build

cmake ..      // 生成makefile

make           // 进行编译

make install // 把库考到系统目录

cd ..

安装完毕后,头文件会装到 /usr/local/include/sw/的redis++。

编译C++redis客户端

要写redis代码需要包含一下头文件<sw/redis++/redis++.h>。

当我们创建好Redis对象之后,执行的各种命令,其实就是该类中调用各种方法即可。

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <sw/redis++/redis++.h>using  namespace std;int main()
{// 创建redis对象的时候需要再构造函数中指定 redis 服务器的地址和端口 由于我们redis客户端和服务器在同一个主机,写本地回环即可sw::redis::Redis redis("tcp://127.0.0.1:6379"); // 如果是不同主机,就写对应主机的ip即可,6379是端口号// 测试是否连接成功 让客户端给服务器发送一个 ping 服务器会返回一个 PONG,通过返回值获取到string result = redis.ping();cout << result << endl;return 0;}

使用makefile编译文件

编译程序的时候,需要引入库文件。

1. redis++自己的静态库  

我们可以使用find命令查找。

/usr/local/lib/libredis++.a

2.hiredis的静态库

/usr/lib/x86_64-linux-gnu/libhiredis.a 

3.线程库 

-pthread直接写就可以

hello:01.hello.ccg++ -o $@ $^ -std=c++17 /usr/local/lib/libredis++.a  /usr/lib/x86_64-linux-gnu/libhiredis.a -pthread.PHONY:clean
clean:rm -f hello

redis的通用命令使用 

如果会使用redis的命令,那么使用C++编写redis客户端是非常简单的,很多都是接口都是一样的设计,命令的参数怎么写的的函数的参数就怎么传递就行。下面我并不会介绍每个函数的作用是啥,因为默认各位都是对命令了然于胸的,如果看到哪一个命令想不起来它的作用,随便上网查一下即可,或者是看下代码应该就能想起来,并且我不会把所有的命令都写出来,因为有些命令是非常简单的。

get/set


void test1(sw::redis::Redis &redis)
{cout << "get 和 set 的使用" << endl;// 1.清空一下数据库,以免影响我们使用redis.flushall();// 使用 set 设置 keyredis.set("key1", "111");redis.set("key2", "222");redis.set("key3", "333");// 使用 get 获取 key// get的返回值是sw::redis::OptionalString类型auto value1 = redis.get("key1");// optional 可以隐式转换成 bool 类型,可以直接在 if中判断,如果是无效元素,就返回false// 使用之前判断是否存在if (value1){// get的返回值是sw::redis::OptionalString类型,cout不支持该类型的运算符重载// 此处把sw::redis::OptionalString里面包含的值取出来就行,OptionalString只包含一个元素的容器// 可以使用value()方法取出来cout << "key1: " << value1.value() << endl;}auto value2 = redis.get("key2");if (value2){cout << "key2: " << value2.value() << endl;}auto value3 = redis.get("key3");if (value3){cout << "key3: " << value3.value() << endl;}// redis中并不存在key4auto value4 = redis.get("key4");if (value4){cout << "key4: " << value4.value() << endl;}
}

 

exists

// exists
void test2(sw::redis::Redis &redis)
{cout << "exists" << endl;// 1.清空一下数据库,以免影响我们使用redis.flushall();// 使用 set 设置 keyredis.set("key1", "111");// 存在的key// 返回值是一个long long类型,表示有几个key存在auto ret = redis.exists("key1");cout << ret << endl;// 不存在的keyret = redis.exists("key2");cout << ret << endl;// 判定多个 keyredis.set("key3", "333");ret = redis.exists({"key1", "key2", "key3"});cout << ret << endl;
}

del

void test3(sw::redis::Redis &redis)
{cout << "del" << endl;// 1.清空一下数据库,以免影响我们使用redis.flushall();// 使用 set 设置 keyredis.set("key1", "111");redis.set("key2", "222");// 删除一个key// 返回值是成功删除的个数auto ret = redis.del("key1");cout << ret << endl; // key1 存在返回值为1 表示删除1个// 删除多个keyret = redis.del({"key2", "key3"});cout << ret << endl; // key2 存在 key3 不存在 返回值2 表示删除2个// 判定被删除的key是否存在ret = redis.exists({"key1", "key2"}); // key1 key2都被删除了,因此存在个数0cout << ret << endl;
}

keys


void test4(sw::redis::Redis &redis)
{cout << "keys" << endl;// 1.清空一下数据库,以免影响我们使用redis.flushall();// 使用 set 设置 keyredis.set("key1", "111");redis.set("key2", "222");redis.set("key3", "333");redis.set("key4", "444");redis.set("key5", "555");redis.set("key6", "666");// keys第一个参数匹配规则// keys第二个参数是一个"插入迭代器",需要先准备好一个保存结果的容器// 接下来在创建一个插入迭代器指向容器的位置,就可以吧keys获取到的结果依次通过刚才的插入迭代器插入到容器指定位置中vector<string> v;// 这个auto的类型是 back_inserter<std::vector<std::string>>// 此处的it就指向v的起始地址auto it = back_inserter(v);// 这个地方是把keys查询的所有结果插入到v这个容器中redis.keys("*", it);// keys查询的key都放到v这个容器中// 遍历vector,查看结果for (auto &elem : v){cout << elem << endl;}
}

expire /ttl

void test5(sw::redis::Redis &redis)
{cout << "expire and ttl" << endl;// 1.清空一下数据库,以免影响我们使用redis.flushall();// 使用 set 设置 keyredis.set("key1", "111");// 设置key的过期时间redis.expire("key1", 10);                  // 这里可以直接跟字面值表示10sredis.expire("key1", chrono::seconds(10)); // 建议使用chrono的库函数,更加直观// 先休眠一会再查看过期时间this_thread::sleep_for(chrono::seconds(5)); // 休眠5s// 获取key的过期时间long long time = redis.ttl("key1");cout << time << endl; // 5s后,key1的过期时间还剩5s
}

type

void test6(sw::redis::Redis &redis)
{cout << "type" << endl;// 1.清空一下数据库redis.flushall();// 2. string 类型redis.set("key1", "111");string type = redis.type("key1");cout << "key1: " << type << endl;// 3. list 类型redis.lpush("key2", "111");type = redis.type("key2");cout << "key2: " << type << endl;// 4. hash 类型redis.hset("key3", "field1", "111");type = redis.type("key3");cout << "key3: " << type << endl;// 5. set 类型redis.sadd("key4", "111");type = redis.type("key4");cout << "key4: " << type << endl;// 6. zset 类型redis.zadd("key5", "吕布", 99);type = redis.type("key5");cout << "key5: " << type << endl;
}

string类型核心操作 

set和get

void test1(sw::redis::Redis &redis)
{cout << "get 和 set 的使用" << endl;// 1.清空一下数据库,以免影响我们使用redis.flushall();redis.set("key", "111");// value是optionalString类型,需要使用value()方法取出来auto value = redis.get("key");if (value){std::cout << "value: " << value.value() << std::endl;}// 修改key的valueredis.set("key", "222");value = redis.get("key");if (value){std::cout << "value: " << value.value() << std::endl;}}

set带有超时时间

void test2(sw::redis::Redis &redis)
{cout << "set 带有超时时间" << endl;// 1.清空一下数据库,以免影响我们使用redis.flushall();// 给key设置超时时间为9sredis.set("key", "111", chrono::seconds(9));// 休息6sthis_thread::sleep_for(chrono::seconds(6));// 获取超时时间auto time = redis.ttl("key");cout << "time: " << time << endl;
}

 

set带有NX

void test3(sw::redis::Redis &redis)
{cout << "set NX 和 XX" << endl;// 1.清空一下数据库,以免影响我们使用redis.flushall();// 包含该命名空间可以直接使用 0s 这样的字面值常量using namespace std::chrono_literals;// set的重载版本中,没有单独提供 NX 和 XX版本,必须搭配过期时间使用,不需要过期时间就设置为0s// 不存在就设定redis.set("key", "111", 0s, sw::redis::UpdateType::NOT_EXIST);auto value = redis.get("key");if (value){cout << "value: " << value.value() << endl;}else{cout << "key 不存在!" << endl;}
}

void test3(sw::redis::Redis &redis)
{cout << "set NX 和 XX" << endl;// 1.清空一下数据库,以免影响我们使用redis.flushall();// 包含该命名空间可以直接使用 0s 这样的字面值常量using namespace std::chrono_literals;redis.set("key", "111");// set的重载版本中,没有单独提供 NX 和 XX版本,必须搭配过期时间使用,不需要过期时间就设置为0s// 上面已经设置了key,所有下面set不会设置成功redis.set("key", "222", 0s, sw::redis::UpdateType::NOT_EXIST);auto value = redis.get("key");if (value){// 打印的结果是111cout << "value: " << value.value() << endl;}else{cout << "key 不存在!" << endl;}
}

string带有XX

void test3(sw::redis::Redis &redis)
{cout << "set NX 和 XX" << endl;// 1.清空一下数据库,以免影响我们使用redis.flushall();// 包含该命名空间可以直接使用 0s 这样的字面值常量using namespace std::chrono_literals;redis.set("key", "111");// set的重载版本中,没有单独提供 NX 和 XX版本,必须搭配过期时间使用,不需要过期时间就设置为0s// 已经存在才能设置成功,上面存在,因此可以设置成功redis.set("key", "222", 0s, sw::redis::UpdateType::EXIST);auto value = redis.get("key");if (value){// 打印的结果是222cout << "value: " << value.value() << endl;}else{cout << "key 不存在!" << endl;}
}

mset

void test4(sw::redis::Redis &redis)
{cout << "mset" << endl;redis.flushall();// 第一种写法,使用初始化列表描述多个键值对redis.mset({make_pair("key1", "111"),make_pair("key2", "222")});// 第二种写法,把多个键值对提前放到容器中,以迭代器的形式告诉msetvector<pair<string, string>> keys;keys.push_back(make_pair("key3", "333"));keys.push_back(make_pair("key4", "444"));redis.mset(keys.begin(), keys.end());auto value = redis.get("key1");if (value){cout << "value: " << value.value() << endl;}value = redis.get("key2");if (value){cout << "value: " << value.value() << endl;}value = redis.get("key3");if (value){cout << "value: " << value.value() << endl;}value = redis.get("key4");if (value){cout << "value: " << value.value() << endl;}
}

mget

void test5(sw::redis::Redis &redis)
{cout << "mget" << endl;redis.flushall();vector<pair<string, string>> keys;keys.push_back(make_pair("key1", "111"));keys.push_back(make_pair("key2", "222"));keys.push_back(make_pair("key3", "333"));redis.mset(keys.begin(), keys.end());vector<sw::redis::OptionalString> values;auto it = std::back_inserter(values);redis.mget({"key1", "key2", "key3", "key4"}, it);for (auto &elem : values){if (elem)cout << elem.value() << endl;elsecout << "元素无效" << endl;}
}

getrange和setrange


void test6(sw::redis::Redis &redis)
{cout << "getrange and setrange " << endl;redis.flushall();redis.set("key", "hello world");// 获取到下标2-5的字符string ret = redis.getrange("key", 2, 5);cout << "ret: " << ret << endl;// 将下标2后面的字符修改为musicredis.setrange("key", 2, "music");auto value = redis.get("key");if (value)cout << "value: " << value.value() << endl;
}

incr和decr 

void test7(sw::redis::Redis &redis)
{cout << "incr end decr" << endl;redis.flushall();redis.set("key", "100");// 自增long long ret = redis.incr("key");auto value = redis.get("key");if (value)cout << "value: " << value.value() << endl;// 自减ret = redis.decr("key");value = redis.get("key");if (value)cout << "value: " << value.value() << endl;
}

list类型核心操作

lpush和lrange

void test1(Redis &redis)
{cout << "lpush 和 lrange" << endl;redis.flushall();//  插入单个元素redis.lpush("key", "111");// 插入一组元素,基于初始化列表redis.lpush("key", {"222", "333", "444"});// 插入一组元素,基于迭代器vector<string> values = {"555", "666", "777"};redis.lpush("key", values.begin(), values.end());// lrange 获取到列表中的元素vector<string> results;auto it = std::back_inserter(results);redis.lrange("key", 0, -1, it);for (auto &elem : results){cout << elem << endl;}
}

lpush是头插,因此越后面的元素在前面。 rpush和lpush的用法是一模一样的,这里就不介绍了。

 lpop和rpop

void test3(Redis &redis)
{cout << "lpop 和 rpop" << endl;redis.flushall();// 构造一个 listredis.rpush("key", {"111", "222", "333", "444", "555"});// lpop 从左边弹出一个元素auto value = redis.lpop("key");if (value)cout << value.value() << endl;// rpop 从右边弹出一个元素value = redis.rpop("key");if (value)cout << value.value() << endl;
}

brpop

阻塞删除一个key

void test4(Redis &redis)
{cout << "blpop" << endl;redis.flushall();// key不存在一定会阻塞// 返回值是 OptionalStringPair 是一个pair// pair的first是来自哪一个key// pair的second是哪个元素被删了auto result = redis.blpop("key");if (result){cout << "key:" << result.value().first << endl;cout << "elem:" << result.value().second << endl;}else{cout << "result 无效!" << endl;}
}

阻塞删除多个key 并设置超时时间

void test4(Redis &redis)
{using namespace std::chrono_literals;cout << "blpop" << endl;redis.flushall();// key不存在一定会阻塞// 返回值是 OptionalStringPair 是一个pair// pair的first是来自哪一个key// pair的second是哪个元素被删了auto result = redis.blpop({"key", "key1", "key2"}, 10s);if (result){cout << "key:" << result.value().first << endl;cout << "elem:" << result.value().second << endl;}else{cout << "result 无效!" << endl;}
}

llen


void test5(Redis &redis)
{cout << "llen" << endl;redis.flushall();redis.lpush("key", {"111", "222", "333", "444", "555"});long long len = redis.llen("key");cout << "len: " << len << endl;
}

redis-plus-plus这个库,接口风格设计是非常统一的。

当一个函数,参数需要传递多个值的时候,往往都是支持初始化列表或者是一对迭代器的方式来进行实现。

当一个函数,返回值需要表示多个数据的时候,往往也会借助插入迭代器,来实现往一个容器中添加元素的效果。

当某些场景涉及到无效值的时候,往往会搭配std::optional来进行使用。

set类型核心操作

sadd和smembers

void test1(Redis &redis)
{cout << "sadd 和 smembers" << endl;redis.flushall();// 一次添加一个元素redis.sadd("key", "111");// 一次添加多个元素 使用初始化列表redis.sadd("key", {"222", "333", "444"});// 一次添加多个元素 使用迭代器// 使用什么容器都可以,set、vector、list...set<string> elems = {"555", "666", "777"};redis.sadd("key", elems.begin(), elems.end());// 获取集合中的所有元素vector<string> result;auto it = std::back_inserter(result);redis.smembers("key", it);for (auto &e : result){cout << e << endl;}
}

sismember

void test2(Redis &redis)
{cout << "sismember" << endl;redis.flushall();redis.sadd("key", {"111", "222", "333"});// 判断元素是否存在bool ret1 = redis.sismember("key", "111");bool ret2 = redis.sismember("key", "444");cout << "ret1: " << ret1 << endl;cout << "ret2: " << ret2 << endl;
}

 scard


void test3(Redis &redis)
{cout << "scard" << endl;redis.flushall();redis.sadd("key", {"111", "222", "333"});// 获取集合中元素的个数int ret = redis.scard("key");cout << "ret: " << ret << endl;
}

spop

void test4(Redis &redis)
{cout << "spop" << endl;redis.flushall();redis.sadd("key", {"111", "222", "333"});// 随机删除元素auto result = redis.spop("key");if (result){cout << "result: " << result.value() << endl;}else{cout << "result 无效 !" << endl;}
}

sinter

void test5(Redis &redis)
{cout << "sinter" << endl;redis.flushall();redis.sadd("key1", {"111", "222", "333"});redis.sadd("key2", {"111", "222", "444"});set<string> result;auto it = std::inserter(result, result.end());redis.sinter({"key1", "key2"}, it);for (auto &e : result){cout << e << endl;}
}

 

sinterstore

void test6(Redis &redis)
{cout << "sinterstore" << endl;redis.flushall();redis.sadd("key1", {"111", "222", "333"});redis.sadd("key2", {"111", "222", "444"});long long len = redis.sinterstore("key3", {"key1", "key2"});cout << "len: " << len << endl;set<string> result;auto it = std::inserter(result, result.end());redis.smembers("key3", it);for (auto &e : result){cout << e << endl;}
}

求并集和求交集基本一致,不多赘述。

hash类型核心操作

hset和hget

void test1(Redis &redis)
{cout << "hset 和 hget" << endl;redis.flushall();// 一次添加一个元素redis.hset("key", "f1", "111");redis.hset("key", make_pair("f2", "222"));// 一次添加多个元素(初始化列表)redis.hset("key", {make_pair("f3", "333"), make_pair("f4", "444")});// 一次添加多个元素(迭代器)vector<pair<string, string>> fileds = {make_pair("f5", "555"), make_pair("f6", "666")};redis.hset("key", fileds.begin(), fileds.end());auto value = redis.hget("key", "f1");if (value){cout << value.value() << endl;}
}

 hexists和hdel和hlen

void test2(Redis &redis)
{cout << "hexists hdel hlen" << endl;redis.flushall();redis.hset("key", "f1", "111");redis.hset("key", "f2", "222");redis.hset("key", "f3", "333");bool ret1 = redis.hexists("key", "f1");bool ret2 = redis.hexists("key", "f3");cout << "ret1: " << ret1 << endl;cout << "ret2: " << ret2 << endl;// 一次删除一个filedlong long r = redis.hdel("key", "f1");cout << "删除了" << r << "个" << endl;// 一次删除多个filedr = redis.hdel("key", {"f2", "f3"});cout << "删除了" << r << "个" << endl;long long len = redis.hlen("key");cout << "len: " << len << endl;
}

hmset和hmget

void test4(Redis &redis)
{cout << "hmset hmget" << endl;redis.flushall();// 初始化列表redis.hmset("key", {make_pair("f1", "111"), make_pair("f2", "222")});// 迭代器vector<pair<string, string>> filed = {make_pair("f3", "333"), make_pair("f4", "444")};redis.hmset("key", filed.begin(), filed.end());vector<string> result;auto it = std::back_inserter(result);redis.hmget("key", {"f1", "f2", "f3"}, it);for (auto &e : result)cout << e << endl;
}

hkeys hvals

void test3(Redis &redis)
{cout << "heys 和 hvals" << endl;redis.flushall();redis.hset("key", "f1", "111");redis.hset("key", "f2", "222");redis.hset("key", "f3", "333");vector<string> fileds;auto itfileds = std::back_inserter(fileds);redis.hkeys("key", itfileds);for (auto &e : fileds)cout << e << endl;vector<string> values;auto itvalues = std::back_inserter(values);redis.hvals("key", itvalues);for (auto &e : values)cout << e << endl;
}

zset类型核心操作

zadd和zrange

oid test1(Redis &redis)
{cout << "zadd 和 zrange" << endl;redis.flushall();redis.zadd("key", "吕布", 99);redis.zadd("key", {make_pair("赵云", 98),make_pair("关羽", 97)});vector<pair<string, double>> members;members.push_back(make_pair("张飞", 96));members.push_back(make_pair("马超", 95));redis.zadd("key", members.begin(), members.end());// zrange支持两种风格的查询:// 1.只查询member,不带score// 2.查询member 带 score// 关键在于迭代器指向的是只包含一个string,就只包含memeber// 迭代器指向的是一个 pair,里面包含了 string double 就查询member和scorevector<string> memberResults;auto it = std::back_inserter(memberResults);redis.zrange("key", 0, -1, it);for (auto &m : memberResults)cout << m << endl;vector<pair<string, double>> membersWithScore;auto it2 = std::back_inserter(membersWithScore);redis.zrange("key", 0, -1, it2);for (auto &m : membersWithScore)cout << m.first << " " << m.second << endl;
}

zcar和zrem


void test2(Redis &redis)
{cout << "zcard 和 zrem" << endl;redis.flushall();redis.zadd("key", "music", 99);redis.zadd("key", "english", 69);redis.zadd("key", "math", 98);redis.zrem("key", "english");long long len = redis.zcard("key");cout << "len: " << len << endl;
}

zscore 

void test3(Redis &redis)
{cout << "zscore" << endl;redis.flushall();redis.zadd("key", "music", 99);redis.zadd("key", "english", 69);redis.zadd("key", "math", 98);auto score = redis.zscore("key", "music");if (score){cout << "score: " << score.value() << endl;}else{cout << "score 无效!" << endl;}
}

zrank

void test4(Redis &redis)
{cout << "zrank" << endl;redis.flushall();redis.zadd("key", "music", 99);redis.zadd("key", "english", 69);redis.zadd("key", "math", 98);auto rank = redis.zrank("key", "music");if (rank){cout << "rank: " << rank.value() << endl;}else{cout << "rank 无效!" << endl;}
}

相关文章:

C++编写Redis客户端

目录 安装redis-plus-plus库 ​编辑 编译Credis客户端 redis的通用命令使用 get/set exists del keys expire /ttl type string类型核心操作 set和get set带有超时时间 set带有NX string带有XX mset mget getrange和setrange incr和decr list类型核心操作…...

数据开发面试: 项目介绍示例

快照表 快照表&#xff08;Snapshot Table&#xff09;是数据仓库中用来存储某一时间点的数据状态的表。这种表通常包含在特定时间点上业务实体的静态数据&#xff0c;它记录了业务在某一特定时刻的“快照”视图。快照表通常用于存储那些不经常变化的数据&#xff0c;或者即使…...

记录一下Django的密码重置(忘记密码)

一. Django默认的密码重置 1.路由 # url.pyfrom django.contrib.auth import views as auth_viewsurlpatterns [# 密码重置path(password_reset/, auth_views.PasswordResetView.as_view(), namepassword_reset),# 用户输入邮箱后&#xff0c;跳转到此页面path(password_res…...

【运维篇】KubeSphere-02(经验汇总)

一、使用建议 1.对于数据库、对像存储比较重的要不能丢失&#xff0c;有异地存储备份需求的有状态服务&#xff0c;不建议采用k8s进行部署&#xff0c;会导致运维难度更大。 2.对于中间件如redis、MQ、harbor、seata、nacos、zookeeper可采用k8s部署。 3.对于无状态服务tomc…...

MySQL 5.7.40 主从同步配置教程

MySQL 主从同步能有效提升数据冗余备份与负载均衡。下面我将以 MySQL 5.7.40 版本为例&#xff0c;详细讲解如何进行主从同步配置。 MySQL 5.7.40 主从同步配置教程 一、环境准备 假设我们有两台服务器&#xff0c;一台作为主服务器&#xff08;Master&#xff09;&#xff…...

Qt:多线程

目录 初识Qt多线程 QThread常用API QThread的使用 Qt中的锁 条件变量和信号量 初识Qt多线程 Qt 多线程 和 Linux 中的线程本质是一个东西 Linux 中学过的 多线程 APl&#xff0c;Linux 系统提供的 pthread 库 Qt 中针对系统提供的线程 API 重新封装了 C11 中&#xff0c;…...

算法系列之广度优先搜索解决妖怪和尚过河问题

在算法学习中&#xff0c;广度优先搜索&#xff08;BFS&#xff09;是一种常用的图搜索算法&#xff0c;适用于解决最短路径问题、状态转换问题等。本文将介绍如何利用广度优先搜索解决经典的“妖怪和尚过河问题”。 问题描述 有三个妖怪和三个和尚需要过河。他们只有一条小船…...

详解常用集合和映射中的线程安全问题

1. 前言 在 Java 中&#xff0c;集合和映射是常用的数据结构&#xff0c;它们分为线程安全和线程不安全两类。我们常用的集合包括&#xff1a;ArrayList、HashSet、CopyOnWriteArrayList、CopyOnWriteArraySet。常用的映射包括&#xff1a;HashMap、ConcurrentHashMap、Hashta…...

计算机毕业设计SpringBoot+Vue.js车辆管理系统(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…...

【js逆向】iwencai国内某金融网站实战

地址&#xff1a;aHR0cHM6Ly93d3cuaXdlbmNhaS5jb20vdW5pZmllZHdhcC9ob21lL2luZGV4 在搜索框中随便输入关键词 查看请求标头&#xff0c;请求头中有一个特殊的 Hexin-V,它是加密过的&#xff1b;响应数据包中全是明文。搞清楚Hexin-V的值是怎么生成的&#xff0c;这个值和cooki…...

安卓设备root检测与隐藏手段

安卓设备root检测与隐藏手段 引言 安卓设备的root权限为用户提供了深度的系统控制能力&#xff0c;但也可能带来安全风险。因此&#xff0c;许多应用&#xff08;如银行软件、游戏和流媒体平台&#xff09;会主动检测设备是否被root&#xff0c;并限制其功能。这种对抗催生了ro…...

【音视频 | AAC】AAC编码库faac介绍、使用步骤、例子代码

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…...

Unity摄像机跟随物体

功能描述 实现摄像机跟随物体&#xff0c;并使物体始终保持在画面中心位置。 实现步骤 创建脚本&#xff1a;在Unity中创建一个新的C#脚本&#xff0c;命名为CameraFollow。 代码如下&#xff1a; using UnityEngine;public class CameraFollow : MonoBehaviour {public Tran…...

dp_走方格(包含dfs分析,记忆化搜索)

类似题目解析&#xff1a;dp_最长上升子序列&#xff08;包含dfs分析&#xff0c;记忆化搜索&#xff09;-CSDN博客 题目链接&#xff1a;2067. 走方格 - AcWing题库 题目图片&#xff1a; 分析题目&#xff08;dfs&#xff09; 这个题目说有一个行为n行&#xff0c;列为m列…...

软考 中级软件设计师 考点笔记总结 day01

文章目录 软考1.0上午考点下午考点 软考1.11、数值及其转换2、计算机内数据表示2.1、定点数 - 浮点数2.2、奇偶校验 和 循环冗余校验 (了解)2.3、海明码 &#xff08;掌握&#xff09;2.4、机器数 软考1.0 上午考点 软件工程基础知识&#xff1a; 开发模型、设计原则、测试方…...

如何用Kimi生成PPT?秒出PPT更高效!

做PPT是不是总是让你头疼&#xff1f;&#x1f629; 快速制作出专业的PPT&#xff0c;今天我们要推荐两款超级好用的AI工具——Kimi 和 秒出PPT&#xff01;我们来看看哪一款更适合你吧&#xff01;&#x1f680; &#x1f947; Kimi&#xff1a;让PPT制作更轻松 Kimi的生成效…...

K8S学习之基础十八:k8s的灰度发布和金丝雀部署

灰度发布 逐步扩大新版本的发布范围&#xff0c;从少量用户逐步扩展到全体用户。 特点是分阶段发布、持续监控、逐步扩展 适合需要逐步验证和降低风险的更新 金丝雀部署 将新版本先部署到一小部分用户或服务器&#xff0c;观察其表现&#xff0c;再决定是否全面推广。 特点&…...

Java 深度复制对象:从基础到实战

目录 一、深度复制的概念二、实现深度复制的方法1. 使用序列化2. 手动实现深度复制 三、总结 在 Java 编程中&#xff0c;对象的复制是一个常见的需求。然而&#xff0c;简单的复制操作&#xff08;如直接赋值&#xff09;只会复制对象的引用&#xff0c;而不是创建一个新的对象…...

【前端】webstorm创建一个导航页面:HTML、CSS 和 JavaScript 的结合

文章目录 前言一、项目结构二、HTML 结构三、CSS 样式四、JavaScript 功能五、现代化风格优化htmlcssjavascript运行效果 总结 前言 在现代网页开发中&#xff0c;一个良好的导航栏是提升用户体验的重要组成部分。在这篇文章中&#xff0c;我将向您展示如何创建一个简单而完整…...

AI编程: 一个案例对比CPU和GPU在深度学习方面的性能差异

背景 字节跳动正式发布中国首个AI原生集成开发环境工具&#xff08;AI IDE&#xff09;——AI编程工具Trae国内版。 该工具模型搭载doubao-1.5-pro&#xff0c;支持切换满血版DeepSeek R1&V3&#xff0c; 可以帮助各阶段开发者与AI流畅协作&#xff0c;更快、更高质量地完…...

第11章 web应用程序安全(网络安全防御实战--蓝军武器库)

网络安全防御实战--蓝军武器库是2020年出版的&#xff0c;已经过去3年时间了&#xff0c;最近利用闲暇时间&#xff0c;抓紧吸收&#xff0c;总的来说&#xff0c;第11章开始学习利用web应用程序安全&#xff0c;主要讲信息收集、dns以及burpsuite&#xff0c;现在的资产测绘也…...

MySQL复习笔记

MySQL复习笔记 1.MySQL 1.1什么是数据库 数据库(DB, DataBase) 概念&#xff1a;数据仓库&#xff0c;软件&#xff0c;安装在操作系统&#xff08;window、linux、mac…&#xff09;之上 作用&#xff1a;存储数据&#xff0c;管理数据 1.2 数据库分类 关系型数据库&#…...

GitHub上传项目

总结&#xff08;有基础的话直接执行这几步&#xff0c;就不需要再往下看了&#xff09;&#xff1a; git init 修改git的config文件&#xff1a;添加:[user]:name你的github用户名 email你注册github的用户名 git branch -m master main git remote add origin 你的URL gi…...

自我训练模型:通往未来的必经之路?

摘要 在探讨是否唯有通过自我训练模型才能掌握未来的问题时&#xff0c;文章强调了底层技术的重要性。当前&#xff0c;许多人倾向于关注应用层的便捷性&#xff0c;却忽视了支撑这一切的根本——底层技术。将模型简单视为产品是一种短视行为&#xff0c;长远来看&#xff0c;理…...

qt 操作多个sqlite文件

qt 操作多个sqlite文件 Chapter1 qt 操作多个sqlite文件1. 引入必要的头文件2. 创建并连接多个SQLite数据库3. 代码说明4. 注意事项 Chapter2 qt 多线程操作sqlite多文件1. 引入必要的头文件2. 创建数据库操作的工作线程类3. 在主线程中创建并启动多个工作线程4. 代码说明5. 运…...

【每日学点HarmonyOS Next知识】多继承、swiper容器、事件传递、滚动安全区域、提前加载网络图片

1、HarmonyOS ArkTS如何让一个类可以具备其他多个类的能力&#xff1f; ArkTS如何让一个类可以具备其他多个类的能力&#xff0c;类似于多继承。 接口支持多继承。类不支持&#xff0c;其只支持单继承。 &#xff08;报错&#xff1a;Classes can only extend a single class…...

DIY Tomcat:手写一个简易Servlet容器

在Java Web开发领域&#xff0c;Tomcat堪称经典&#xff0c;它作为Servlet容器&#xff0c;承载着无数Web应用的运行。今天&#xff0c;我将带大家一同探索如何手写一个简易的Tomcat&#xff0c;深入理解其底层原理。 一、背景知识 在开始之前&#xff0c;我们需要对几个关键…...

如何在Ubuntu上直接编译Apache Doris

以下是在 Ubuntu 22.04 上直接编译 Apache Doris 的完整流程&#xff0c;综合多个版本和环境的最佳实践&#xff1a; 注意&#xff1a;Ubuntu的数据盘VMware默认是20G&#xff0c;编译不够用&#xff0c;给到50G以上吧 一、环境准备 1. 安装系统依赖 # 基础构建工具链 apt i…...

基于ssm的物资进销存(全套)

现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本货物进销管理系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息&#…...

【CVPR2025】 EVSSM:用状态空间模型高效去模糊

Efficient Visual State Space Model for Image Deblurring 论文信息 题目&#xff1a; Efficient Visual State Space Model for Image Deblurring 用于图像去模糊的高效视觉状态空间模型 源码&#xff1a;https://github.com/kkkls/EVSSM 创新点 提出了高效视觉状态空间模型…...