Integers 只是一个 CRLF 终止的字符串

介绍RedisSyncer在RDB同步过程中如何处理大Key,通过拆分提高同步效率及避免内存溢出。同时解析Redis RDB文件格式及其内部结构。

RDB 同步过程中的大 Key 拆分

RedisSyncer 在全量同步阶段在遇到 LIST、SET、ZSET、HASH 等结构等时候,当数据大小超过阀值后 RedisSyncer 会通过迭代器的形式将 key 拆分成多个子命令写入目标库。防止部分超大 key 一次性读入内存导致程序产生 oom 并提高同步的速度。而对于不存在大 key 的命令同步工具会通过序列化逆序列化的形式写入目标。

附录一  Redis RDB 协议

redis RDB Dump 文件格式

----------------------------# RDB is a binary format. There are no new lines or spaces in the file.
52 45 44 49 53              # Magic String "REDIS"
30 30 30 37                 # 4 digit ASCCII RDB Version Number. In this case, version = "0007" = 7
----------------------------
FE 00                       # FE = code that indicates database selector. db number = 00
----------------------------# Key-Value pair starts
FD $unsigned int            # FD indicates "expiry time in seconds". After that, expiry time is read as a 4 byte unsigned int
$value-type                 # 1 byte flag indicating the type of value - set, map, sorted set etc.
$string-encoded-key         # The key, encoded as a redis string
$encoded-value              # The value. Encoding depends on $value-type
----------------------------
FC $unsigned long           # FC indicates "expiry time in ms". After that, expiry time is read as a 8 byte unsigned long
$value-type                 # 1 byte flag indicating the type of value - set, map, sorted set etc.
$string-encoded-key         # The key, encoded as a redis string
$encoded-value              # The value. Encoding depends on $value-type
----------------------------
$value-type                 # This key value pair doesn't have an expiry. $value_type guaranteed != to FD, FC, FE and FF
$string-encoded-key
$encoded-value
----------------------------
FE $length-encoding         # Previous db ends, next db starts. Database number read using length encoding.
----------------------------
...                         # Key value pairs for this database, additonal database

FF                          ## End of RDB file indicator
8 byte checksum             ## CRC 64 checksum of the entire file.

RDB文件以魔术字符串“REDIS”开头。
52 45 44 49 53 # "REDIS"

RDB 版本号
接下来的 4 个字节存储 rdb 格式的版本号。这 4 个字节被解释为 ascii 字符,然后使用字符串到整数转换转换为整数。
00 00 00 03 # Version = 3

Database Selector
一个Redis实例可以有多个数据库。
单个字节0xFE标记数据库选择器的开始。在该字节之后,一个可变长度字段指示数据库编号。请参阅“长度编码”部分以了解如何读取此数据库编号。

键值对
在数据库选择器之后,该文件包含一系列键值对。
za
每个键值对有 4 个部分 -

1.密钥到期时间戳。
2.指示值类型的一字节标志
3.密钥,编码为 Redis 字符串。请参阅“Redis 字符串编码”
4.根据值类型编码的值。参见“Redis 值编码”

附录二 Redis RESP 协议

Redis RESP 协议

RESP 协议是在 Redis 1.2 中引入的,但它成为了 Redis 2.0 中与 Redis 服务器通信的标准方式。是在 Redis 客户端中实现的协议。 RESP 实际上是一种序列化协议,它支持以下数据类型:简单字符串、错误、整数、批量字符串和数组。

RESP 在 Redis 中用作请求 - 响应协议的方式如下:

  • 客户端将命令作为批量字符串的 RESP 数组发送到 Redis 服务器。

  • 服务器根据命令实现以其中一种 RESP 类型进行回复。

在 RESP 中,某些数据的类型取决于第一个字节:

  • 对于简单字符串,回复的第一个字节是 “+”

  • 对于错误,回复的第一个字节是 “-”

  • 对于整数,回复的第一个字节是 “:”

  • 对于批量字符串,回复的第一个字节是 “$”

  • 对于数组,回复的第一个字节是 “*”

RESP 能够使用稍后指定的批量字符串或数组的特殊变体来表示 Null 值。在 RESP 中,协议的不同部分总是以 “\r\n”(CRLF)终止。

RESP Simple Strings

'+' 字符开头,后跟不能包含 CR 或 LF 字符(不允许换行)的字符串,以 CRLF 结尾(即 “\r\n”)。如:

"+OK\r\n"

RESP Errors

 "-Error message\r\n"  

如:

-ERR unknown command 'foobar'
-WRONGTYPE Operation against a key holding the wrong kind of value

RESP Integers

Integers 只是一个 CRLF 终止的字符串,代表一个整数,以 “:” 字节为前缀。 例如

":0\r\n" 
":1000\r\n"

Bulk Strings

