如何最快解析特定格式字符串?支持std::string_view与格式校验
高效解析
<double>,<double>,<char[12]>格式字符串 需求与约束
需要解析格式为<double>,<double>,<char[12]>的字符串(示例:"12.34555,23.44343343,abcdef"),核心目标是极致性能,同时满足以下要求:
- 支持非空终止字符串(如
std::string_view) - 具备完整的格式错误校验能力
- 已知限制:
<charconv>对double类型无效,无法使用该库的高性能解析<iostreams>性能差且会产生内存分配,直接排除- 自行实现
strtod难度过高,不考虑 - 可选方案为
strtod或sscanf,其中sscanf实现简洁但性能存疑
原sscanf示例代码
用户提供的sscanf实现虽然简洁,但存在依赖空终止字符串、错误校验不足、无法直接支持std::string_view等问题:
#include <cstdio> #include <cstring> void scan(const char *s){ double a = 0; double b = 0; char h[32]; // 填充内容,用于判断是否解析成功 memset(h, 'x', 31); h[31] = '\0'; sscanf(s, "%lf,%lf,%s", &a, &b, h); printf("%.4lf\t%.4lf\t%s\n", a, b, h); } int main() { scan("12.34555,23.44343343,abcdef"); scan("12.34555,,abcdef"); scan("12.34555,23.44343343,"); scan("12.34555,23.44343343"); scan("12.34555,"); scan("12.34555"); scan(""); scan("boba"); }
优化方案:strtod+手动分割
方案优势
- 原生支持
std::string_view,无需空终止符,避免内存拷贝 strtod的解析性能优于sscanf,不需要处理格式化字符串的解析开销- 可实现精细化的错误校验,覆盖所有格式异常场景
- 如果知道固定小数位,可进一步手动解析优化性能
优化后代码
#include <cstdlib> #include <string_view> #include <cctype> #include <stdexcept> #include <cstdio> struct ParsedData { double a; double b; char h[13]; // 对应char[12],预留一位存字符串终止符 }; // 跳过空白字符(可根据需求调整是否保留) const char* skip_whitespace(const char* ptr) { while (std::isspace(static_cast<unsigned char>(*ptr))) { ++ptr; } return ptr; } // 解析double,返回解析后的下一个指针,失败则返回nullptr const char* parse_double(const char* start, const char* end, double& out) { char* end_ptr; out = std::strtod(start, &end_ptr); // 校验:没有解析到有效数字 或 解析超出输入范围 if (end_ptr == start || end_ptr > end) { return nullptr; } return end_ptr; } // 解析char[12],返回解析后的下一个指针,失败则返回nullptr const char* parse_char_array(const char* start, const char* end, char out[13]) { const char* ptr = start; int count = 0; // 最多解析12个非分隔符、非空白字符 while (ptr < end && count < 12) { if (*ptr == ',' || std::isspace(static_cast<unsigned char>(*ptr))) { break; } out[count++] = *ptr++; } out[count] = '\0'; // 校验:没有解析到有效字符 if (count == 0) { return nullptr; } return ptr; } ParsedData parse(std::string_view sv) { ParsedData result{}; const char* ptr = sv.data(); const char* end = sv.data() + sv.size(); // 解析第一个double ptr = skip_whitespace(ptr); ptr = parse_double(ptr, end, result.a); if (!ptr || ptr >= end || *ptr != ',') { throw std::invalid_argument("第一个double格式错误或缺失分隔符"); } ++ptr; // 跳过逗号分隔符 // 解析第二个double ptr = skip_whitespace(ptr); ptr = parse_double(ptr, end, result.b); if (!ptr || ptr >= end || *ptr != ',') { throw std::invalid_argument("第二个double格式错误或缺失分隔符"); } ++ptr; // 跳过逗号分隔符 // 解析char[12] ptr = skip_whitespace(ptr); ptr = parse_char_array(ptr, end, result.h); if (!ptr) { throw std::invalid_argument("字符数组格式错误"); } // 校验:是否存在多余的无效字符 ptr = skip_whitespace(ptr); if (ptr != end) { throw std::invalid_argument("存在多余的尾随字符"); } return result; } // 测试用例 int main() { auto test = [](std::string_view input) { try { auto data = parse(input); printf("%.4lf\t%.4lf\t%s\n", data.a, data.b, data.h); } catch (const std::exception& e) { printf("错误:%s\n", e.what()); } }; test("12.34555,23.44343343,abcdef"); test("12.34555,,abcdef"); test("12.34555,23.44343343,"); test("12.34555,23.44343343"); test("12.34555,"); test("12.34555"); test(""); test("boba"); }
固定小数位优化(可选)
如果已知double的小数位固定(比如都是5位),可以替换parse_double为手动解析,进一步提升性能:
const char* parse_fixed_double(const char* start, const char* end, double& out, int decimal_digits) { const char* ptr = start; long long integer_part = 0; long long fractional_part = 0; bool negative = false; // 处理符号 if (ptr < end && (*ptr == '+' || *ptr == '-')) { negative = (*ptr == '-'); ++ptr; } // 解析整数部分 while (ptr < end && std::isdigit(static_cast<unsigned char>(*ptr))) { integer_part = integer_part * 10 + (*ptr++ - '0'); } // 解析小数部分 if (ptr < end && *ptr == '.') { ++ptr; int digits = 0; while (ptr < end && digits < decimal_digits && std::isdigit(static_cast<unsigned char>(*ptr))) { fractional_part = fractional_part * 10 + (*ptr++ - '0'); digits++; } // 补零到指定小数位 while (digits < decimal_digits) { fractional_part *= 10; digits++; } } else { // 没有小数部分,补零 for (int i = 0; i < decimal_digits; ++i) { fractional_part *= 10; } } // 计算最终值 double scale = 1.0; for (int i = 0; i < decimal_digits; ++i) { scale *= 10.0; } out = static_cast<double>(integer_part) + static_cast<double>(fractional_part) / scale; if (negative) { out = -out; } // 校验:是否解析到有效数字 if (ptr == start) { return nullptr; } return ptr; }
内容的提问来源于stack exchange,提问作者Nick
相关产品推荐
相关产品推荐

