You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何最快解析特定格式字符串?支持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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 14:52:53