用于表示长度最大为 512 MB 的单个二进制安全字符串。批量字符串按以下方式编码:

  • “$” 字节后跟组成字符串的字节数(前缀长度),以 CRLF 结尾。

  • 实际的字符串数据。

  • 最后的 CRLF。

“foobar” 的编码如下:

<think>我们正在解决从字符串中提取整数的问题。根据用户需求,只需要提取整数(即不带小数点和指数部分的数字)。我们可以使用两种主要方法:正则表达式和手动解析。由于用户提到“C++ extract integer from string”,我们重点关注整数提取。 方法1:使用正则表达式提取整数 正则表达式模式:`[-+]?\d+` 或 `[+-]?[0-9]+` 解释:可选的正负号,后跟一个或多个数字。 方法2:使用字符串流(stringstream)提取整数 我们可以利用字符串流,按空格分割字符串,然后尝试将每个部分转换为整数。但这种方法要求数字是以空格分隔的,如果字符串中没有空格或者数字与其他非空格字符相邻,则无法正确提取。 方法3:手动遍历字符串,逐个字符判断并构建整数。 考虑到用户可能面对各种格式的字符串(数字可能与其他非数字字符相邻),我们推荐使用正则表达式或手动遍历。 根据引用[1]和[2],我们也可以使用字符串流(stringstream)结合十六进制转换,但这里我们只需要十进制整数。 下面我们提供三种方法: ### 方法1:正则表达式(推荐) ```cpp #include <iostream> #include <string> #include <regex> #include <vector> std::vector<int> extractIntegersRegex(const std::string& str) { std::vector<int> integers; // 匹配整数,包括正负号 std::regex pattern(R"([-+]?\d+)"); std::sregex_iterator it(str.begin(), str.end(), pattern); std::sregex_iterator end; while (it != end) { integers.push_back(std::stoi(it->str())); ++it; } return integers; } int main() { std::string text = "There are 123 apples and -456 oranges, and 7890 bananas."; auto integers = extractIntegersRegex(text); for (int num : integers) { std::cout << num << " "; } // 输出: 123 -456 7890 } ``` ### 方法2:使用字符串流(适用于空格分隔的单词) ```cpp #include <iostream> #include <string> #include <sstream> #include <vector> std::vector<int> extractIntegersStream(const std::string& str) { std::vector<int> integers; std::stringstream ss(str); std::string word; while (ss >> word) { try { // 尝试将每个单词转换为整数 int num = std::stoi(word); integers.push_back(num); } catch (const std::invalid_argument& e) { // 转换失败,忽略非整数 } catch (const std::out_of_range& e) { // 数字超出范围,忽略 } } return integers; } int main() { std::string text = "123 -456 7890"; auto integers = extractIntegersStream(text); for (int num : integers) { std::cout << num << " "; } // 输出: 123 -456 7890 } ``` 注意:这种方法只能提取以空格分隔的整数,如果字符串是"123abc"则无法提取。 ### 方法3:手动解析(遍历字符串) ```cpp #include <iostream> #include <string> #include <vector> #include <cctype> std::vector<int> extractIntegersManual(const std::string& str) { std::vector<int> integers; bool inNumber = false; bool negative = false; int current = 0; for (size_t i = 0; i < str.length(); i++) { char c = str[i]; if (std::isdigit(c)) { if (!inNumber) { inNumber = true; current = 0; // 检查前一个字符是否为负号(且负号前面不是数字) if (i > 0 && str[i-1] == '-') { negative = true; } else { negative = false; } } current = current * 10 + (c - '0'); } else { if (inNumber) { integers.push_back(negative ? -current : current); inNumber = false; } // 重置负号标记,因为负号只有在数字紧邻前面才有效 negative = false; } } // 处理字符串以数字结尾的情况 if (inNumber) { integers.push_back(negative ? -current : current); } return integers; } int main() { std::string text = "123 apples -456 oranges7890"; auto integers = extractIntegersManual(text); for (int num : integers) { std::cout << num << " "; } // 输出: 123 -456 7890 } ``` 注意:这个手动解析方法比较简单,它假设负号紧挨着数字才有效(即负号和数字之间没有空格)。如果负号与数字之间有空格,则不会视为负数。 ### 方法比较 | 方法 | 适用场景 | 注意事项 | |------|----------|----------| | 正则表达式 | 通用,能处理各种复杂情况 | 需要C++11或以上 | | 字符串流 | 简单,仅空格分隔的数字 | 无法处理非空格分隔的数字 | | 手动解析 | 可控性强,不依赖新标准 | 需要处理更多边界情况 | ### 推荐 对于大多数情况,推荐使用正则表达式方法,因为它简洁且功能强大。如果项目不支持C++11,可以考虑手动解析方法。 ### 常见问题 **Q: 如何提取无符号整数?** A: 修改正则表达式为`\d+`,并在转换时使用`std::stoul`。 **Q: 如果数字很大,超过int范围怎么办?** A: 使用`std::stol`或`std::stoll`代替`std::stoi`,并存储为long或long long。 **Q: 如何提取十六进制整数?** A: 使用正则表达式`0x[0-9a-fA-F]+`,然后使用`std::stol`配合基数16转换。 ### 参考引用 在字符串和数字转换方面,引用[1]介绍了使用`to_string`和`stringstream`的方法,引用[5]展示了使用`stringstream`进行十六进制转换的示例,这些方法在提取数字后的转换步骤中很有用[^1][^5]。 §§相关问题§§ 1. 如何从字符串中提取浮点数? 2. C++正则表达式提取数字时如何避免异常? 3. 如何提取科学计数法表示的数字(如1.23e4)? 4. 使用手动解析方法时,如何处理负号与数字之间的空格? 5. 在C++98中如何实现类似正则表达式的字符串提取功能?</think>在 C++ 中从字符串提取整数,有几种高效可靠的方法。以下是三种常用方法及其实现: ### 方法 1:使用正则表达式(推荐) ```cpp #include <iostream> #include <string> #include <regex> #include <vector> std::vector<int> extractIntegers(const std::string& str) { std::vector<int> integers; // 匹配带符号的整数:[-+]? 表示可选正负号,\d+ 表示一个或多个数字 std::regex pattern(R"([-+]?\d+)"); std::sregex_iterator it(str.begin(), str.end(), pattern); std::sregex_iterator end; while (it != end) { integers.push_back(std::stoi(it->str())); // 字符串转整数 ++it; } return integers; } int main() { std::string text = "价格: $12, 库存: -25件, 折扣率: 15%"; auto numbers = extractIntegers(text); for (int num : numbers) { std::cout << num << " "; // 输出: 12 -25 15 } } ``` ### 方法 2:使用字符串流(`stringstream`) ```cpp #include <iostream> #include <string> #include <sstream> #include <vector> #include <cctype> std::vector<int> extractIntegersStream(const std::string& str) { std::vector<int> integers; std::istringstream iss(str); std::string token; int sign = 1; // 符号标记 while (iss >> token) { // 按空格分割字符串 if (token == "-") { sign = -1; // 检测负号 continue; } // 移除非数字字符 std::string cleanToken; for (char c : token) { if (std::isdigit(c)) cleanToken += c; } if (!cleanToken.empty()) { integers.push_back(sign * std::stoi(cleanToken)); sign = 1; // 重置符号 } } return integers; } // 使用方式同方法1 ``` ### 方法 3:手动遍历(高效低内存) ```cpp #include <iostream> #include <string> #include <vector> #include <cctype> std::vector<int> extractIntegersManual(const std::string& str) { std::vector<int> integers; int current = 0; bool inNumber = false; bool negative = false; for (char c : str) { if (std::isdigit(c)) { inNumber = true; current = current * 10 + (c - '0'); // 构建数字 } else if (c == '-' && !inNumber) { negative = true; // 检测负号 } else { if (inNumber) { integers.push_back(negative ? -current : current); current = 0; inNumber = false; negative = false; } } } // 处理字符串末尾的数字 if (inNumber) integers.push_back(negative ? -current : current); return integers; } // 使用方式同方法1 ``` ### 三种方法对比 | **方法** | **优点** | **缺点** | **适用场景** | |------------------|-----------------------------------|------------------------------|---------------------------| | **正则表达式** | 代码简洁,支持复杂模式 | 需要 C++11 或更高版本 | 通用场景,格式多变的数据 | | **字符串流** | 标准库支持,无需额外依赖 | 无法处理无空格分隔的数字 | 空格分隔的规整数据 | | **手动遍历** | 最高效,零内存分配,兼容旧标准 | 需自行处理边界情况 | 性能敏感场景或嵌入式系统 | ### 关键点说明 1. **数字转换**:使用 `std::stoi()` 将字符串转为整数(引用[1]) 2. **符号处理**:通过负号标记处理负数 3. **边界情况**: - 连续数字(如"123abc" → 123) - 结尾数字(如"value:42" → 42) - 负号检测(如"-25" → -25) ### 常见问题解答 **Q: 如何处理超大整数(超出 int 范围)?** A: 改用 `std::stol()` 或 `std::stoll()` 并存储为 `long`/`long long` 类型。 **Q: 如何提取十六进制整数?** A: 使用正则表达式 `0x[0-9a-fA-F]+` 配合 `std::stol(token, nullptr, 16)`(引用[5]) **Q: 如何获取数字在原字符串中的位置?** A: 使用 `sregex_iterator` 的 `position()` 方法: ```cpp auto match = *it; int pos = match.position(); // 数字起始位置 int len = match.length(); // 数字长度 ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值