c++oding="utf-8" ?>
用 std::unordered_map 做词频统计更高效,因其平均 o(1) 插入和查找优于 std::map 的 o(log n);推荐用 std::istringstream 分词、统一转小写、过滤空串和纯标点;ascii 直方图应归一化缩放并用 round() 避免截断偏差;regex 分词慎用,优先捕获单词而非分割;高频场景需 reserve、move 插入及及时释放内存。

用 std::unordered_map 做词频统计,别用 std::map
词频统计本质是键值对计数,std::unordered_map<:string int></:string> 平均 O(1) 插入和查找,而 std::map 是 O(log n),在万级单词以上差距明显。注意:C++11 起默认哈希函数对 std::string 足够高效,无需自定义;但若字符串极短(如单字符)且数量极大,可考虑用 std::string_view(C++17)减少拷贝——前提是源字符串生命周期可控。
常见错误:直接对 std::string 用 .c_str() 构造 std::string_view 后存入 map,结果指向临时内存,后续访问未定义行为。
实操建议:
- 用
std::istringstream或std::regex_token_iterator切分单词,避开手写空格/标点逻辑漏洞 - 统一转小写(
std::tolower配合std::locale)再插入,避免 “The” 和 “the” 被算作不同词 - 跳过空字符串和纯标点(长度为 0 或
std::all_of(s.begin(), s.end(), ::ispunct))
直方图输出用 ASCII 还是调用外部绘图库?
纯控制台直方图只适合调试或轻量展示,不建议在生产中用于报告。ASCII 直方图核心是“归一化+字符填充”,难点不在画线,而在缩放策略:
- 最大频次设为 50 个星号(
'*'),其余按比例取整:int bar_len = static_cast<int>(round(50.0 * freq / max_freq))</int> - 避免除零:检查
max_freq == 0(空输入或全过滤掉) - 不要用浮点除法后截断(
(int)(x * 50 / max)),会系统性偏小;round()更公平
如果需要 PNG/SVG 输出,别自己画——用 gnuplot 命令行或 matplotlib-cpp 绑定,前者更轻量(只需生成数据文件 + 一行 shell 调用)。
std::regex 分词时容易崩的三个点
正则切分看似方便,但 C++ 标准库 std::regex 实现质量参差,尤其在 MSVC 和 libstdc++ 上性能差异大,且易触发栈溢出或无限循环。
- 模式别写
R"(\W+)"去匹配分隔符,改用R"([a-zA-Z]+)"直接捕获单词——避免空匹配和边界重叠 - 禁用
ECMAScript以外的语法(如basic、awk),跨平台兼容性差 - 若文本含大量 Unicode(如中文),
std::regex基本失效;此时应改用 ICU 库或简单按字节扫描 + UTF-8 解码判断字母范围
实测:10MB 英文文本,std::regex_token_iterator 在 GCC 12 下比 std::istringstream 慢 3–5 倍,且内存峰值高 40%。
高频场景下的内存与缓存优化
当处理日志或爬虫数据(百万单词级),瓶颈常在内存分配而非算法。每个 std::string 默认小字符串优化(SSO)仅覆盖 15–22 字节,长单词仍触发堆分配。
- 预估单词平均长度,用
std::string::reserve()初始化临时缓冲(如word.reserve(32)) - 用
std::vector<:pair int>></:pair>存最终结果,排序后再输出;避免在unordered_map中反复迭代(哈希表遍历无序且缓存不友好) - 若需 Top-K,用
std::partial_sort_copy或std::nth_element,别全排序——100 万词全排序比取前 100 名慢两个数量级
最易被忽略的是:统计完立即释放原始文本缓冲(如 std::string 或 std::vector<char></char>),否则 map 的 key 仍持有冗余副本;用 std::move 插入能省一半内存拷贝。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











