034 《Folly Regex.h 权威指南:从入门到精通 (Folly Regex.h: The Definitive Guide from Beginner to Expert)》


作者 Lou Xiao, gemini 创建时间 "2025-04-17 03:08:33" 更新时间 "2025-04-17 03:08:33"

🌟🌟🌟本文案由Gemini 2.0 Flash Thinking Experimental 01-21创作,用来辅助学习知识。🌟🌟🌟

书籍大纲

1. chapter 1: 走进正则表达式 (Introduction to Regular Expressions)

1.1 什么是正则表达式 (What is Regular Expression)

正则表达式(Regular Expression, Regex 或 RE)是一种强大的文本处理工具,它使用预定义的模式来匹配和操作字符串。可以将其视为一种模式匹配语言(pattern matching language),用于在文本中搜索、替换、验证和提取符合特定模式的字符串。

在日常的文本处理和编程工作中,我们经常需要处理复杂的字符串操作,例如:

验证数据格式: 检查用户输入的邮箱地址、电话号码、身份证号等是否符合预定的格式。
搜索特定模式: 在大量的文本数据中查找特定的关键词、短语或符合某种规律的字符串,例如,查找所有以 "Error" 开头的日志行。
替换文本内容: 批量替换文档中的特定文本,例如,将所有日期格式从 "YYYY-MM-DD" 转换为 "MM/DD/YYYY"。
提取信息: 从非结构化的文本中提取出结构化的信息,例如,从网页源代码中提取所有的链接地址。

传统的字符串查找和操作方法,例如 find(), substr(), replace() 等,在处理简单的字符串时可能足够。但是,当面对复杂和灵活的模式匹配需求时,这些方法往往显得力不从心,代码冗长且容易出错。而正则表达式则提供了一种简洁、高效且通用的解决方案。

正则表达式的核心思想 是使用元字符(metacharacters)普通字符(ordinary characters) 组合成一个模式(pattern),这个模式描述了要匹配的字符串的规则。

普通字符: 包括字母、数字、汉字、标点符号等,它们在正则表达式中直接匹配自身。例如,模式 "abc" 只能匹配字符串 "abc"
元字符: 是一些具有特殊含义的字符,例如 .*+?[]()^$\| 等。它们赋予了正则表达式强大的匹配能力和灵活性。例如,. 可以匹配任意单个字符,* 可以匹配前一个字符零次或多次。

通过组合普通字符和元字符,我们可以构建出各种复杂的正则表达式模式,以满足不同的文本处理需求。

例如,正则表达式 \d+ 可以匹配一个或多个数字,常用于提取文本中的数字信息;正则表达式 [a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,} 可以用于验证邮箱地址的格式。

学习和掌握正则表达式,能够极大地提高文本处理的效率和代码的简洁性,是程序员和文本处理人员必备的技能之一。而 folly/Regex.h 库,作为 Facebook Folly 库的一部分,提供了一个强大而高效的 C++ 正则表达式实现,是我们在 C++ 环境下进行正则表达式操作的优秀选择。

1.2 正则表达式的历史与发展 (History and Development of Regular Expressions)

正则表达式的概念并非横空出世,它的发展历程与计算机科学的早期发展紧密相连,并深深地影响了后续的文本处理和编程技术。

正则表达式的理论基础可以追溯到 20 世纪 50 年代,起源于 形式语言理论(formal language theory)。美国数学家 斯蒂芬·科尔·克莱尼(Stephen Cole Kleene) 在 1956 年的论文中,首次引入了 正则集合(regular sets) 的概念,并使用数学符号描述了这种集合,这些符号就是正则表达式的雏形,被称为 克莱尼星号(Kleene star) 等。克莱尼为正则表达式的理论基础奠定了坚实的基础。

在 20 世纪 60 年代,正则表达式的概念开始在计算机科学领域得到应用。肯·汤普逊(Ken Thompson),Unix 系统的创始人之一,在开发 QED 文本编辑器(QED text editor) 时,将正则表达式引入到文本搜索功能中。QED 编辑器中的正则表达式功能,极大地提升了文本处理的效率和灵活性。

随后,肯·汤普逊和 丹尼斯·里奇(Dennis Ritchie) 在开发 Unix 系统 时,进一步将正则表达式的概念融入到 Unix 工具中,例如 grep (Global Regular Expression Print) 命令,成为了 Unix 系统中强大的文本搜索工具。grep 命令的诞生,标志着正则表达式开始在实际应用中发挥重要作用。

在 20 世纪 70 年代,随着 Perl 编程语言 的诞生,正则表达式迎来了新的发展机遇。拉里·沃尔(Larry Wall),Perl 语言的设计者,将正则表达式深度集成到 Perl 语言中,并对其语法进行了扩展和增强,使其更加强大和易用。Perl 语言的流行,极大地推动了正则表达式的普及和应用。

此后,正则表达式被广泛应用于各种编程语言和文本处理工具中,例如 Python, Java, JavaScript, sed, awk 等。不同的编程语言和工具,在正则表达式的语法和实现上可能存在细微的差异,但其核心思想和基本概念是相通的。

随着互联网和大数据时代的到来,文本数据的规模呈爆炸式增长,正则表达式在 数据挖掘(data mining)网络安全(network security)日志分析(log analysis) 等领域的应用越来越广泛。为了满足不断增长的应用需求,正则表达式的实现也在不断演进和优化,例如,出现了各种高性能的正则表达式引擎,以及对 Unicode 等字符集的支持。

folly/Regex.h 库,作为 Facebook Folly 库的一部分,正是现代正则表达式技术发展的一个体现。它在继承了经典正则表达式思想的基础上,针对现代 C++ 应用的需求,进行了性能优化和功能增强,为 C++ 开发者提供了高效、可靠的正则表达式工具。

总而言之,正则表达式从最初的理论概念,发展成为 आज एक 广泛应用于各个领域的实用技术,经历了漫长的发展历程,凝聚了众多计算机科学家的智慧和努力。了解正则表达式的历史与发展,有助于我们更好地理解其设计思想和应用场景,从而更好地掌握和运用这一强大的工具。

1.3 正则表达式的应用场景 (Application Scenarios of Regular Expressions)

正则表达式作为一种强大的文本处理工具,其应用场景非常广泛,几乎涉及到所有需要处理文本数据的领域。 掌握正则表达式,能够极大地提升在各种场景下处理文本数据的效率和能力。

以下列举一些正则表达式的典型应用场景:

数据验证(Data Validation)

表单验证: 在 Web 开发中,使用正则表达式验证用户在表单中输入的数据,例如邮箱地址、电话号码、用户名、密码强度等,确保数据的合法性和有效性。例如,验证邮箱地址格式、手机号码格式、身份证号码格式等。
数据清洗: 在数据分析和数据挖掘领域,使用正则表达式清洗不规范的数据,例如去除字符串首尾的空格、统一日期格式、纠正拼写错误等,提高数据质量。

文本搜索与查找(Text Searching and Finding)

代码编辑器: 在代码编辑器中使用正则表达式进行代码搜索,可以实现更精确、更灵活的查找功能,例如查找所有以特定前缀开头的变量名、查找包含特定模式的注释行等。
文档处理软件: 在 Word、WPS 等文档处理软件中使用正则表达式进行高级查找和替换,例如查找所有特定格式的日期、替换所有重复的空格等。
日志分析: 在日志文件中使用正则表达式搜索特定的错误信息、警告信息、异常信息等,帮助快速定位问题和分析系统运行状态。例如,查找特定时间段内的错误日志、统计特定类型的错误数量等。
网络爬虫: 在网络爬虫中使用正则表达式从 HTML 页面中提取目标信息,例如提取网页标题、链接地址、正文内容等。

文本替换与编辑(Text Replacement and Editing)

代码重构: 在代码重构中使用正则表达式进行批量代码替换,例如批量修改变量名、函数名、代码结构调整等,提高代码重构效率。
文本格式转换: 使用正则表达式进行文本格式转换,例如将 Markdown 格式转换为 HTML 格式、将 CSV 格式转换为 JSON 格式等。
批量文本编辑: 使用脚本语言结合正则表达式批量编辑文本文件,例如批量修改文件名、批量添加或删除文本行、批量替换文件内容等。

数据提取与分析(Data Extraction and Analysis)

信息抽取: 从非结构化的文本数据中提取结构化的信息,例如从新闻报道中提取人名、地名、机构名等,从简历中提取教育经历、工作经历等。
数据分析: 使用正则表达式对文本数据进行统计分析,例如统计词频、分析情感倾向、识别命名实体等。
协议解析: 在网络编程中使用正则表达式解析网络协议,例如 HTTP 协议头部解析、自定义协议解析等。

安全领域(Security Field)

入侵检测: 在入侵检测系统中使用正则表达式检测恶意攻击模式,例如 SQL 注入攻击、跨站脚本攻击等。
安全审计: 在安全审计日志中使用正则表达式分析安全事件,例如分析异常登录行为、识别敏感数据泄露等。

除了以上列举的场景,正则表达式还在生物信息学、金融分析、自然语言处理等领域有着广泛的应用。 随着信息技术的不断发展,文本数据的处理需求日益增长,正则表达式的应用前景将更加广阔。 掌握正则表达式,无疑是提升个人技能和竞争力的重要一步。

1.4 folly/Regex.h 库简介 (Introduction to folly/Regex.h Library)

folly/Regex.h 是 Facebook 开源的 Folly 库(Facebook Open Source Library) 中的一个 C++ 正则表达式库。 Folly 库是一个高度模块化、高性能的 C++ 库集合,被广泛应用于 Facebook 的各种基础设施和应用中。 folly/Regex.h 作为 Folly 库的重要组成部分,继承了 Folly 库一贯的 高性能(high performance)高可靠性(high reliability)易用性(usability) 的特点。

folly/Regex.h 库提供了一套简洁而强大的 API,用于在 C++ 程序中进行正则表达式的匹配、查找、替换等操作。 它支持 Perl 兼容正则表达式(Perl Compatible Regular Expressions, PCRE) 语法,这意味着熟悉 PCRE 语法的开发者可以快速上手 folly/Regex.h 库。 同时,folly/Regex.h 库在性能方面进行了大量的优化,特别是在处理大规模文本数据和高并发场景下,表现出色。

1.4.1 folly/Regex.h 的起源与设计目标 (Origin and Design Goals of folly/Regex.h)

folly/Regex.h 库的诞生,源于 Facebook 内部对高性能正则表达式库的需求。 在 Facebook 的海量数据处理和高并发服务场景中,需要频繁地进行文本匹配和处理操作,例如日志分析、数据清洗、协议解析、反垃圾信息等。 std::regex 作为 C++ 标准库提供的正则表达式库,在某些场景下性能表现并不理想,无法满足 Facebook 的高性能需求。

为了解决这个问题,Facebook 工程师开发了 folly/Regex.h 库。 其主要设计目标包括:

高性能folly/Regex.h 库的首要设计目标是提供卓越的性能,尤其是在大规模文本数据处理和高并发场景下。 为了实现高性能,folly/Regex.h 库在底层实现上进行了大量的优化,例如使用了高效的匹配算法、针对性的内存管理策略等。

PCRE 兼容: 为了降低学习成本和迁移成本,folly/Regex.h 库选择了兼容 PCRE 语法。 PCRE 是目前最流行、功能最强大的正则表达式语法之一,被广泛应用于各种编程语言和工具中。 兼容 PCRE 语法,使得开发者可以更容易地将已有的正则表达式知识和经验应用到 folly/Regex.h 库中。

易用性folly/Regex.h 库在 API 设计上力求简洁易用,提供了清晰明了的类和方法,方便开发者快速上手和使用。 同时,folly/Regex.h 库也提供了丰富的文档和示例代码,帮助开发者更好地理解和使用库的功能。

与 Folly 库的良好集成folly/Regex.h 库作为 Folly 库的一部分,与 Folly 库的其他组件(例如 folly::StringPiece, folly::FBString 等) 实现了良好的集成。 这种集成使得开发者可以更方便地在 Folly 框架下使用正则表达式,并充分利用 Folly 库提供的各种工具和基础设施。

总而言之,folly/Regex.h 库的起源和设计目标,是为了满足 Facebook 内部对高性能、易用、PCRE 兼容的 C++ 正则表达式库的需求。 它的诞生,体现了 Facebook 在追求技术卓越和解决实际问题方面的努力。

1.4.2 folly/Regex.h 的优势与特点 (Advantages and Features of folly/Regex.h)

folly/Regex.h 库相比于其他 C++ 正则表达式库,具有以下显著的优势和特点:

卓越的性能folly/Regex.h 库在性能方面进行了深度优化,尤其是在大规模文本数据处理和高并发场景下,性能表现优于 std::regex 等标准库实现。 这得益于其高效的匹配算法、优化的内存管理以及针对性的微架构优化。 对于性能敏感的应用场景,folly/Regex.h 库是一个非常理想的选择。

PCRE 语法兼容folly/Regex.h 库完全兼容 PCRE 语法,支持 PCRE 的所有常用特性,包括各种元字符、量词、断言、分组、选项等。 这意味着开发者可以使用熟悉的 PCRE 语法来编写正则表达式,无需学习新的语法规则。 同时也方便了将已有的 PCRE 正则表达式迁移到 folly/Regex.h 库中使用。

简洁易用的 APIfolly/Regex.h 库提供了简洁明了、易于使用的 C++ API。 核心类 folly::Regex 提供了 isMatch(), find(), findAll(), replace() 等常用的匹配和操作方法。 API 设计符合 C++ 编程习惯,易于理解和上手。

与 Folly 库的无缝集成folly/Regex.h 库与 Folly 库的其他组件实现了无缝集成,例如可以方便地与 folly::StringPiecefolly::FBString 等 Folly 字符串类一起使用,避免了不必要的字符串拷贝和转换开销。 同时,folly/Regex.h 库也可以很好地融入 Folly 库的异步编程框架中。

良好的错误处理folly/Regex.h 库提供了完善的错误处理机制,通过 folly::RegexException 异常类来报告正则表达式编译和匹配过程中的错误。 开发者可以使用 try-catch 语句来捕获和处理正则表达式异常,提高程序的健壮性。

Unicode 支持folly/Regex.h 库对 Unicode 字符集提供了良好的支持,可以处理包含各种 Unicode 字符的文本数据。 支持 Unicode 字符属性和 Unicode 模式匹配,满足国际化应用的需求。

活跃的社区和持续的维护folly/Regex.h 库作为 Facebook 开源项目的一部分,拥有活跃的开发社区和持续的维护。 Facebook 工程师会定期更新和改进 folly/Regex.h 库,修复 bug,提升性能,增加新功能,确保库的稳定性和先进性。

总而言之,folly/Regex.h 库凭借其卓越的性能、PCRE 语法兼容性、简洁易用的 API、与 Folly 库的无缝集成等优势和特点,成为了 C++ 开发者在高性能正则表达式处理方面的优秀选择。

1.4.3 folly/Regex.h 与其他正则表达式库的比较 (Comparison of folly/Regex.h with Other Regular Expression Libraries)

在 C++ 生态系统中,除了 folly/Regex.h 库,还有一些其他的正则表达式库可供选择,例如:

std::regex (C++ 标准库正则表达式)std::regex 是 C++11 标准引入的正则表达式库,是 C++ 标准库的一部分,具有良好的跨平台性和通用性。 std::regex 支持多种正则表达式语法,默认通常是 ECMAScript 语法。

▮▮▮▮⚝ 优点: 标准库的一部分,跨平台性好,无需额外安装依赖。
▮▮▮▮⚝ 缺点: 性能相对较差,尤其是在大规模文本处理和高并发场景下,性能瓶颈较为明显。 编译速度较慢。 错误信息不够友好。

Boost.Regex (Boost 正则表达式库): Boost.Regex 是 Boost 库中的正则表达式库,历史悠久,功能强大,被广泛应用于各种 C++ 项目中。 Boost.Regex 也支持多种正则表达式语法,包括 ECMAScript, POSIX Extended, POSIX Basic, Perl 等。

▮▮▮▮⚝ 优点: 功能强大,支持多种正则表达式语法,性能优于 std::regex,社区活跃,文档完善。
▮▮▮▮⚝ 缺点: 依赖 Boost 库,需要额外安装 Boost 库。 编译速度相对较慢。

PCRE (Perl Compatible Regular Expressions): PCRE 是一个独立的、开源的正则表达式库,使用 C 语言编写,被广泛应用于各种编程语言和工具中,例如 Perl, Python, PHP, Nginx 等。 PCRE 库以其强大的功能和优异的性能而闻名。

▮▮▮▮⚝ 优点: 功能极其强大,性能非常优秀,PCRE 语法是业界标准,应用广泛。
▮▮▮▮⚝ 缺点: C 语言接口,C++ 开发者使用时可能需要进行一定的封装。 需要额外安装 PCRE 库。

RE2 (Google RE2): RE2 是 Google 开源的正则表达式库,使用 C++ 编写,专注于提供高性能的正则表达式匹配,尤其是在安全性和可预测性方面表现出色。 RE2 库避免了回溯(backtracking)机制,从而避免了正则表达式的 ReDoS (Regular expression Denial of Service) 攻击风险。

▮▮▮▮⚝ 优点: 高性能,安全性高,避免 ReDoS 攻击,编译速度快。
▮▮▮▮⚝ 缺点: 功能相对较少,不支持一些 PCRE 的高级特性,例如反向引用、环视断言等。 语法与 PCRE 略有差异。

folly/Regex.h 库的定位和优势

folly/Regex.h 库在上述几种正则表达式库中,定位介于 Boost.Regex 和 RE2 之间。 它在性能上优于 std::regex 和 Boost.Regex,接近甚至在某些场景下优于 PCRE 和 RE2。 同时,folly/Regex.h 库在功能上比 RE2 更强大,更接近 PCRE,支持 PCRE 的绝大部分常用特性。 并且,folly/Regex.h 库提供了更加现代化的 C++ 接口,易用性更好,与 Folly 库的集成也更加方便。

总结比较

特性/库 std::regex Boost.Regex PCRE RE2 folly/Regex.h
性能 较低 较高 优秀 优秀 优秀
功能 基本 强大 极其强大 较少 强大
语法 ECMAScript 多种 PCRE RE2 变种 PCRE
安全性 一般 一般 一般 一般
易用性 (C++ API) 一般 较好 C 接口 C++ 接口 很好
依赖 标准库 Boost PCRE 库 RE2 库 Folly 库
编译速度 较慢 较慢
Unicode 支持 良好 良好 良好 良好 良好

如何选择

简单场景,追求跨平台和零依赖std::regex 可以满足基本需求。
功能需求较高,对性能有一定要求: Boost.Regex 是一个不错的选择。
追求极致性能,需要 PCRE 语法兼容folly/Regex.h 是一个非常好的选择,尤其是在 Folly 框架下。
追求极致性能和安全性,可以牺牲部分功能: RE2 是一个值得考虑的选择,尤其是在需要避免 ReDoS 攻击的场景下。
需要使用其他语言调用,或者需要 C 接口: PCRE 是一个通用的选择。

在本书中,我们将专注于深入学习和应用 folly/Regex.h 库,充分发挥其高性能和易用性的优势,帮助读者掌握在 C++ 环境下进行高效正则表达式处理的技能。

END_OF_CHAPTER

2. chapter 2: 正则表达式基础语法 (Basic Syntax of Regular Expressions)

2.1 元字符 (Metacharacters)

正则表达式的强大之处在于其元字符 (Metacharacters) 的使用。元字符是具有特殊含义的字符,它们不代表字面意义上的字符本身,而是用于控制匹配模式的特殊符号。理解和掌握元字符是学习正则表达式的基础。

2.1.1 . 字符:匹配任意字符 (. Character: Match any character)

. 字符是正则表达式中最简单的元字符之一。它代表匹配任意单个字符,除了换行符(在某些模式下,. 也可以匹配换行符,具体取决于正则表达式引擎和选项设置)。

示例 1: 匹配任意字符
假设我们想要匹配任何以 "c" 开头,以 "t" 结尾的三个字符的单词,可以使用正则表达式 c.t

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: cat, cut, c t, c@t, caat
2正则表达式: c.t
3匹配结果: cat, cut, c@t

在上面的例子中,. 字符分别匹配了 au@ 这些不同的字符,使得 c.t 能够匹配到 "cat"、"cut" 和 "c@t"。但是 "c t" 因为中间是空格,也被匹配了,而 "caat" 因为是四个字符,所以不匹配。

注意
. 字符在默认情况下不匹配换行符 \n。如果需要匹配包括换行符在内的任意字符,通常需要在正则表达式的选项中设置单行模式(Single-line mode),例如在 folly/Regex.h 中,可以通过 folly::RegexOptions::dotall 选项来实现。

2.1.2 ^ 和 Anchors(and Characters: Anchors)

^$锚字符 (Anchors),它们不匹配任何字符,而是匹配位置^ 匹配字符串的开始位置$ 匹配字符串的结束位置

示例 2: 匹配字符串的开始和结束
假设我们想要匹配以 "Hello" 开头的字符串,可以使用正则表达式 ^Hello

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: Hello world, Say Hello, Hello
2正则表达式: ^Hello
3匹配结果: Hello world, Hello

在上面的例子中,^Hello 确保匹配的 "Hello" 必须出现在字符串的开头。因此,"Hello world" 和 "Hello" 被匹配,而 "Say Hello" 因为 "Hello" 不是在开头,所以不匹配。

同样,如果我们想要匹配以 "world" 结尾的字符串,可以使用正则表达式 world$

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: Hello world, world hello, world
2正则表达式: world$
3匹配结果: Hello world, world

world$ 确保匹配的 "world" 必须出现在字符串的结尾。因此,"Hello world" 和 "world" 被匹配,而 "world hello" 因为 "world" 不是在结尾,所以不匹配。

组合使用 ^$
^$ 可以组合使用,以匹配整个字符串。例如,^Hello$ 只会匹配完全等于 "Hello" 的字符串。

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: Hello, HelloWorld, Hello
2正则表达式: ^Hello$
3匹配结果: Hello

只有当字符串完全是 "Hello" 时,^Hello$ 才会匹配。 "HelloWorld" 和单独的 "Hello" (在列表中) 因为不是完全匹配,所以不匹配。

2.1.3 [] 字符类 (Character Classes [])

字符类 (Character Classes) [] 允许我们定义一个字符集合,正则表达式将匹配集合中的任意一个字符

示例 3: 匹配字符类
假设我们想要匹配 "bat"、"cat" 或 "hat",可以使用字符类 [bch]at

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: bat, cat, hat, rat, eat
2正则表达式: [bch]at
3匹配结果: bat, cat, hat

在上面的例子中,[bch] 定义了一个字符类,它包含字符 'b'、'c' 和 'h'。正则表达式 [bch]at 将匹配以 'b'、'c' 或 'h' 开头,后跟 "at" 的字符串。 "rat" 和 "eat" 因为首字母不在字符类 [bch] 中,所以不匹配。

字符类中的范围
在字符类中,可以使用 - 符号来表示字符范围

[a-z]:匹配任意小写字母。
[A-Z]:匹配任意大写字母。
[0-9]:匹配任意数字。
[a-zA-Z0-9]:匹配任意字母或数字。

示例 4: 字符类中的范围
匹配一个由字母和数字组成的字符串,可以使用 [a-zA-Z0-9]。例如,要匹配一个以字母开头,后跟数字的字符串,可以使用 [a-zA-Z][0-9]

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: a1, B2, z9, 1a, AB
2正则表达式: [a-zA-Z][0-9]
3匹配结果: a1, B2, z9

[a-zA-Z][0-9] 匹配第一个字符是字母(大小写均可),第二个字符是数字的字符串。 "1a" 因为第一个字符是数字,所以不匹配。"AB" 因为第二个字符不是数字,所以不匹配。

排除型字符类
在字符类的开头使用 ^ 符号,可以创建排除型字符类,表示匹配不在集合中的任意字符

[^0-9]:匹配任意非数字字符。
[^a-z]:匹配任意非小写字母字符。

示例 5: 排除型字符类
匹配一个非数字字符,可以使用 [^0-9]。例如,要匹配一个以非数字字符开头的字符串,可以使用 [^0-9].

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: a1, #2, 3c, 4d
2正则表达式: [^0-9].
3匹配结果: a1, #2, 3c

[^0-9]. 匹配第一个字符是非数字字符,第二个字符是任意字符的字符串。 "4d" 因为第一个字符是数字,所以不匹配。注意,空格也是非数字字符,所以 " 3c" 中的空格符合 [^0-9]

2.1.4 ' 转义字符 (Escape Character ')

\ 转义字符 (Escape Character) 用于取消元字符的特殊含义,使其被视为普通字符进行匹配。在正则表达式中,很多字符具有特殊含义(例如 ., *, +, ?, ^, $, [], (), {}, |, \),如果想要匹配这些字符本身,就需要使用 \ 进行转义。

示例 6: 转义字符
假设我们想要匹配字符串中的 . 字符,而不是任意字符,需要使用 \.

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: example.com, example, example+com
2正则表达式: example\.com
3匹配结果: example.com

在上面的例子中,example\.com 中的 \. 匹配的是字面意义上的点号 ".",而不是任意字符。因此,只有 "example.com" 被匹配,而 "example" 和 "example+com" 因为没有点号 ".",所以不匹配。

常见的需要转义的元字符
以下是一些常见的需要转义的元字符:

\.:匹配点号 .
\*:匹配星号 *
\+:匹配加号 +
\?:匹配问号 ?
\^:匹配脱字符 ^
\$:匹配美元符号 $
\[:匹配左方括号 [
\]:匹配右方括号 ]
\(:匹配左圆括号 (
\):匹配右圆括号 )
\{:匹配左花括号 {
\}:匹配右花括号 }
\|:匹配竖线 |
\\:匹配反斜杠 \

转义字符在字符类中的应用
在字符类 [] 中,某些元字符的特殊含义会发生变化,但 \ 转义字符仍然可以用于转义字符类内部的特殊字符。例如,要匹配字符 -]\ 本身,可以将它们放在字符类中并进行转义,或者将 - 放在字符类的开头或结尾,将 ] 放在字符类的开头。

[\[\]]:匹配 [] 字符。
[a\-z][-az][az-]:匹配 a-z 字符。
[\\abc]:匹配 \abc 字符。

2.2 量词 (Quantifiers)

量词 (Quantifiers) 用于指定模式出现的次数。量词可以控制模式必须重复多少次才能被匹配。

2.2.1 * 零次或多次 ( * Zero or more times)

* 量词表示匹配前面的模式零次或多次。这意味着模式可以出现零次、一次、两次,或者任意多次。

示例 7: * 量词
假设我们想要匹配 "go" 后面跟着零个或多个 "o" 的字符串,可以使用正则表达式 go*gle

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: gogle, google, gooogle, goooogle, gggle
2正则表达式: go*gle
3匹配结果: gogle, google, gooogle, goooogle

在上面的例子中,o* 匹配零个、一个、两个或多个 "o" 字符。因此,"gogle" (零个 'o')、"google" (一个 'o')、"gooogle" (两个 'o') 和 "goooogle" (三个 'o') 都被匹配。 "gggle" 因为 "g" 和 "gle" 之间不是 "o" 字符,所以不匹配。

2.2.2 + 一次或多次 ( + One or more times)

+ 量词表示匹配前面的模式一次或多次。这意味着模式必须至少出现一次,可以出现一次、两次,或者任意多次,但不能是零次。

示例 8: + 量词
假设我们想要匹配 "go" 后面跟着一个或多个 "o" 的字符串,可以使用正则表达式 go+gle

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: gogle, google, gooogle, goooogle, gggle
2正则表达式: go+gle
3匹配结果: google, gooogle, goooogle

* 不同,o+ 匹配一个或多个 "o" 字符。因此,"google" (一个 'o')、"gooogle" (两个 'o') 和 "goooogle" (三个 'o') 被匹配。 "gogle" (零个 'o') 和 "gggle" 因为 "g" 和 "gle" 之间不是至少一个 "o" 字符,所以不匹配。

2.2.3 ? 零次或一次 ( ? Zero or one time)

? 量词表示匹配前面的模式零次或一次。这意味着模式可以出现零次或一次,但不能超过一次。

示例 9: ? 量词
假设我们想要匹配 "color" 或 "colour",可以使用正则表达式 colou?r

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: color, colour, colouuur, colr
2正则表达式: colou?r
3匹配结果: color, colour

u? 匹配零个或一个 "u" 字符。因此,"color" (零个 'u') 和 "colour" (一个 'u') 被匹配。 "colouuur" (多个 'u') 和 "colr" (没有 'u',但缺少 'o') 因为 "u" 出现的次数不对,所以不匹配。

2.2.4 {n}, {n,}, {n,m} 指定次数 (Specified number of times {n}, {n,}, {n,m})

{} 量词允许我们更精确地指定模式出现的次数

{n}:匹配前面的模式恰好 n 次
{n,}:匹配前面的模式至少 n 次
{n,m}:匹配前面的模式至少 n 次,但不超过 m 次

示例 10: {n} 量词
匹配恰好出现 3 个数字的字符串 \d{3} (其中 \d 是预定义的字符类,表示匹配一个数字,等价于 [0-9])。

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: 123, 1234, 12, abc123def
2正则表达式: \d{3}
3匹配结果: 123, abc123def (匹配 "123")

\d{3} 匹配连续出现三次的数字。 "123" 和 "abc123def" 中包含 "123" 都被匹配。"1234" 虽然包含 "123",但是 \d{3} 只匹配三个数字,所以只匹配 "123" 部分。"12" 因为数字少于三次,所以不匹配。

示例 11: {n,} 量词
匹配至少出现 2 个字母的字符串 [a-zA-Z]{2,}

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: ab, abc, a, 12, abcd123
2正则表达式: [a-zA-Z]{2,}
3匹配结果: ab, abc, abcd123 (匹配 "abcd")

[a-zA-Z]{2,} 匹配连续出现至少两次的字母。 "ab"、"abc" 和 "abcd123" 中包含至少两个连续字母的部分都被匹配。"a" 和 "12" 因为字母少于两次,所以不匹配。

示例 12: {n,m} 量词
匹配出现 2 到 4 次数字的字符串 \d{2,4}

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: 12, 123, 1234, 12345, a123b
2正则表达式: \d{2,4}
3匹配结果: 12, 123, 1234, a123b (匹配 "123")

\d{2,4} 匹配连续出现 2 到 4 次的数字。 "12"、"123"、"1234" 和 "a123b" 中包含 2 到 4 个连续数字的部分都被匹配。"12345" 虽然包含 "1234",但是 \d{2,4} 最多匹配四个数字,所以只匹配 "1234" 部分。

贪婪匹配与懒惰匹配
默认情况下,量词 *+{n,}{n,m} 都是贪婪的 (Greedy)。这意味着它们会尽可能多地匹配字符。例如,对于文本 "abbbbc",正则表达式 ab*c 会匹配 "abbbbc" 整个字符串,而不是 "abc"。

如果想要进行懒惰匹配 (Lazy)最小匹配 (Minimal Matching),可以在量词后面加上 ?。懒惰量词会尽可能少地匹配字符。例如,*?+???{n,}?{n,m}? 都是懒惰量词。

示例 13: 贪婪匹配与懒惰匹配
对于文本 "<h1>Title</h1>",使用贪婪匹配 <h1>.*</h1> 和懒惰匹配 <h1>.*?</h1> 的区别。

⚝ 贪婪匹配 <h1>.*</h1>:会匹配从第一个 <h1> 到最后一个 </h1> 之间的所有内容,结果是 "<h1>Title</h1>"。
⚝ 懒惰匹配 <h1>.*?</h1>:会匹配从第一个 <h1> 到最近的 </h1> 之间的内容,结果是 "<h1>Title</h1>"。

在这个简单的例子中,结果相同。但是,如果文本是 "<h1>Title1</h1>...<h1>Title2</h1>",贪婪匹配会匹配 "<h1>Title1</h1>...<h1>Title2</h1>",而懒惰匹配会分别匹配 "<h1>Title1</h1>" 和 "<h1>Title2</h1>"。

2.3 分组与捕获 (Grouping and Capturing)

分组 (Grouping)捕获 (Capturing) 是正则表达式中非常强大的功能,它们允许我们将模式的一部分组合成一个单元,并对这个单元进行操作,例如应用量词或提取匹配的内容。

2.3.1 () 分组 (Grouping ())

圆括号 () 用于创建分组。分组可以将多个字符或模式组合成一个单元,然后可以对这个单元应用量词或其他操作。

示例 14: 分组
假设我们想要匹配 "abab" 或 "cdcd" 这样的模式,即 "ab" 或 "cd" 重复两次,可以使用分组 (ab){2}(cd){2}。更通用的,可以使用 (ab|cd){2}

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: abab, cdcd, ab, cd, ababab, cdcdcd
2正则表达式: (ab|cd){2}
3匹配结果: abab, cdcd

(ab|cd) 创建了一个分组,表示匹配 "ab" 或 "cd"。 {2} 量词应用于这个分组,表示分组必须重复出现两次。因此,"abab" 和 "cdcd" 被匹配。"ab" 和 "cd" 因为只出现一次,所以不匹配。"ababab" 和 "cdcdcd" 因为重复了三次,超过了两次,所以也不匹配。

2.3.2 捕获组 (Capturing Groups)

默认情况下,圆括号 () 创建的是捕获组 (Capturing Groups)。这意味着正则表达式引擎会记住捕获组匹配到的文本,以便后续引用或提取。捕获组从左到右编号,从 1 开始。第 0 组是整个正则表达式的匹配结果。

示例 15: 捕获组
假设我们想要匹配日期格式 "YYYY-MM-DD",并提取年、月、日,可以使用捕获组 (\d{4})-(\d{2})-(\d{2})

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/Regex.h>
2#include <iostream>
3#include <string>
4int main() {
5 std::string text = "Today is 2023-10-27.";
6 folly::Regex regex(R"((\d{4})-(\d{2})-(\d{2}))");
7 folly::smatch match;
8 if (folly::Regex::find(regex, text, match)) {
9 std::cout << "匹配成功!" << std::endl;
10 std::cout << "完整匹配: " << match[0] << std::endl;
11 std::cout << "年: " << match[1] << std::endl;
12 std::cout << "月: " << match[2] << std::endl;
13 std::cout << "日: " << match[3] << std::endl;
14 } else {
15 std::cout << "匹配失败!" << std::endl;
16 }
17 return 0;
18}

代码解释
(\d{4})-(\d{2})-(\d{2}) 定义了三个捕获组:
▮▮▮▮⚝ 第 1 组 (\d{4}) 捕获四位数字(年)。
▮▮▮▮⚝ 第 2 组 (\d{2}) 捕获两位数字(月)。
▮▮▮▮⚝ 第 3 组 (\d{2}) 捕获两位数字(日)。
folly::Regex::find(regex, text, match) 执行匹配,并将结果存储在 match 对象中。
match[0] 存储整个正则表达式的匹配结果 "2023-10-27"。
match[1]match[2]match[3] 分别存储第 1、2、3 组捕获的内容 "2023"、"10" 和 "27"。

输出结果

1.双击鼠标左键复制此行;2.单击复制所有代码。
1匹配成功!
2完整匹配: 2023-10-27
3年: 2023
4月: 10
5日: 27

2.3.3 非捕获组 (?:...) (Non-capturing Groups (?:...))

非捕获组 (Non-capturing Groups) 使用 (?:...) 语法创建。非捕获组与捕获组类似,也可以将模式组合成一个单元,但不会捕获匹配的文本。这意味着正则表达式引擎不会记住非捕获组匹配到的内容,也不会为其分配组号。

使用场景
当我们需要使用分组的分组功能,例如应用量词或选择,但不需要捕获分组内容时,可以使用非捕获组。这可以提高正则表达式的性能,并减少内存消耗,尤其是在处理大量文本或复杂的正则表达式时。

示例 16: 非捕获组
假设我们想要匹配 "industries" 或 "industry",可以使用非捕获组 (?:industr(?:y|ies))

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: industry, industries, industrial
2正则表达式: industr(?:y|ies)
3匹配结果: industry, industries

(?:y|ies) 是一个非捕获组,它匹配 "y" 或 "ies"。 (?:industr(?:y|ies)) 确保 "industr" 后跟 "y" 或 "ies"。 "industrial" 因为结尾不是 "y" 或 "ies",所以不匹配。

如果我们使用捕获组 (industr(y|ies)),则会捕获两个组:
⚝ 第 1 组: "industry" 或 "industries"
⚝ 第 2 组: "y" 或 "ies"

而使用非捕获组 (?:industr(?:y|ies)),则不会捕获任何组,只进行匹配,这在只需要判断是否匹配,而不需要提取分组内容时,更加高效。

2.4 选择 (Alternation)

选择 (Alternation) 使用 | 或操作符 (OR operator) 来表示多个模式之间的选择| 允许我们指定多个备选项,正则表达式引擎会尝试匹配其中任意一个模式。

2.4.1 | 或操作符 (| OR operator)

| 操作符用于分隔多个模式,表示匹配左侧的模式或右侧的模式。

示例 17: | 或操作符
假设我们想要匹配 "cat" 或 "dog",可以使用正则表达式 cat|dog

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: cat, dog, bird, cats, dogs
2正则表达式: cat|dog
3匹配结果: cat, dog

cat|dog 匹配 "cat" 或 "dog"。因此,"cat" 和 "dog" 被匹配。"bird"、"cats" 和 "dogs" 因为既不是 "cat" 也不是 "dog",所以不匹配。

| 操作符的优先级
| 操作符的优先级最低,这意味着它会将左右两侧的整个模式作为备选项。如果需要限制 | 的作用范围,可以使用分组 ()

示例 18: | 操作符的优先级
比较 cat|dogca(t|d)og 的区别。

cat|dog:匹配 "cat" 或 "dog"。
ca(t|d)og:匹配 "catog" 或 "cadog"。

ca(t|d)og 中,(t|d) 分组限制了 | 的作用范围,使其只在 "t" 和 "d" 之间进行选择,而不是整个 "cat" 和 "dog"。

示例 19: 结合分组和 | 操作符
匹配 "get" 或 "set" 后面跟着一个或多个数字,可以使用 (get|set)\s+\d+ (其中 \s+ 匹配一个或多个空白字符)。

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: get 123, set 456, put 789, getabc
2正则表达式: (get|set)\s+\d+
3匹配结果: get 123, set 456

(get|set) 匹配 "get" 或 "set"。 \s+ 匹配一个或多个空白字符。 \d+ 匹配一个或多个数字。整个正则表达式 (get|set)\s+\d+ 匹配以 "get" 或 "set" 开头,后跟一个或多个空白字符,再后跟一个或多个数字的字符串。 "put 789" 因为不是以 "get" 或 "set" 开头,所以不匹配。"getabc" 因为 "get" 和 "abc" 之间没有空白字符,所以不匹配。

总结
本章介绍了正则表达式的基础语法,包括元字符、量词、分组与捕获以及选择。掌握这些基础知识是深入学习和应用正则表达式的关键。在后续章节中,我们将继续学习 folly/Regex.h 库的具体使用方法,以及更高级的正则表达式语法和应用场景。

END_OF_CHAPTER

3. chapter 3: folly/Regex.h 快速上手 (Quick Start with folly/Regex.h)

3.1 环境配置与编译 (Environment Setup and Compilation)

在使用 folly/Regex.h 之前,我们需要先配置好开发环境并完成编译。由于 folly/Regex.h 是 Facebook 开源库 Folly (Facebook Open Source Library) 的一部分,因此需要先安装 Folly 库及其依赖项。本节将指导读者完成环境配置和编译的步骤。

3.1.1 依赖库安装 (Dependency Library Installation)

folly/Regex.h 依赖于 Folly 库,而 Folly 本身又依赖于许多其他的开源库。因此,安装 folly/Regex.h 的第一步是安装 Folly 及其依赖项。具体的依赖库包括但不限于:

Boost:Folly 广泛使用了 Boost 库,包括 Boost.Atomic, Boost.Config, Boost.Context, Boost.Coroutine, Boost.Filesystem, Boost.Optional, Boost.Program_Options, Boost.Random, Boost.Regex, Boost.System, Boost.Thread, Boost.Unordered 等组件。
Double-conversion:用于快速精确地转换浮点数到字符串以及字符串到浮点数。
Glog (gflags):Google Logging Library,用于日志记录。
Gflags:Google Flags,用于命令行参数解析。
Libevent:一个事件通知库。
LZ4:快速压缩算法库。
OpenSSLBoringSSL:用于安全通信和加密。
Snappy:快速压缩和解压缩库。
Zlib:通用的数据压缩库。
Zstd:Zstandard 快速压缩算法库。
CMake:用于构建 Folly 项目的构建工具。
pkg-config:用于获取已安装库的编译和链接参数。
Python (通常是 Python 2 和 Python 3):一些构建脚本可能需要 Python。
GCC/Clang:C++ 编译器,推荐使用较新版本的 GCC 或 Clang 以获得更好的 C++14/17 支持。

安装步骤 (以 Ubuntu 为例)

在 Ubuntu 系统上,可以通过以下步骤安装 Folly 的依赖库。

1.双击鼠标左键复制此行;2.单击复制所有代码。
1sudo apt-get update
2sudo apt-get install -y \
3 cmake \
4 pkg-config \
5 python \
6 libboost-dev \
7 libboost-system-dev \
8 libboost-thread-dev \
9 libboost-filesystem-dev \
10 libboost-program-options-dev \
11 libboost-regex-dev \
12 libboost-context-dev \
13 libboost-coroutine-dev \
14 libboost-atomic-dev \
15 libboost-chrono-dev \
16 libdouble-conversion-dev \
17 libgflags-dev \
18 libglog-dev \
19 libevent-dev \
20 liblz4-dev \
21 libssl-dev \
22 libsnappy-dev \
23 zlib1g-dev \
24 libzstd-dev

其他系统

对于其他操作系统 (如 macOS, CentOS, Fedora 等),可以使用相应的包管理器 (如 brew, yum, dnf) 安装上述依赖库。例如,在 macOS 上可以使用 Homebrew:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1brew update
2brew install cmake pkg-config python boost double-conversion gflags glog libevent lz4 openssl snappy zlib zstd

手动构建 Folly

安装完依赖库后,需要手动下载 Folly 源代码并进行编译安装。可以从 Folly 的 GitHub 仓库 (<https://github.com/facebook/folly>) 获取源代码。

1.双击鼠标左键复制此行;2.单击复制所有代码。
1git clone https://github.com/facebook/folly.git
2cd folly
3mkdir build && cd build
4cmake ..
5make -j$(nproc) # 使用多核编译加速
6sudo make install

make install 默认会将 Folly 安装到 /usr/local/lib/usr/local/include/folly 等目录。如果需要自定义安装路径,可以使用 CMake 的 -DCMAKE_INSTALL_PREFIX 选项。

3.1.2 编译选项 (Compilation Options)

在编译使用 folly/Regex.h 的代码时,需要确保编译器能够找到 Folly 库的头文件和库文件。通常,在安装 Folly 后,pkg-config 可以帮助我们获取正确的编译和链接选项。

使用 pkg-config

假设你的源代码文件名为 regex_example.cpp,可以使用以下命令编译:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1c++ -std=c++17 -o regex_example regex_example.cpp $(pkg-config --cflags --libs folly)

-std=c++17:指定 C++ 标准为 C++17 或更高版本,Folly 推荐使用 C++17 或更新的标准。
$(pkg-config --cflags --libs folly):这部分命令会展开为 Folly 库所需的编译选项 (头文件路径) 和链接选项 (库文件路径和库名称)。

手动指定编译选项

如果 pkg-config 无法正常工作,或者需要更精细的控制,可以手动指定编译选项。假设 Folly 安装在 /usr/local 目录下,可以这样编译:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1c++ -std=c++17 -o regex_example regex_example.cpp -I/usr/local/include -L/usr/local/lib -lfolly -lfollybenchmark -lfolly_test_util -ldouble-conversion -lglog -lgflags -levent -llz4 -lzstd -lsnappy -lz -lssl -lcrypto -lboost_system -lboost_thread -lboost_filesystem -lboost_program_options -lboost_regex -lboost_context -lboost_coroutine -lboost_atomic

这个命令包含了:

-I/usr/local/include:指定头文件搜索路径为 /usr/local/include
-L/usr/local/lib:指定库文件搜索路径为 /usr/local/lib
-lfolly -lfollybenchmark ... -lboost_atomic:指定需要链接的 Folly 库及其依赖库。

CMake 构建

对于更复杂的项目,推荐使用 CMake 进行构建管理。一个简单的 CMakeLists.txt 文件示例如下:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1cmake_minimum_required(VERSION 3.10)
2project(RegexExample)
3find_package(Folly REQUIRED)
4add_executable(regex_example regex_example.cpp)
5target_link_libraries(regex_example PRIVATE Folly::folly)

然后,可以使用以下命令构建项目:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1mkdir build && cd build
2cmake ..
3make -j$(nproc)

这种方式更加清晰和易于维护,尤其是在项目依赖较多时。

3.2 folly::Regex 的基本使用 (Basic Usage of folly::Regex)

folly::Regex 库提供了类似于 std::regex 的接口,但通常在性能和功能上有所增强。本节将介绍 folly::Regex 的基本用法,包括如何创建 folly::Regex 对象,以及如何使用 isMatch(), find(), findAll() 等方法进行正则表达式匹配。

3.2.1 创建 folly::Regex 对象 (Creating folly::Regex Objects)

要使用 folly::Regex,首先需要创建一个 folly::Regex 对象。folly::Regex 类的构造函数可以接受多种类型的正则表达式模式,包括 C 风格字符串、std::stringfolly::StringPiece

示例代码

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/Regex.h>
2#include <iostream>
3#include <string>
4int main() {
5 // ① 使用 C 风格字符串创建 Regex 对象
6 folly::Regex regex1("hello");
7 // ② 使用 std::string 创建 Regex 对象
8 std::string pattern = "world";
9 folly::Regex regex2(pattern);
10 // ③ 使用 folly::StringPiece 创建 Regex 对象 (假设已经有一个 folly::StringPiece 对象)
11 folly::StringPiece sp = "example";
12 folly::Regex regex3(sp);
13 std::cout << "Regex objects created successfully!" << std::endl;
14 return 0;
15}

在上述代码中,我们展示了三种创建 folly::Regex 对象的方法。构造函数会编译正则表达式模式,如果模式无效,将会抛出 folly::RegexException 异常。因此,在实际应用中,应该适当地处理异常。

3.2.2 folly::Regex::isMatch(): 匹配判断 (Matching Judgment with folly::Regex::isMatch())

folly::Regex::isMatch() 方法用于判断给定的输入字符串是否与正则表达式模式匹配。它返回一个布尔值,true 表示匹配成功,false 表示匹配失败。

示例代码

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/Regex.h>
2#include <iostream>
3#include <string>
4int main() {
5 folly::Regex regex("test");
6 std::string text1 = "This is a test string.";
7 std::string text2 = "No match here.";
8 // ① 判断 text1 是否匹配
9 if (regex.isMatch(text1)) {
10 std::cout << "\"" << text1 << "\" matches the regex." << std::endl;
11 } else {
12 std::cout << "\"" << text1 << "\" does not match the regex." << std::endl;
13 }
14 // ② 判断 text2 是否匹配
15 if (regex.isMatch(text2)) {
16 std::cout << "\"" << text2 << "\" matches the regex." << std::endl;
17 } else {
18 std::cout << "\"" << text2 << "\" does not match the regex." << std::endl;
19 }
20 return 0;
21}

输出结果

1.双击鼠标左键复制此行;2.单击复制所有代码。
1"This is a test string." matches the regex.
2"No match here." does not match the regex.

isMatch() 方法非常适合用于快速判断字符串是否符合某种模式,例如验证用户输入、过滤数据等场景。

3.2.3 folly::Regex::find():查找匹配 (Finding Matches with folly::Regex::find())

folly::Regex::find() 方法用于在输入字符串中查找第一个匹配正则表达式模式的子字符串。它返回一个 folly::Optional<folly::RegexMatch> 对象。如果找到匹配项,folly::Optional 对象包含一个 folly::RegexMatch 对象,否则 folly::Optional 对象为空。folly::RegexMatch 对象包含了匹配结果的详细信息,例如匹配的起始位置、长度以及捕获组 (capture groups) 的信息。

示例代码

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/Regex.h>
2#include <folly/Optional.h>
3#include <iostream>
4#include <string>
5int main() {
6 folly::Regex regex("(\\w+)@(\\w+)\\.(\\w+)"); // 匹配邮箱地址的正则表达式
7 std::string text = "Contact us at support@example.com or sales@company.net.";
8 // 查找第一个匹配项
9 folly::Optional<folly::RegexMatch> match = regex.find(text);
10 if (match.has_value()) {
11 std::cout << "Found match!" << std::endl;
12 std::cout << " Full match: \"" << match->str() << "\"" << std::endl; // 完整匹配的字符串
13 std::cout << " Start index: " << match->begin() << std::endl; // 匹配的起始索引
14 std::cout << " End index: " << match->end() << std::endl; // 匹配的结束索引
15 // 访问捕获组
16 std::cout << " Group 1 (username): \"" << match->group(1) << "\"" << std::endl;
17 std::cout << " Group 2 (domain): \"" << match->group(2) << "\"" << std::endl;
18 std::cout << " Group 3 (tld): \"" << match->group(3) << "\"" << std::endl;
19 } else {
20 std::cout << "No match found." << std::endl;
21 }
22 return 0;
23}

输出结果

1.双击鼠标左键复制此行;2.单击复制所有代码。
1Found match!
2 Full match: "support@example.com"
3 Start index: 15
4 End index: 32
5 Group 1 (username): "support"
6 Group 2 (domain): "example"
7 Group 3 (tld): "com"

在上述代码中,我们使用了 find() 方法查找文本中的第一个邮箱地址。如果找到匹配,我们通过 match->str() 获取完整匹配的字符串,通过 match->begin()match->end() 获取匹配的起始和结束索引,并通过 match->group(n) 获取第 n 个捕获组的内容。

3.2.4 folly::Regex::findAll():查找所有匹配 (Finding All Matches with folly::Regex::findAll())

folly::Regex::findAll() 方法用于在输入字符串中查找所有匹配正则表达式模式的子字符串。它返回一个 std::vector<folly::RegexMatch> 对象,其中包含了所有匹配项的 folly::RegexMatch 对象。

示例代码

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/Regex.h>
2#include <iostream>
3#include <string>
4#include <vector>
5int main() {
6 folly::Regex regex("\\d+"); // 匹配一个或多个数字
7 std::string text = "There are 123 apples and 45 oranges, total 168 fruits.";
8 // 查找所有匹配项
9 std::vector<folly::RegexMatch> matches = regex.findAll(text);
10 if (!matches.empty()) {
11 std::cout << "Found " << matches.size() << " matches:" << std::endl;
12 for (const auto& match : matches) {
13 std::cout << " Match: \"" << match.str() << "\", Start index: " << match.begin() << ", End index: " << match.end() << std::endl;
14 }
15 } else {
16 std::cout << "No matches found." << std::endl;
17 }
18 return 0;
19}

输出结果

1.双击鼠标左键复制此行;2.单击复制所有代码。
1Found 3 matches:
2 Match: "123", Start index: 10, End index: 13
3 Match: "45", Start index: 23, End index: 25
4 Match: "168", Start index: 42, End index: 45

findAll() 方法非常适合用于需要提取文本中所有符合某种模式的子字符串的场景,例如日志分析、数据挖掘等。

3.3 简单代码示例 (Simple Code Examples)

为了帮助读者更好地理解 folly/Regex.h 的基本用法,本节将提供两个简单的代码示例:邮箱地址验证和 URL 提取。

3.3.1 邮箱地址验证 (Email Address Validation)

邮箱地址验证是正则表达式的经典应用场景之一。以下代码示例展示了如何使用 folly/Regex.h 验证邮箱地址的格式是否正确。

示例代码

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/Regex.h>
2#include <iostream>
3#include <string>
4bool isValidEmail(const std::string& email) {
5 // 一个简单的邮箱地址正则表达式
6 folly::Regex regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
7 return regex.isMatch(email);
8}
9int main() {
10 std::string email1 = "test@example.com";
11 std::string email2 = "invalid-email";
12 std::string email3 = "another.test@sub.domain.net";
13 std::cout << "\"" << email1 << "\" is valid email: " << (isValidEmail(email1) ? "true" : "false") << std::endl;
14 std::cout << "\"" << email2 << "\" is valid email: " << (isValidEmail(email2) ? "true" : "false") << std::endl;
15 std::cout << "\"" << email3 << "\" is valid email: " << (isValidEmail(email3) ? "true" : "false") << std::endl;
16 return 0;
17}

输出结果

1.双击鼠标左键复制此行;2.单击复制所有代码。
1"test@example.com" is valid email: true
2"invalid-email" is valid email: false
3"another.test@sub.domain.net" is valid email: true

正则表达式解释^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$

^:匹配字符串的开始。
[a-zA-Z0-9._%+-]+:匹配一个或多个字母、数字、点、下划线、百分号、加号或减号 (用户名部分)。
@:匹配 @ 字符。
[a-zA-Z0-9.-]+:匹配一个或多个字母、数字、点或减号 (域名部分)。
\.:匹配点字符 (需要转义)。
[a-zA-Z]{2,}:匹配两个或多个字母 (顶级域名部分)。
$:匹配字符串的结束。

请注意,这只是一个简单的邮箱地址验证正则表达式,实际应用中可能需要更复杂的正则表达式来处理各种特殊情况。

3.3.2 URL 提取 (URL Extraction)

URL 提取是另一个常见的正则表达式应用场景。以下代码示例展示了如何使用 folly/Regex.h 从文本中提取 URL。

示例代码

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/Regex.h>
2#include <iostream>
3#include <string>
4#include <vector>
5int main() {
6 std::string text = "Visit our website at https://www.example.com or http://blog.example.net for more information.";
7 // 一个简单的 URL 正则表达式
8 folly::Regex regex("(https?://[\\w./-]+)");
9 std::vector<folly::RegexMatch> matches = regex.findAll(text);
10 if (!matches.empty()) {
11 std::cout << "URLs found:" << std::endl;
12 for (const auto& match : matches) {
13 std::cout << " " << match.str() << std::endl;
14 }
15 } else {
16 std::cout << "No URLs found." << std::endl;
17 }
18 return 0;
19}

输出结果

1.双击鼠标左键复制此行;2.单击复制所有代码。
1URLs found:
2 https://www.example.com
3 http://blog.example.net

正则表达式解释(https?://[\\w./-]+)

( ):分组,用于捕获整个 URL。
https?:匹配 httphttps? 表示 s 出现零次或一次。
://:匹配 :// 字符串。
[\\w./-]+:匹配一个或多个字母、数字、下划线、点、斜杠或减号 (URL 的路径和域名部分)。
():分组结束。

这个正则表达式可以提取以 http://https:// 开头的简单 URL。对于更复杂的 URL 格式,可能需要更精细的正则表达式。

通过本章的学习,读者应该已经掌握了 folly/Regex.h 的基本环境配置、编译方法以及基本使用方式,包括创建 folly::Regex 对象,使用 isMatch(), find(), findAll() 方法进行匹配判断和查找,并了解了如何通过简单的代码示例应用这些知识。在接下来的章节中,我们将深入探讨 folly/Regex.h 的进阶应用和高级特性。

END_OF_CHAPTER

4. chapter 4: folly/Regex.h 进阶应用 (Advanced Applications of folly/Regex.h)

本章将深入探讨 folly/Regex.h 库的一些高级应用,旨在帮助读者掌握更强大的正则表达式使用技巧,从而能够处理更复杂的文本匹配和处理任务。我们将涵盖迭代器、子匹配、正则表达式选项以及错误处理等方面,并通过实际的代码示例,展示如何在实际开发中灵活运用这些高级特性。

4.1 迭代器 (Iterators)

在处理大型文本或需要多次查找匹配项时,迭代器是一种非常高效的方式。folly/Regex.h 提供了迭代器支持,允许我们逐个访问匹配结果,而无需一次性加载所有结果到内存中。这对于性能敏感的应用场景尤其重要。

4.1.1 folly::Regex::findEach():迭代匹配结果 (Iterating Match Results with folly::Regex::findEach())

folly::Regex::findEach() 方法是 folly/Regex.h 库提供的用于迭代匹配结果的关键接口。它返回一个迭代器,允许我们遍历输入文本中所有与正则表达式匹配的子串。与 findAll() 方法一次性返回所有匹配结果不同,findEach() 允许我们按需处理每个匹配项,从而节省内存并提高效率。

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/Regex.h>
2#include <iostream>
3#include <string>
4int main() {
5 std::string text = "apple banana apple orange apple";
6 folly::Regex regex("apple");
7 // 使用 findEach 迭代匹配结果
8 for (auto const& match : regex.findEach(text)) {
9 std::cout << "找到匹配项: " << match.str()
10 << ", 位置: " << match.begin() << "-" << match.end() << std::endl;
11 }
12 return 0;
13}

代码解释:

① 我们首先包含了必要的头文件 folly/Regex.h,并引入了 iostreamstring 以进行输入输出和字符串操作。
② 定义了一个字符串 text,其中包含多个 "apple" 单词。
③ 创建了一个 folly::Regex 对象 regex,用于匹配 "apple"。
④ 使用 regex.findEach(text) 获取一个迭代器范围,并使用 for-range 循环遍历每个匹配项 match
⑤ 在循环中,我们使用 match.str() 获取匹配的字符串,match.begin()match.end() 获取匹配项在原始文本中的起始和结束位置,并将这些信息打印到控制台。

输出结果:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1找到匹配项: apple, 位置: 0-5
2找到匹配项: apple, 位置: 13-18
3找到匹配项: apple, 位置: 26-31

从输出结果可以看出,findEach() 方法成功找到了文本中所有 "apple" 的匹配项,并输出了它们的位置信息。

4.1.2 使用迭代器处理大型文本 (Using Iterators to Process Large Text)

当处理非常大的文本文件或数据流时,一次性加载所有内容到内存中可能不可行。findEach() 迭代器在这种情况下就显得尤为重要。我们可以逐行或分块读取文本,并使用迭代器处理每个部分,从而避免内存溢出,并提高处理效率。

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/Regex.h>
2#include <iostream>
3#include <fstream>
4#include <string>
5int main() {
6 std::ifstream inputFile("large_text_file.txt"); // 假设存在一个名为 large_text_file.txt 的大型文本文件
7 if (!inputFile.is_open()) {
8 std::cerr << "无法打开文件 large_text_file.txt" << std::endl;
9 return 1;
10 }
11 folly::Regex regex("\\b\\w+\\b"); // 匹配单词
12 std::string line;
13 while (std::getline(inputFile, line)) { // 逐行读取文件
14 for (auto const& match : regex.findEach(line)) {
15 std::cout << "找到单词: " << match.str() << std::endl;
16 // 在这里可以对每个匹配到的单词进行进一步处理
17 }
18 }
19 inputFile.close();
20 return 0;
21}

代码解释:

① 我们包含了 fstream 头文件,用于文件操作。
② 打开名为 "large_text_file.txt" 的文件,并检查文件是否成功打开。
③ 创建了一个正则表达式 regex("\\b\\w+\\b"),用于匹配单词(\b 匹配单词边界,\w+ 匹配一个或多个单词字符)。
④ 使用 std::getline(inputFile, line) 逐行读取文件内容到 line 字符串中。
⑤ 对于每一行 line,使用 regex.findEach(line) 获取迭代器,并遍历匹配到的每个单词。
⑥ 在循环中,我们打印出找到的单词,并可以在注释处添加对单词的进一步处理逻辑。

优势:

内存效率: 迭代器模式避免了一次性加载整个大型文本到内存,而是逐个处理匹配项,显著降低了内存消耗。
处理能力: 可以处理任意大小的文本,只要能够逐块读取,就可以使用迭代器进行处理。
灵活性: 允许在找到每个匹配项后立即进行处理,可以实现更复杂的流式处理逻辑。

4.2 子匹配 (Submatches)

正则表达式的分组捕获功能允许我们提取匹配字符串中的特定部分,这些被捕获的部分称为子匹配(Submatches)。folly/Regex.h 提供了访问和操作子匹配的功能,使得我们可以更精细地解析和处理文本数据。

4.2.1 获取子匹配结果 (Getting Submatch Results)

使用 () 括号在正则表达式中创建捕获组。folly::RegexMatch 对象提供了访问这些捕获组的方法。folly::RegexMatchoperator[] 允许通过索引访问捕获组,索引 0 代表整个匹配,索引 1, 2, 3... 代表正则表达式中从左到右的第 1, 2, 3... 个捕获组。

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/Regex.h>
2#include <iostream>
3#include <string>
4int main() {
5 std::string text = "Name: John Doe, Age: 30";
6 folly::Regex regex("Name: (\\w+ \\w+), Age: (\\d+)"); // 两个捕获组:姓名和年龄
7 auto match = regex.find(text);
8 if (match) {
9 std::cout << "完整匹配: " << match->str() << std::endl;
10 std::cout << "姓名: " << match->str(1) << std::endl; // 获取第一个捕获组
11 std::cout << "年龄: " << match->str(2) << std::endl; // 获取第二个捕获组
12 } else {
13 std::cout << "未找到匹配项" << std::endl;
14 }
15 return 0;
16}

代码解释:

① 定义了一个包含姓名和年龄信息的字符串 text
② 创建了一个正则表达式 regex,其中包含两个捕获组:
▮▮▮▮⚝ (\\w+ \\w+) 捕获姓名(两个单词)。
▮▮▮▮⚝ (\\d+) 捕获年龄(一个或多个数字)。
③ 使用 regex.find(text) 查找匹配项,并将结果存储在 match 中。
④ 如果找到匹配项(if (match)),则:
▮▮▮▮⚝ match->str() 获取整个匹配的字符串。
▮▮▮▮⚝ match->str(1) 获取第一个捕获组(姓名)的字符串。
▮▮▮▮⚝ match->str(2) 获取第二个捕获组(年龄)的字符串。
⑤ 将完整匹配和子匹配结果打印到控制台。

输出结果:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1完整匹配: Name: John Doe, Age: 30
2姓名: John Doe
3年龄: 30

从输出结果可以看出,我们成功地从匹配的字符串中提取出了姓名和年龄信息,这得益于子匹配的功能。

4.2.2 命名捕获组 (Named Capture Groups)

为了提高正则表达式的可读性和可维护性,folly/Regex.h 支持命名捕获组。我们可以为捕获组指定名称,然后通过名称而不是索引来访问子匹配结果。命名捕获组的语法是 (?<name>...)(?'name'...)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/Regex.h>
2#include <iostream>
3#include <string>
4int main() {
5 std::string text = "Name: John Doe, Age: 30";
6 folly::Regex regex("Name: (?<name>\\w+ \\w+), Age: (?<age>\\d+)"); // 命名捕获组:name 和 age
7 auto match = regex.find(text);
8 if (match) {
9 std::cout << "完整匹配: " << match->str() << std::endl;
10 std::cout << "姓名: " << match->str("name") << std::endl; // 通过名称获取捕获组
11 std::cout << "年龄: " << match->str("age") << std::endl; // 通过名称获取捕获组
12 } else {
13 std::cout << "未找到匹配项" << std::endl;
14 }
15 return 0;
16}

代码解释:

① 正则表达式 regex 中使用了命名捕获组:
▮▮▮▮⚝ (?<name>\\w+ \\w+) 将捕获组命名为 "name"。
▮▮▮▮⚝ (?<age>\\d+) 将捕获组命名为 "age"。
② 在获取子匹配结果时,我们使用 match->str("name")match->str("age"),通过名称而不是索引来访问捕获组。

输出结果:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1完整匹配: Name: John Doe, Age: 30
2姓名: John Doe
3年龄: 30

使用命名捕获组的好处在于,即使正则表达式的结构发生变化,只要捕获组的名称保持不变,访问子匹配的代码就不需要修改,提高了代码的健壮性和可读性。尤其是在复杂的正则表达式中,命名捕获组可以极大地提升代码的可维护性。

4.3 正则表达式选项 (Regular Expression Options)

folly/Regex.h 提供了丰富的选项来控制正则表达式的匹配行为。通过 folly::RegexOptions 类,我们可以配置各种选项,例如忽略大小写、多行模式、Unicode 支持等,以满足不同的匹配需求。

4.3.1 folly::RegexOptions:选项配置 (Option Configuration with folly::RegexOptions)

folly::RegexOptions 类允许我们设置正则表达式的选项。在创建 folly::Regex 对象时,可以将 folly::RegexOptions 对象作为参数传入,从而应用指定的选项。

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/Regex.h>
2#include <iostream>
3#include <string>
4int main() {
5 std::string text = "Hello World";
6 folly::RegexOptions options;
7 options.setIgnoreCase(true); // 设置忽略大小写选项
8 folly::Regex regex("hello", options); // 创建 Regex 对象时传入选项
9 if (regex.isMatch(text)) {
10 std::cout << "找到匹配项 (忽略大小写)" << std::endl;
11 } else {
12 std::cout << "未找到匹配项" << std::endl;
13 }
14 return 0;
15}

代码解释:

① 创建了一个 folly::RegexOptions 对象 options
② 使用 options.setIgnoreCase(true) 设置忽略大小写选项。
③ 在创建 folly::Regex 对象 regex 时,将 options 对象作为第二个参数传入。
④ 使用 regex.isMatch(text) 进行匹配判断。由于设置了忽略大小写选项,"hello" 可以匹配到 "Hello"。

常用的 folly::RegexOptions 方法:

setIgnoreCase(bool ignoreCase): 设置是否忽略大小写。
setMultiline(bool multiline): 设置是否启用多行模式(影响 ^$ 的行为)。
setDotall(bool dotall): 设置 . 是否匹配换行符。
setUnicode(bool unicode): 设置是否启用 Unicode 支持。
setExtended(bool extended): 设置是否启用扩展模式(允许在正则表达式中使用空格和注释)。

4.3.2 常用选项详解 (Detailed Explanation of Common Options)

以下详细解释几个常用的 folly::RegexOptions 选项:

忽略大小写 (IgnoreCase)
▮▮▮▮⚝ 选项方法: options.setIgnoreCase(true)
▮▮▮▮⚝ 作用: 使正则表达式在匹配时忽略字符的大小写差异。例如,设置此选项后,"abc" 可以匹配 "AbC", "aBc", "ABC" 等。
▮▮▮▮⚝ 应用场景: 文本搜索、用户输入验证等,当大小写不重要时非常有用。

多行模式 (Multiline)
▮▮▮▮⚝ 选项方法: options.setMultiline(true)
▮▮▮▮⚝ 作用: 影响 ^$ 锚点的行为。
▮▮▮▮▮▮▮▮⚝ 默认情况下(多行模式关闭),^ 匹配整个输入字符串的开始位置,$ 匹配整个输入字符串的结束位置。
▮▮▮▮▮▮▮▮⚝ 启用多行模式后,^ 匹配每一行的开始位置,$ 匹配每一行的结束位置。
▮▮▮▮⚝ 应用场景: 处理包含多行文本的数据,例如日志文件、多行配置文本等。

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/Regex.h>
2#include <iostream>
3#include <string>
4int main() {
5 std::string text = "line1\nline2\nline3";
6 folly::RegexOptions options;
7 options.setMultiline(true); // 启用多行模式
8 folly::Regex regex("^line\\d+$", options); // 匹配以 "line" 开头,数字结尾的行
9 for (auto const& match : regex.findEach(text)) {
10 std::cout << "匹配行: " << match.str() << std::endl;
11 }
12 return 0;
13}

输出结果:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1匹配行: line1
2匹配行: line2
3匹配行: line3

在多行模式下,^line\\d+$ 成功匹配了每一行以 "line" 开头和数字结尾的行。

点号匹配所有 (Dotall)
▮▮▮▮⚝ 选项方法: options.setDotall(true)
▮▮▮▮⚝ 作用: 改变 . 元字符的行为。
▮▮▮▮▮▮▮▮⚝ 默认情况下(Dotall 模式关闭),. 匹配除换行符 \n 之外的任何字符。
▮▮▮▮▮▮▮▮⚝ 启用 Dotall 模式后,. 可以匹配包括换行符在内的任何字符。
▮▮▮▮⚝ 应用场景: 需要跨越多行匹配的场景,例如匹配 HTML 或 XML 文档中的标签内容。

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/Regex.h>
2#include <iostream>
3#include <string>
4int main() {
5 std::string text = "<div>\nThis is\nmulti-line\ncontent.\n</div>";
6 folly::RegexOptions options;
7 options.setDotall(true); // 启用 Dotall 模式
8 folly::Regex regex("<div>(.*)</div>", options); // 匹配 <div>...</div> 之间的所有内容
9 auto match = regex.find(text);
10 if (match) {
11 std::cout << "匹配内容: " << match->str(1) << std::endl;
12 }
13 return 0;
14}

输出结果:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1匹配内容:
2This is
3multi-line
4content.

在 Dotall 模式下,(.*) 成功匹配了 <div></div> 标签之间的所有内容,包括换行符。

Unicode 支持 (Unicode)
▮▮▮▮⚝ 选项方法: options.setUnicode(true)
▮▮▮▮⚝ 作用: 启用 Unicode 支持,使得正则表达式可以正确处理 Unicode 字符。
▮▮▮▮⚝ 应用场景: 处理包含多语言字符的文本,例如国际化应用、多语言文档处理等。
▮▮▮▮⚝ 注意: 启用 Unicode 支持可能会对性能产生一定影响。

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/Regex.h>
2#include <iostream>
3#include <string>
4int main() {
5 std::string text = "你好,世界!"; // 包含中文和标点符号的 Unicode 字符串
6 folly::RegexOptions options;
7 options.setUnicode(true); // 启用 Unicode 支持
8 folly::Regex regex("\\w+", options); // 匹配单词字符 (包括 Unicode 字符)
9 for (auto const& match : regex.findEach(text)) {
10 std::cout << "匹配单词: " << match.str() << std::endl;
11 }
12 return 0;
13}

输出结果:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1匹配单词: 你好
2匹配单词: 世界

启用 Unicode 支持后,\w+ 可以正确匹配中文单词 "你好" 和 "世界"。

4.4 错误处理 (Error Handling)

正则表达式的编译和匹配过程中可能会出现错误,例如正则表达式语法错误、资源耗尽等。folly/Regex.h 使用异常来处理这些错误,提供了 folly::RegexException 异常类型,用于表示正则表达式相关的错误。

4.4.1 folly::RegexException:异常类型 (Exception Type folly::RegexException)

folly::RegexExceptionfolly/Regex.h 库中用于表示正则表达式异常的类。当正则表达式编译或匹配过程中发生错误时,会抛出 folly::RegexException 类型的异常。

folly::RegexException 继承自 std::runtime_error,提供了标准的异常接口。我们可以通过 what() 方法获取异常的详细错误信息。

4.4.2 异常处理实践 (Error Handling Practices)

为了保证程序的健壮性,我们需要妥善处理正则表达式可能抛出的异常。通常使用 try-catch 块来捕获和处理 folly::RegexException 异常。

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/Regex.h>
2#include <iostream>
3#include <string>
4int main() {
5 std::string text = "some text";
6 std::string regexPattern = "("; // 错误的正则表达式,缺少闭括号
7 try {
8 folly::Regex regex(regexPattern); // 尝试编译错误的正则表达式
9 if (regex.isMatch(text)) {
10 std::cout << "找到匹配项" << std::endl;
11 }
12 } catch (const folly::RegexException& e) {
13 std::cerr << "正则表达式错误: " << e.what() << std::endl; // 捕获并处理异常
14 }
15 return 0;
16}

代码解释:

① 定义了一个错误的正则表达式模式 regexPattern = "(",它缺少闭括号,会导致编译错误。
② 使用 try-catch 块包围可能抛出异常的代码。
③ 在 try 块中,尝试创建 folly::Regex 对象 regex(regexPattern)。由于 regexPattern 是错误的正则表达式,这将抛出 folly::RegexException 异常。
④ 在 catch 块中,捕获 folly::RegexException 类型的异常 e
⑤ 使用 e.what() 获取异常的错误信息,并将其打印到错误输出流 std::cerr

输出结果:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1正则表达式错误: regex error: parentheses not balanced at position 1

从输出结果可以看出,我们成功捕获了 folly::RegexException 异常,并获取了详细的错误信息 "parentheses not balanced at position 1",这有助于我们诊断和修复正则表达式错误。

最佳实践:

始终使用 try-catch 包围正则表达式的编译和匹配代码,特别是当正则表达式的来源不可靠(例如用户输入)时。
详细记录异常信息,包括错误类型、错误位置等,以便于调试和问题排查。
根据实际应用场景选择合适的错误处理策略,例如,可以向用户显示友好的错误提示,或者记录错误日志并进行重试等。

通过本章的学习,我们深入了解了 folly/Regex.h 库的进阶应用,包括迭代器、子匹配、正则表达式选项和错误处理。掌握这些高级特性,可以帮助我们更有效地处理复杂的文本匹配和处理任务,并编写出更健壮、更高效的正则表达式应用。

END_OF_CHAPTER

5. chapter 5: 高级正则表达式语法 (Advanced Regular Expression Syntax)

5.1 环视断言 (Lookaround Assertions)

环视断言(Lookaround Assertions)是正则表达式中一种非常强大的零宽度断言(zero-width assertions)机制。它们允许你在匹配某个模式的同时,对当前匹配位置的前后环境进行条件判断,但不消耗任何字符。这意味着环视断言本身不包含在最终的匹配结果中,仅仅作为匹配成功的条件。环视断言极大地增强了正则表达式的灵活性和精确性,使得我们可以处理更复杂的文本匹配任务。

5.1.1 正向肯定环视 (?=...) (Positive Lookahead (?=...))

正向肯定环视 (?=...) 表示当前匹配位置的右侧必须满足括号 ... 内的模式才能成功匹配。它“环顾”右侧,并“断言”右侧的内容必须是某种模式,但实际匹配结果不包含环视部分匹配的内容。

例如,我们要匹配所有后面跟着 "USD" 的数字金额。正则表达式可以写成 \d+(?=USD)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: "The price is 123USD, and another is 456EUR."
2正则表达式: \d+(?=USD)
3匹配结果: "123"

在这个例子中,\d+ 匹配一个或多个数字,(?=USD) 是正向肯定环视,它断言数字后面必须紧跟着 "USD"。因此,只有 "123" 满足条件,而 "456" 虽然也是数字,但后面跟着的是 "EUR",不满足环视条件,所以不会被匹配。

应用场景:

① 查找后面跟着特定字符或模式的文本。
② 在替换操作中,只替换满足特定后置条件的文本。
③ 验证输入格式,例如密码强度验证,要求密码后面必须包含特定字符类型。

代码示例 (伪代码,概念演示):

1.双击鼠标左键复制此行;2.单击复制所有代码。
1// 假设 folly::Regex 支持环视断言
2folly::Regex regex("\\d+(?=USD)");
3std::string text = "The price is 123USD, and another is 456EUR.";
4auto matches = regex.findAll(text);
5for (const auto& match : matches) {
6 std::cout << match.str() << std::endl; // 输出: 123
7}

5.1.2 正向否定环视 (?!...) (Negative Lookahead (?!...))

正向否定环视 (?!...) 表示当前匹配位置的右侧必须不满足括号 ... 内的模式才能成功匹配。它“环顾”右侧,并“断言”右侧的内容不能是某种模式。同样,实际匹配结果不包含环视部分匹配的内容。

例如,我们要匹配所有后面不是 "USD" 的数字金额。正则表达式可以写成 \d+(?!USD)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: "The price is 123USD, and another is 456EUR."
2正则表达式: \d+(?!USD)
3匹配结果: "456"

在这个例子中,\d+ 匹配一个或多个数字,(?!USD) 是正向否定环视,它断言数字后面不能紧跟着 "USD"。因此,只有 "456" 满足条件,因为 "456" 后面跟着的是 "EUR",不满足否定环视的条件,而被匹配。"123" 后面跟着 "USD",不满足否定环视条件,所以不会被匹配。

应用场景:

① 排除后面跟着特定字符或模式的文本。
② 在数据清洗中,移除不符合特定后置条件的文本。
③ 过滤掉不希望匹配的特定上下文中的模式。

代码示例 (伪代码,概念演示):

1.双击鼠标左键复制此行;2.单击复制所有代码。
1// 假设 folly::Regex 支持环视断言
2folly::Regex regex("\\d+(?!USD)");
3std::string text = "The price is 123USD, and another is 456EUR.";
4auto matches = regex.findAll(text);
5for (const auto& match : matches) {
6 std::cout << match.str() << std::endl; // 输出: 456
7}

5.1.3 反向肯定环视 (?<=...) (Positive Lookbehind (?<=...))

反向肯定环视 (?<=...) 表示当前匹配位置的左侧必须满足括号 ... 内的模式才能成功匹配。它“环顾”左侧,并“断言”左侧的内容必须是某种模式。同样,实际匹配结果不包含环视部分匹配的内容。

注意: 大多数正则表达式引擎对反向环视的模式有一定限制,通常要求是固定长度的。这是因为引擎需要预先知道反向环视的长度才能正确地回溯和匹配。不过,folly/Regex.h 的具体实现需要查阅文档确认是否支持变长反向环视。如果不支持,需要使用固定长度的模式。

例如,我们要匹配所有前面跟着 "$" 符号的数字金额。正则表达式可以写成 (?<=\$)\d+

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: "The price is $123, and another is 456EUR."
2正则表达式: (?<=\$)\d+
3匹配结果: "123"

在这个例子中,(?<=\$) 是反向肯定环视,它断言数字前面必须紧跟着 "$" 符号。\d+ 匹配一个或多个数字。因此,只有 "123" 满足条件,因为 "123" 前面是 "$" 符号。 "456" 前面是空格,不满足环视条件,所以不会被匹配。

应用场景:

① 查找前面跟着特定字符或模式的文本。
② 在数据提取中,只提取满足特定前置条件的文本。
③ 处理结构化数据,例如提取特定标签内的内容。

代码示例 (伪代码,概念演示):

1.双击鼠标左键复制此行;2.单击复制所有代码。
1// 假设 folly::Regex 支持环视断言
2folly::Regex regex("(?<=\$)\\d+");
3std::string text = "The price is $123, and another is 456EUR.";
4auto matches = regex.findAll(text);
5for (const auto& match : matches) {
6 std::cout << match.str() << std::endl; // 输出: 123
7}

5.1.4 反向否定环视 (?<!...) (Negative Lookbehind (?<!...))

反向否定环视 (?<!...) 表示当前匹配位置的左侧必须不满足括号 ... 内的模式才能成功匹配。它“环顾”左侧,并“断言”左侧的内容不能是某种模式。同样,实际匹配结果不包含环视部分匹配的内容。

注意: 与反向肯定环视类似,反向否定环视的模式也可能受到长度限制,具体取决于 folly/Regex.h 的实现。

例如,我们要匹配所有前面不是 "$" 符号的数字金额。正则表达式可以写成 (?<!\$)\d+

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: "The price is $123, and another is 456EUR."
2正则表达式: (?<!\$)\d+
3匹配结果: "456"

在这个例子中,(?<!\$) 是反向否定环视,它断言数字前面不能紧跟着 "$" 符号。\d+ 匹配一个或多个数字。因此,只有 "456" 满足条件,因为 "456" 前面是空格,不满足否定环视的条件,而被匹配。"123" 前面是 "$" 符号,不满足否定环视条件,所以不会被匹配。

应用场景:

① 排除前面跟着特定字符或模式的文本。
② 在数据清洗中,移除不符合特定前置条件的文本。
③ 过滤掉不希望匹配的特定上下文中的模式。

代码示例 (伪代码,概念演示):

1.双击鼠标左键复制此行;2.单击复制所有代码。
1// 假设 folly::Regex 支持环视断言
2folly::Regex regex("(?<!\$)\\d+");
3std::string text = "The price is $123, and another is 456EUR.";
4auto matches = regex.findAll(text);
5for (const auto& match : matches) {
6 std::cout << match.str() << std::endl; // 输出: 456
7}

5.2 条件表达式 (Conditional Expressions)

条件表达式(Conditional Expressions)是正则表达式中更高级的特性,它允许你根据条件来选择不同的匹配模式。条件通常基于捕获组是否匹配成功或者环视断言是否为真。条件表达式使得正则表达式可以处理更加复杂的逻辑分支。

folly/Regex.h 对条件表达式的支持程度需要查阅其官方文档。常见的条件表达式语法有两种形式:

(?(condition)then|else):如果 condition 为真,则匹配 then 部分,否则匹配 else 部分。else 部分是可选的,可以写成 (?(condition)then)
(?(group_number)then|else)(?(group_name)then|else):根据编号或名称为 group_numbergroup_name 的捕获组是否匹配成功来决定匹配 then 部分还是 else 部分。

示例:匹配美国或加拿大的电话号码,区号可选,但如果出现区号,则必须是括号包围。

美国电话号码格式可能为: (区号) 电话号码电话号码
加拿大电话号码格式可能为: (区号) 电话号码电话号码

假设区号是3位数字,电话号码是7位数字,区号用括号包围是可选的。

正则表达式可以写成: \(?(\d{3})?\)?[ -]?(\d{7})

现在我们想改进这个表达式,使得只有当存在左括号 ( 时,才必须有右括号 )。 这时可以使用条件表达式。

改进后的正则表达式 (假设 folly/Regex.h 支持条件表达式和捕获组回溯引用): (\()?\d{3}(?(1)\)|)[ -]?\d{7}

(\()?: 尝试匹配一个左括号 (,并将其放入第一个捕获组 (组号为 1)。 ? 使其可选。
\d{3}: 匹配三位数字 (区号)。
(?(1)\)|): 这是一个条件表达式。 (?(1)) 检查第一个捕获组是否匹配成功(即是否存在左括号)。
▮▮▮▮⚝ 如果第一个捕获组匹配成功(存在左括号),则执行 then 部分 \),即匹配一个右括号 ).
▮▮▮▮⚝ 如果第一个捕获组匹配失败(不存在左括号),则执行 else 部分 (为空),即什么都不匹配。
[ -]?: 匹配一个可选的空格或连字符 -
\d{7}: 匹配七位数字 (电话号码)。

应用场景:

① 处理格式不完全统一的数据,例如可选部分出现时必须满足特定条件。
② 根据不同的上下文环境应用不同的匹配规则。
③ 实现更复杂的文本解析逻辑。

代码示例 (伪代码,概念演示):

1.双击鼠标左键复制此行;2.单击复制所有代码。
1// 假设 folly::Regex 支持条件表达式和捕获组回溯引用
2folly::Regex regex("(\\()?\\d{3}(?(1)\\)|)[ -]?\\d{7}");
3std::vector<std::string> phoneNumbers = {
4 "(123)4567890",
5 "1234567890",
6 "(1234567890", // 错误格式,缺少右括号,但根据表达式,这也会被部分匹配,需要更精确的表达式
7 "123)4567890" // 错误格式,多余右括号,但根据表达式,这也会被部分匹配,需要更精确的表达式
8};
9for (const auto& number : phoneNumbers) {
10 if (regex.isMatch(number)) {
11 std::cout << number << " is a valid phone number format." << std::endl;
12 } else {
13 std::cout << number << " is not a valid phone number format." << std::endl;
14 }
15}

更精确的电话号码匹配 (使用环视断言和条件表达式结合):

为了更精确地匹配带括号的区号,可以结合环视断言和条件表达式,确保括号成对出现。

^(?:\((\d{3})\)|(\d{3}))[ -]?(\d{7})$ (不使用条件表达式,使用分支选择)

或者使用条件表达式和环视断言 (更复杂,可能过度设计,但展示条件表达式的应用):

^(?(\()(?<=\()(\d{3})\)(?=[ -]?)|\d{3})[ -]?(\d{7})$ (这个表达式可能过于复杂,可读性差,实际应用中应权衡复杂性和可读性)

5.3 递归正则表达式 (Recursive Regular Expressions) (如果 folly/Regex.h 支持)

递归正则表达式(Recursive Regular Expressions)是一种非常高级的特性,它允许正则表达式自身调用自身,从而可以处理嵌套结构的数据,例如嵌套的括号、XML 或 JSON 结构等。

folly/Regex.h 对递归正则表达式的支持需要查阅其官方文档。 并非所有正则表达式引擎都支持递归正则表达式,因为其实现较为复杂,并且可能带来性能问题。

示例:匹配嵌套的括号。

例如,我们要匹配如下形式的字符串,其中括号可以任意嵌套: ( ( ) ( ( ) ) )

使用递归正则表达式 (假设语法为 (?R) 表示递归调用整个正则表达式): \(([^()]|(?R))*\)

\(: 匹配一个左括号。
([^()]|(?R))*: 匹配零个或多个以下内容:
▮▮▮▮⚝ [^()]: 匹配任何不是括号的字符。
▮▮▮▮⚝ |(?R): 或者递归调用整个正则表达式 (?R),即再次匹配一个括号对,允许嵌套。
\): 匹配一个右括号。

应用场景:

① 解析嵌套的数据结构,例如 XML, JSON, 带有嵌套括号的表达式。
② 验证代码语法,例如检查括号是否匹配。
③ 处理具有递归定义的文本格式。

代码示例 (伪代码,概念演示,假设 folly::Regex 支持递归正则表达式):

1.双击鼠标左键复制此行;2.单击复制所有代码。
1// 假设 folly::Regex 支持递归正则表达式
2folly::Regex regex("\\(([^()]|(?R))*\\)");
3std::vector<std::string> nestedParentheses = {
4 "()",
5 "(())",
6 "((()))",
7 "(()())",
8 "( ( ) ( ( ) ) )",
9 "(( )" // 不匹配,缺少右括号
10};
11for (const auto& parentheses : nestedParentheses) {
12 if (regex.isMatch(parentheses)) {
13 std::cout << parentheses << " is a valid nested parentheses string." << std::endl;
14 } else {
15 std::cout << parentheses << " is not a valid nested parentheses string." << std::endl;
16 }
17}

注意: 递归正则表达式可能会导致性能问题,特别是当嵌套层级很深或者正则表达式写得不当时,可能引发回溯风暴。 使用时需要谨慎,并进行性能测试。 此外,folly/Regex.h 对递归正则表达式的支持需要确认。如果不支持,则需要寻找其他方法来处理嵌套结构的数据,例如使用解析器 (parser) 而不是纯粹的正则表达式。

5.4 Unicode 支持 (Unicode Support)

Unicode 支持(Unicode Support)在现代文本处理中至关重要。 Unicode 是一种字符编码标准,旨在涵盖世界上所有的书写系统。 folly/Regex.h 对 Unicode 的支持程度直接影响其处理多语言文本的能力。

5.4.1 Unicode 字符属性 (Unicode Character Properties)

Unicode 字符属性(Unicode Character Properties)是指 Unicode 标准为每个字符定义的各种属性,例如字符所属的类别(字母、数字、标点符号等)、脚本(拉丁文、中文、阿拉伯文等)以及其他特性。

正则表达式引擎如果支持 Unicode 字符属性,就可以使用特殊的转义序列来匹配具有特定属性的字符,而不仅仅是 ASCII 字符集。 常见的 Unicode 字符属性类别包括:

\p{L}\p{Letter}: 匹配任何字母字符。
\p{N}\p{Number}: 匹配任何数字字符。
\p{P}\p{Punct}: 匹配任何标点符号字符。
\p{S}\p{Symbol}: 匹配任何符号字符。
\p{Z}\p{Separator}: 匹配任何分隔符字符(例如空格、制表符)。
\p{Lu}\p{Uppercase_Letter}: 匹配大写字母。
\p{Ll}\p{Lowercase_Letter}: 匹配小写字母。
\p{Nd}\p{Decimal_Number}: 匹配十进制数字。
\p{Sc}\p{Currency_Symbol}: 匹配货币符号。

以及更多其他属性,可以参考 Unicode 标准文档。

示例:匹配任何语言的字母和数字。

如果我们想匹配任何语言的字母和数字,而不仅仅是 ASCII 字母和数字,可以使用 Unicode 字符属性。

正则表达式: \p{L}+\p{N}+

\p{L}+: 匹配一个或多个任何语言的字母字符。
\p{N}+: 匹配一个或多个任何语言的数字字符。

1.双击鼠标左键复制此行;2.单击复制所有代码。
1文本: "你好123 こんにちは456"
2正则表达式: \p{L}+\p{N}+
3匹配结果: "你好123", "こんにちは456"

在这个例子中,\p{L} 可以匹配中文 "你好" 和日文 "こんにちは" 中的字母字符,\p{N} 可以匹配数字 "123" 和 "456"。

应用场景:

① 处理多语言文本,例如国际化应用。
② 更精确地匹配特定类型的字符,而不仅仅局限于 ASCII 字符集。
③ 数据清洗和验证,例如验证用户输入是否包含特定类型的 Unicode 字符。

代码示例 (伪代码,概念演示,假设 folly::Regex 支持 Unicode 字符属性):

1.双击鼠标左键复制此行;2.单击复制所有代码。
1// 假设 folly::Regex 支持 Unicode 字符属性
2folly::Regex regex("\\p{L}+\\p{N}+");
3std::string text = "你好123 こんにちは456 English789";
4auto matches = regex.findAll(text);
5for (const auto& match : matches) {
6 std::cout << match.str() << std::endl;
7 // 输出:
8 // 你好123
9 // こんにちは456
10 // English789
11}

5.4.2 Unicode 模式匹配 (Unicode Pattern Matching)

Unicode 模式匹配(Unicode Pattern Matching)是指正则表达式引擎在处理 Unicode 字符时,如何进行字符匹配、大小写转换、以及其他与语言相关的操作。 正确的 Unicode 模式匹配需要考虑以下方面:

字符编码: folly/Regex.h 需要支持 UTF-8 等 Unicode 编码格式,以便正确解析和处理多字节字符。
大小写不敏感匹配: 在 Unicode 环境下,大小写不敏感匹配需要考虑不同语言的大小写规则。 例如,土耳其语中 'i' 和 'I' 的大小写转换与英语不同。 正确的 Unicode 大小写不敏感匹配需要使用 Unicode 标准定义的大小写折叠 (case folding) 规则。
单词边界: 单词边界 \b 在 Unicode 环境下也需要正确处理不同语言的单词分隔符。 单词边界的定义可能因语言而异。
行尾和行首: ^$ 锚点在多行模式下,需要正确识别 Unicode 换行符 (例如 \n, \r, \r\n, \u2028, \u2029, \u0085)。
字符排序和 Collation: 在某些语言中,字符的排序规则很复杂,例如在德语中,'ä' 可能被视为 'ae' 排序。 高级的 Unicode 正则表达式引擎可能支持 Collation 感知的匹配和排序。

folly/Regex.h 的 Unicode 支持能力需要查阅其官方文档,了解其具体实现和支持的 Unicode 特性。 如果 folly/Regex.h 的 Unicode 支持有限,在处理复杂的国际化文本时可能需要借助其他库或技术。

最佳实践:

明确指定 UTF-8 编码: 确保你的输入文本和正则表达式模式都使用 UTF-8 编码,并告知 folly/Regex.h 使用 UTF-8 模式 (如果需要显式指定)。
使用 Unicode 字符属性: 尽可能使用 Unicode 字符属性 \p{...} 来匹配字符类别,而不是使用 ASCII 范围 [a-zA-Z0-9] 等,以提高代码的国际化兼容性。
测试多语言文本: 在开发国际化应用时,务必使用各种语言的文本进行测试,确保正则表达式在不同语言环境下都能正确工作。
查阅文档: 仔细阅读 folly/Regex.h 的文档,了解其 Unicode 支持的详细信息和限制。

总结:

Unicode 支持是现代正则表达式库的重要组成部分。 了解 folly/Regex.h 的 Unicode 支持能力,并遵循 Unicode 最佳实践,可以帮助你编写出更健壮、更国际化的文本处理程序。

END_OF_CHAPTER

6. chapter 6: folly/Regex.h API 全面解析 (Comprehensive API Analysis of folly/Regex.h)

6.1 folly::Regex 类详解 (Detailed Explanation of folly::Regex Class)

6.1.1 构造函数与析构函数 (Constructors and Destructor)

folly::Regex 类是 folly/Regex.h 库的核心,用于表示编译后的正则表达式。本节将详细介绍 folly::Regex 类的构造函数和析构函数,帮助读者理解如何创建和销毁 folly::Regex 对象。

构造函数 (Constructors)

folly::Regex 提供了多个构造函数,允许从不同的源创建正则表达式对象。以下是常用的构造函数:

默认构造函数 (Default Constructor)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1Regex();

⚝ 默认构造函数创建一个无效的 folly::Regex 对象。
注意:默认构造函数创建的对象在使用前必须通过赋值操作符或移动操作符赋予有效的正则表达式,否则尝试使用该对象将导致未定义行为。
适用场景:在需要稍后初始化 folly::Regex 对象,或者作为类成员变量时使用。

正则表达式字符串构造函数 (Regex String Constructor)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1explicit Regex(folly::StringPiece pattern, RegexOptions options = RegexOptions());
2explicit Regex(const std::string& pattern, RegexOptions options = RegexOptions());
3explicit Regex(const char* pattern, RegexOptions options = RegexOptions());

⚝ 这些构造函数接受一个正则表达式模式字符串 (pattern) 和可选的正则表达式选项 (options)。
pattern 可以是 folly::StringPiecestd::string,或者 C 风格的字符串 (const char*)。
options 参数是 folly::RegexOptions 对象,用于配置正则表达式的行为,例如是否忽略大小写、是否支持 Unicode 等。如果未提供 options,则使用默认选项 RegexOptions()
异常:如果提供的 pattern 是无效的正则表达式,构造函数会抛出 folly::RegexException 异常。
代码示例

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/regex.h>
2#include <iostream>
3int main() {
4 try {
5 folly::Regex regex1("abc"); // 从 folly::StringPiece 创建
6 folly::Regex regex2(std::string("def")); // 从 std::string 创建
7 folly::Regex regex3("ghi", folly::RegexOptions::IgnoreCase); // 从 C 风格字符串创建,并设置忽略大小写选项
8 std::cout << "Regex objects created successfully." << std::endl;
9 } catch (const folly::RegexException& e) {
10 std::cerr << "RegexException caught: " << e.what() << std::endl;
11 return 1;
12 }
13 return 0;
14}

拷贝构造函数 (Copy Constructor)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1Regex(const Regex& other);

⚝ 拷贝构造函数创建一个新的 folly::Regex 对象,它是现有 folly::Regex 对象 other 的副本。
⚝ 新对象和原始对象包含相同的正则表达式模式和选项,但它们是独立的实体。
适用场景:当需要复制一个已存在的 folly::Regex 对象时使用,例如在函数参数传递或容器存储中。

移动构造函数 (Move Constructor)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1Regex(Regex&& other) noexcept;

⚝ 移动构造函数通过转移资源(而不是复制)来创建一个新的 folly::Regex 对象,源对象 other 的状态变为有效但未指定。
⚝ 移动构造函数通常比拷贝构造函数更高效,因为它避免了深层复制操作。
适用场景:当源 folly::Regex 对象是临时对象,或者在之后不再需要使用时,移动构造函数是更优的选择。例如,从函数返回 folly::Regex 对象时,会自动调用移动构造函数。

析构函数 (Destructor)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1~Regex();

folly::Regex 的析构函数负责释放 folly::Regex 对象占用的资源,例如编译后的正则表达式引擎内部数据结构所占用的内存。
自动调用:当 folly::Regex 对象的生命周期结束时(例如,超出作用域或被显式删除),析构函数会自动被调用。
资源管理:用户通常不需要显式调用析构函数,folly::Regex 类的析构函数会自动处理资源清理工作,确保内存安全和资源释放。

通过合理使用 folly::Regex 提供的构造函数和析构函数,可以有效地创建和管理正则表达式对象,为后续的正则表达式匹配和操作奠定基础。在实际应用中,应根据具体需求选择合适的构造函数,并注意异常处理,确保程序的健壮性。

6.1.2 成员函数详解 (Detailed Explanation of Member Functions)

folly::Regex 类提供了丰富的成员函数,用于执行正则表达式的匹配、查找、替换等操作。本节将详细介绍这些常用成员函数,帮助读者掌握 folly::Regex 的核心功能。

匹配判断函数 (Matching Judgment Functions)

isMatch()

1.双击鼠标左键复制此行;2.单击复制所有代码。
1bool isMatch(folly::StringPiece text) const;
2bool isMatch(const std::string& text) const;
3bool isMatch(const char* text) const;

isMatch() 函数用于判断正则表达式是否完全匹配整个输入文本 (text)。
参数:接受 folly::StringPiecestd::string 或 C 风格字符串作为输入文本。
返回值:如果正则表达式完全匹配输入文本,则返回 true;否则返回 false
代码示例

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/regex.h>
2#include <iostream>
3int main() {
4 folly::Regex regex("hello");
5 std::cout << std::boolalpha; // 设置输出 bool 值为 true/false
6 std::cout << "Is 'hello' a full match for 'hello'? " << regex.isMatch("hello") << std::endl; // true
7 std::cout << "Is 'world' a full match for 'hello'? " << regex.isMatch("world") << std::endl; // false
8 std::cout << "Is 'hello world' a full match for 'hello'? " << regex.isMatch("hello world") << std::endl; // false
9 return 0;
10}

查找匹配函数 (Finding Match Functions)

find()

1.双击鼠标左键复制此行;2.单击复制所有代码。
1folly::Optional<RegexMatch> find(folly::StringPiece text) const;
2folly::Optional<RegexMatch> find(const std::string& text) const;
3folly::Optional<RegexMatch> find(const char* text) const;

find() 函数在输入文本 (text) 中查找第一个与正则表达式匹配的子串。
参数:接受 folly::StringPiecestd::string 或 C 风格字符串作为输入文本。
返回值
▮▮▮▮⚝ 如果找到匹配项,返回一个 folly::Optional<RegexMatch> 对象,其中包含匹配结果 (RegexMatch 对象)。
▮▮▮▮⚝ 如果未找到匹配项,返回 folly::none
代码示例

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/regex.h>
2#include <iostream>
3int main() {
4 folly::Regex regex("world");
5 folly::Optional<folly::RegexMatch> match = regex.find("hello world, regex world!");
6 if (match) {
7 std::cout << "Match found!" << std::endl;
8 std::cout << "Matched text: " << match->text() << std::endl; // 输出匹配的文本 "world"
9 std::cout << "Position: " << match->begin() << std::endl; // 输出匹配开始的位置
10 } else {
11 std::cout << "Match not found." << std::endl;
12 }
13 return 0;
14}

findAll()

1.双击鼠标左键复制此行;2.单击复制所有代码。
1std::vector<RegexMatch> findAll(folly::StringPiece text) const;
2std::vector<RegexMatch> findAll(const std::string& text) const;
3std::vector<RegexMatch> findAll(const char* text) const;

findAll() 函数在输入文本 (text) 中查找所有与正则表达式匹配的子串。
参数:接受 folly::StringPiecestd::string 或 C 风格字符串作为输入文本。
返回值:返回一个 std::vector<RegexMatch> 容器,其中包含所有匹配结果 (RegexMatch 对象)。如果未找到任何匹配项,则返回一个空 vector。
代码示例

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/regex.h>
2#include <iostream>
3int main() {
4 folly::Regex regex("world");
5 std::vector<folly::RegexMatch> matches = regex.findAll("hello world, regex world!");
6 if (!matches.empty()) {
7 std::cout << "Matches found!" << std::endl;
8 for (const auto& match : matches) {
9 std::cout << "Matched text: " << match.text() << std::endl; // 输出每个匹配的文本 "world"
10 std::cout << "Position: " << match.begin() << std::endl; // 输出每个匹配开始的位置
11 std::cout << "-------------------" << std::endl;
12 }
13 } else {
14 std::cout << "No matches found." << std::endl;
15 }
16 return 0;
17}

findEach()

1.双击鼠标左键复制此行;2.单击复制所有代码。
1template <typename MatchCallback>
2void findEach(folly::StringPiece text, MatchCallback callback) const;
3template <typename MatchCallback>
4void findEach(const std::string& text, MatchCallback callback) const;
5template <typename MatchCallback>
6void findEach(const char* text, MatchCallback callback) const;

findEach() 函数在输入文本 (text) 中查找所有匹配项,并对每个匹配项执行回调函数 (callback)。
参数
▮▮▮▮⚝ text:输入文本,可以是 folly::StringPiecestd::string 或 C 风格字符串。
▮▮▮▮⚝ callback:一个函数对象(例如 lambda 表达式、函数指针或实现了 operator() 的类),它接受一个 RegexMatch 对象作为参数。findEach() 会为每个找到的匹配项调用此回调函数。
返回值void
适用场景:当需要对每个匹配结果进行自定义处理时,findEach()findAll() 更高效,因为它避免了创建和存储所有匹配结果的 vector。尤其适用于处理大型文本或需要流式处理匹配结果的场景。
代码示例

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/regex.h>
2#include <iostream>
3int main() {
4 folly::Regex regex("\\b\\w+\\b"); // 匹配单词
5 std::string text = "This is a test string for regex.";
6 std::cout << "Words found:" << std::endl;
7 regex.findEach(text, [](const folly::RegexMatch& match) {
8 std::cout << "- " << match.text() << std::endl; // 输出每个匹配的单词
9 });
10 return 0;
11}

其他成员函数 (Other Member Functions)

options()

1.双击鼠标左键复制此行;2.单击复制所有代码。
1RegexOptions options() const;

options() 函数返回创建 folly::Regex 对象时使用的 folly::RegexOptions 对象。
返回值folly::RegexOptions 对象,包含了正则表达式的选项设置。
用途:可以用于检查正则表达式对象所使用的选项,例如是否忽略大小写、是否使用 Unicode 支持等。

empty()

1.双击鼠标左键复制此行;2.单击复制所有代码。
1bool empty() const;

empty() 函数检查 folly::Regex 对象是否为空,即是否包含有效的正则表达式。
返回值:如果 folly::Regex 对象为空,则返回 true;否则返回 false
应用场景:可以用于检查默认构造函数创建的 folly::Regex 对象是否已经被赋予了有效的正则表达式。

assign()

1.双击鼠标左键复制此行;2.单击复制所有代码。
1Regex& assign(folly::StringPiece pattern, RegexOptions options = RegexOptions());
2Regex& assign(const std::string& pattern, RegexOptions options = RegexOptions());
3Regex& assign(const char* pattern, RegexOptions options = RegexOptions());
4Regex& assign(const Regex& other);
5Regex& assign(Regex&& other) noexcept;

assign() 函数用于为现有的 folly::Regex 对象赋予新的正则表达式模式和选项。它类似于赋值操作符,但提供了更明确的函数调用形式。
参数:与构造函数类似,接受正则表达式模式字符串 (pattern) 和可选的正则表达式选项 (options),或者另一个 folly::Regex 对象 (other)。
返回值:返回对当前 folly::Regex 对象的引用 (Regex&),支持链式调用。
异常:如果提供的 pattern 是无效的正则表达式,assign() 函数会抛出 folly::RegexException 异常。
代码示例

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/regex.h>
2#include <iostream>
3int main() {
4 folly::Regex regex; // 默认构造函数创建空对象
5 try {
6 regex.assign("new pattern"); // 使用 assign() 赋予新的正则表达式
7 std::cout << "Regex object assigned successfully." << std::endl;
8 } catch (const folly::RegexException& e) {
9 std::cerr << "RegexException caught: " << e.what() << std::endl;
10 return 1;
11 }
12 return 0;
13}

swap()

1.双击鼠标左键复制此行;2.单击复制所有代码。
1void swap(Regex& other) noexcept;

swap() 函数交换当前 folly::Regex 对象和另一个 folly::Regex 对象 other 的内容。
参数:另一个 folly::Regex 对象的引用 (Regex& other)。
操作:高效地交换两个 folly::Regex 对象内部的正则表达式模式、选项和状态,而无需进行深层复制。
应用场景:在需要交换两个 folly::Regex 对象内容时,例如在算法实现或资源管理中,swap() 可以提供高效的交换操作。

赋值操作符 (Assignment Operators)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1Regex& operator=(const Regex& other);
2Regex& operator=(Regex&& other) noexcept;

⚝ 赋值操作符允许将一个 folly::Regex 对象赋值给另一个 folly::Regex 对象。
拷贝赋值 (Copy Assignment)operator=(const Regex& other) 执行深层拷贝,将 other 对象的内容复制到当前对象。
移动赋值 (Move Assignment)operator=(Regex&& other) noexcept 执行移动操作,将 other 对象的资源转移到当前对象,other 对象变为有效但未指定的状态。
代码示例

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/regex.h>
2#include <iostream>
3int main() {
4 folly::Regex regex1("pattern1");
5 folly::Regex regex2;
6 regex2 = regex1; // 拷贝赋值,regex2 变为 regex1 的副本
7 std::cout << "regex2 assigned from regex1 (copy)." << std::endl;
8 folly::Regex regex3("pattern3");
9 folly::Regex regex4;
10 regex4 = std::move(regex3); // 移动赋值,regex4 获取 regex3 的资源
11 std::cout << "regex4 assigned from regex3 (move)." << std::endl;
12 return 0;
13}

通过熟练掌握 folly::Regex 类的这些成员函数,读者可以灵活地进行正则表达式的匹配、查找和管理,从而在各种文本处理任务中高效地使用 folly/Regex.h 库。在实际应用中,应根据具体需求选择合适的成员函数,并结合错误处理机制,编写健壮可靠的正则表达式代码。

6.2 folly::RegexMatch 类详解 (Detailed Explanation of folly::RegexMatch Class)

folly::RegexMatch 类用于表示正则表达式匹配的结果。当使用 folly::Regexfind()findAll()findEach() 等函数进行匹配操作时,如果找到匹配项,就会返回一个或多个 folly::RegexMatch 对象。本节将详细介绍 folly::RegexMatch 类的成员函数,帮助读者理解如何访问和处理匹配结果。

基本信息访问函数 (Basic Information Access Functions)

text()

1.双击鼠标左键复制此行;2.单击复制所有代码。
1folly::StringPiece text() const;

text() 函数返回匹配的文本内容。
返回值folly::StringPiece 对象,表示匹配的子字符串。
代码示例

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/regex.h>
2#include <iostream>
3int main() {
4 folly::Regex regex("(\\w+) (\\w+)"); // 匹配两个单词
5 folly::Optional<folly::RegexMatch> match = regex.find("John Doe is here.");
6 if (match) {
7 std::cout << "Matched text: " << match->text() << std::endl; // 输出 "John Doe"
8 }
9 return 0;
10}

begin()

1.双击鼠标左键复制此行;2.单击复制所有代码。
1size_t begin() const;

begin() 函数返回匹配子串在原始输入文本中的起始位置(索引)。
返回值size_t 类型,表示起始位置的索引,从 0 开始计数。
代码示例

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/regex.h>
2#include <iostream>
3int main() {
4 folly::Regex regex("regex");
5 folly::Optional<folly::RegexMatch> match = regex.find("This is a regex example.");
6 if (match) {
7 std::cout << "Match starts at position: " << match->begin() << std::endl; // 输出 10 (regex 在 "This is a regex example." 中的起始位置)
8 }
9 return 0;
10}

end()

1.双击鼠标左键复制此行;2.单击复制所有代码。
1size_t end() const;

end() 函数返回匹配子串在原始输入文本中的结束位置的下一个位置(索引)。换句话说,结束位置索引指向匹配子串之后紧邻的字符。
返回值size_t 类型,表示结束位置的索引。
代码示例

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/regex.h>
2#include <iostream>
3int main() {
4 folly::Regex regex("regex");
5 folly::Optional<folly::RegexMatch> match = regex.find("This is a regex example.");
6 if (match) {
7 std::cout << "Match ends at position: " << match->end() << std::endl; // 输出 15 (regex 在 "This is a regex example." 中的结束位置的下一个位置)
8 std::cout << "Match length: " << match->end() - match->begin() << std::endl; // 输出 5 (匹配的长度)
9 }
10 return 0;
11}

子匹配 (Submatch) 访问函数

当正则表达式中包含分组 () 时,folly::RegexMatch 对象可以访问捕获组匹配的子串。

submatchCount()

1.双击鼠标左键复制此行;2.单击复制所有代码。
1size_t submatchCount() const;

submatchCount() 函数返回匹配结果中捕获组的数量。
返回值size_t 类型,表示捕获组的数量。
注意:捕获组的索引从 0 开始,索引 0 代表整个匹配,索引 1, 2, 3... 代表正则表达式中从左到右的第一个、第二个、第三个... 捕获组。

submatch(size_t index)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1folly::StringPiece submatch(size_t index) const;

submatch(index) 函数返回指定索引 (index) 的捕获组匹配的子串。
参数index,要访问的捕获组的索引,范围从 0 到 submatchCount() - 1
返回值folly::StringPiece 对象,表示指定索引的捕获组匹配的子字符串。如果索引超出范围,行为未定义。
代码示例

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/regex.h>
2#include <iostream>
3int main() {
4 folly::Regex regex("(\\w+) (\\w+)"); // 两个捕获组,分别捕获两个单词
5 folly::Optional<folly::RegexMatch> match = regex.find("John Doe is here.");
6 if (match) {
7 std::cout << "Total submatches: " << match->submatchCount() << std::endl; // 输出 3 (包括索引 0 的整个匹配)
8 std::cout << "Submatch 0 (whole match): " << match->submatch(0) << std::endl; // 输出 "John Doe"
9 std::cout << "Submatch 1 (first word): " << match->submatch(1) << std::endl; // 输出 "John"
10 std::cout << "Submatch 2 (second word): " << match->submatch(2) << std::endl; // 输出 "Doe"
11 }
12 return 0;
13}

submatchBegin(size_t index)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1size_t submatchBegin(size_t index) const;

submatchBegin(index) 函数返回指定索引 (index) 的捕获组匹配的子串在原始输入文本中的起始位置(索引)。
参数index,要访问的捕获组的索引。
返回值size_t 类型,表示起始位置的索引。

submatchEnd(size_t index)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1size_t submatchEnd(size_t index) const;

submatchEnd(index) 函数返回指定索引 (index) 的捕获组匹配的子串在原始输入文本中的结束位置的下一个位置(索引)。
参数index,要访问的捕获组的索引。
返回值size_t 类型,表示结束位置的索引。

通过 folly::RegexMatch 提供的这些成员函数,可以方便地访问匹配结果的各个方面,包括整个匹配的文本、位置信息,以及各个捕获组匹配的子串。在实际应用中,可以根据需要灵活地使用这些函数,提取和处理正则表达式匹配的结果。

6.3 folly::RegexOptions 类详解 (Detailed Explanation of folly::RegexOptions Class)

folly::RegexOptions 类用于配置 folly::Regex 对象的行为,例如是否忽略大小写、是否支持 Unicode 等。通过设置不同的选项,可以改变正则表达式的匹配模式。本节将详细介绍 folly::RegexOptions 类提供的常用选项。

常用选项 (Common Options)

folly::RegexOptions 类提供了一系列静态成员常量,用于表示不同的正则表达式选项。这些选项可以通过位或操作符 | 组合使用。

RegexOptions::None

描述:默认选项,不启用任何特殊行为。
:0

RegexOptions::IgnoreCase

描述:忽略大小写进行匹配。例如,使用此选项后,正则表达式 abc 可以匹配 AbCaBc 等。
1 << 0

RegexOptions::Multiline

描述:多行模式。在此模式下,^$ 锚点不仅匹配整个输入文本的开始和结束,还匹配每一行的开始和结束。行的分隔符通常是换行符 \n
1 << 1

RegexOptions::SingleLine

描述:单行模式(也称为 dotall 模式)。在此模式下,. 元字符可以匹配包括换行符 \n 在内的任意字符。默认情况下,. 不匹配换行符。
1 << 2

RegexOptions::Extended

描述:扩展模式(也称为 verbose 模式)。在此模式下,正则表达式中的空白字符(空格、制表符、换行符等)会被忽略,并且允许使用 # 符号添加注释。这可以提高正则表达式的可读性,尤其是在编写复杂的正则表达式时。
1 << 3

RegexOptions::Unicode

描述:Unicode 支持模式。启用此选项后,正则表达式引擎将支持 Unicode 字符属性和 Unicode 相关的匹配规则。例如,可以使用 \p{Lu} 匹配大写字母 Unicode 字符。
1 << 4

选项的组合使用 (Combining Options)

可以使用位或操作符 | 将多个选项组合在一起,以同时启用多种行为。

代码示例

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/regex.h>
2#include <iostream>
3int main() {
4 // 组合使用 IgnoreCase 和 Multiline 选项
5 folly::RegexOptions options = folly::RegexOptions::IgnoreCase | folly::RegexOptions::Multiline;
6 folly::Regex regex("^[a-z]+$", options); // 匹配每行以小写字母开头的行
7 std::cout << std::boolalpha;
8 std::cout << "Match 'ABC' in multiline mode with ignore case? " << regex.isMatch("ABC") << std::endl; // true, 忽略大小写
9 std::cout << "Match 'ABC\nDEF' in multiline mode with ignore case? " << regex.isMatch("ABC\nDEF") << std::endl; // false, 因为是全匹配
10 return 0;
11}

构造函数 (Constructor)

folly::RegexOptions 类有一个默认构造函数,用于创建默认选项对象。

1.双击鼠标左键复制此行;2.单击复制所有代码。
1RegexOptions();

⚝ 默认构造函数创建的 folly::RegexOptions 对象等同于 RegexOptions::None,即不启用任何特殊选项。

使用选项 (Using Options)

folly::RegexOptions 对象作为参数传递给 folly::Regex 类的构造函数或 assign() 函数,以配置正则表达式的行为。

代码示例

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/regex.h>
2#include <iostream>
3int main() {
4 // 创建 RegexOptions 对象并设置选项
5 folly::RegexOptions options;
6 options |= folly::RegexOptions::IgnoreCase; // 启用忽略大小写
7 options |= folly::RegexOptions::Unicode; // 启用 Unicode 支持
8 // 使用设置好的选项创建 Regex 对象
9 folly::Regex regex("你好[a-z]+", options);
10 std::cout << std::boolalpha;
11 std::cout << "Match '你好World' with options? " << regex.isMatch("你好World") << std::endl; // true, 忽略大小写,Unicode 支持
12 return 0;
13}

通过灵活使用 folly::RegexOptions 类提供的选项,可以根据不同的匹配需求配置正则表达式的行为,从而实现更精确和强大的文本处理功能。在实际应用中,应根据具体场景选择合适的选项组合,以达到最佳的匹配效果和性能。

6.4 folly::RegexException 类详解 (Detailed Explanation of folly::RegexException Class)

folly::RegexException 类是 folly/Regex.h 库中用于表示正则表达式异常的类。当在正则表达式编译或匹配过程中发生错误时,例如正则表达式语法错误、资源不足等,folly/Regex.h 库会抛出 folly::RegexException 类型的异常。本节将介绍 folly::RegexException 类的基本信息和异常处理方法。

异常类型 (Exception Type)

folly::RegexException 继承自 std::runtime_error,是标准 C++ 异常类体系的一部分。这意味着可以使用标准的 try-catch 块来捕获和处理 folly::RegexException 异常。

成员函数 (Member Functions)

folly::RegexException 类主要继承了 std::runtime_error 的成员函数,其中最常用的成员函数是 what()

what()

1.双击鼠标左键复制此行;2.单击复制所有代码。
1const char* what() const noexcept override;

what() 函数返回一个 C 风格字符串,描述了异常发生的原因。
返回值const char*,指向描述异常信息的字符串。
用途:在 catch 块中,可以使用 what() 函数获取异常的详细信息,用于错误日志记录、用户提示或程序调试。

异常抛出场景 (Exception Throwing Scenarios)

folly::RegexException 异常通常在以下场景中抛出:

正则表达式编译错误:当传递给 folly::Regex 构造函数或 assign() 函数的正则表达式模式字符串包含语法错误时,例如不合法的元字符、括号不匹配等。
资源不足:在极少数情况下,如果系统资源(例如内存)不足以编译正则表达式,也可能抛出 folly::RegexException 异常。

异常处理实践 (Error Handling Practices)

为了保证程序的健壮性,应该使用 try-catch 块来捕获和处理 folly::RegexException 异常。

代码示例

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/regex.h>
2#include <iostream>
3int main() {
4 try {
5 folly::Regex regex("[invalid regex"); // 包含语法错误的正则表达式
6 // 后续使用 regex 的代码
7 } catch (const folly::RegexException& e) {
8 std::cerr << "RegexException caught: " << e.what() << std::endl; // 输出错误信息
9 // 进行错误处理,例如记录日志、提示用户、程序退出等
10 return 1; // 返回错误码
11 }
12 std::cout << "Program continues after exception handling." << std::endl; // 如果异常被捕获,程序可以继续执行
13 return 0;
14}

在上述代码示例中,try 块包含可能抛出 folly::RegexException 异常的代码(创建 folly::Regex 对象)。如果创建过程中发生异常,程序会跳转到 catch 块,捕获 folly::RegexException 类型的异常对象 e,并使用 e.what() 获取错误信息并输出到标准错误流。之后,程序可以继续执行 catch 块之后的代码。

在实际应用中,应该根据具体的错误处理需求,在 catch 块中编写相应的错误处理逻辑。例如,可以将错误信息记录到日志文件中,或者向用户显示友好的错误提示信息,或者根据错误类型进行不同的处理。合理的异常处理可以提高程序的可靠性和用户体验。

END_OF_CHAPTER

7. chapter 7: 实战案例分析 (Practical Case Studies)

7.1 日志分析 (Log Analysis)

日志分析是运维、开发和安全领域中的一项核心任务。通过分析日志,我们可以追踪系统运行状态、诊断错误、监控性能以及检测安全事件。正则表达式在日志分析中扮演着至关重要的角色,能够高效地从海量日志数据中提取关键信息,并进行格式化和标准化处理。folly/Regex.h 库凭借其高性能和易用性,成为 C++ 环境下日志分析的强大工具。

7.1.1 提取关键日志信息 (Extracting Key Log Information)

在复杂的日志系统中,日志信息通常包含时间戳、日志级别、模块名称、具体消息等多种信息。我们需要使用正则表达式从非结构化的日志文本中提取出这些关键字段,以便进行进一步的分析和处理。

案例描述
假设我们有如下格式的 Web 服务器访问日志,需要提取出访问时间和客户端 IP 地址。

1.双击鼠标左键复制此行;2.单击复制所有代码。
1[2023-10-27 10:00:00] [INFO] [nginx] Client IP: 192.168.1.100, Request: GET /index.html
2[2023-10-27 10:00:05] [ERROR] [nginx] Client IP: 192.168.1.101, Request: POST /api/user
3[2023-10-27 10:00:10] [WARNING] [app] User ID: 123, Action: Login Failed from IP: 192.168.1.102

正则表达式设计
为了提取时间和 IP 地址,我们可以设计如下正则表达式:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] .* IP: (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})

代码示例

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <iostream>
2#include <string>
3#include <vector>
4#include <folly/Regex.h>
5int main() {
6 std::vector<std::string> logs = {
7 "[2023-10-27 10:00:00] [INFO] [nginx] Client IP: 192.168.1.100, Request: GET /index.html",
8 "[2023-10-27 10:00:05] [ERROR] [nginx] Client IP: 192.168.1.101, Request: POST /api/user",
9 "[2023-10-27 10:00:10] [WARNING] [app] User ID: 123, Action: Login Failed from IP: 192.168.1.102"
10 };
11 folly::Regex regex(R"(\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] .* IP: (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))");
12 for (const auto& log : logs) {
13 folly::smatch match;
14 if (folly::regex_search(log, match, regex)) {
15 std::cout << "Timestamp: " << match[1] << ", IP Address: " << match[2] << std::endl;
16 }
17 }
18 return 0;
19}

代码解析
① 我们定义了一个包含多条日志的 std::vector<std::string>
② 使用 folly::Regex 创建正则表达式对象,原始字符串字面量 R"(...)" 用于避免转义字符的困扰。
③ 正则表达式 \[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] .* IP: (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) 包含两个捕获组 ()
▮▮▮▮⚝ 第一个捕获组 (\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) 匹配日期和时间,例如 2023-10-27 10:00:00
▮▮▮▮⚝ 第二个捕获组 (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) 匹配 IP 地址,例如 192.168.1.100
folly::regex_search() 函数用于在日志字符串中查找匹配项,并将匹配结果存储在 folly::smatch 对象 match 中。
⑤ 如果找到匹配项,则可以通过 match[1]match[2] 访问捕获组的内容,分别对应时间和 IP 地址。

输出结果

1.双击鼠标左键复制此行;2.单击复制所有代码。
1Timestamp: 2023-10-27 10:00:00, IP Address: 192.168.1.100
2Timestamp: 2023-10-27 10:00:05, IP Address: 192.168.1.101
3Timestamp: 2023-10-27 10:00:10, IP Address: 192.168.1.102

通过这个案例,我们展示了如何使用 folly/Regex.h 从日志中提取关键信息,为后续的日志分析奠定基础。

7.1.2 日志格式标准化 (Log Format Standardization)

在复杂的分布式系统中,不同组件或服务产生的日志格式可能不尽相同,这给日志分析带来了挑战。日志格式标准化旨在将不同格式的日志统一转换为结构化的格式,例如 JSON 或 CSV,以便于集中管理和分析。正则表达式可以用于解析各种非标准化的日志格式,并提取信息进行重新组织。

案例描述
假设我们有来自不同来源的两种日志格式,需要将它们标准化为统一的 JSON 格式。

日志格式 1 (旧格式)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1[Time: 10:00:00] - User: john.doe - Action: Login Success

日志格式 2 (新格式)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1timestamp=2023-10-27T10:05:00Z | level=INFO | component=auth | message=User authentication successful, username=jane.doe

目标 JSON 格式

1.双击鼠标左键复制此行;2.单击复制所有代码。
1{
2 "timestamp": "2023-10-27T10:00:00Z",
3 "level": "INFO",
4 "component": "auth",
5 "message": "User login success",
6 "username": "john.doe"
7}

正则表达式设计 (针对格式 1)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1\[Time: (\d{2}:\d{2}:\d{2})\] - User: (.*) - Action: (.*)

正则表达式设计 (针对格式 2)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1timestamp=(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z) \| level=(\w+) \| component=(\w+) \| message=(.*), username=(.*)

代码示例

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <iostream>
2#include <string>
3#include <vector>
4#include <folly/Regex.h>
5#include <sstream>
6#include <iomanip>
7std::string formatToJson(const std::string& log) {
8 folly::smatch match;
9 std::stringstream jsonStream;
10 jsonStream << "{";
11 // 尝试匹配格式 1
12 folly::Regex regex1(R"(\[Time: (\d{2}:\d{2}:\d{2})\] - User: (.*) - Action: (.*))");
13 if (folly::regex_search(log, match, regex1)) {
14 std::tm t{};
15 std::istringstream ss(match[1].str());
16 ss >> std::get_time(&t, "%H:%M:%S");
17 if (ss.fail()) {
18 // 处理解析错误
19 }
20 std::time_t timeSinceEpoch = std::mktime(&t);
21 char buffer[20];
22 std::strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", std::gmtime(&timeSinceEpoch));
23 jsonStream << "\"timestamp\": \"" << buffer << "\",";
24 jsonStream << "\"level\": \"INFO\","; // 假设旧格式都是 INFO 级别
25 jsonStream << "\"component\": \"unknown\","; // 组件未知
26 jsonStream << "\"message\": \"" << match[3] << "\",";
27 jsonStream << "\"username\": \"" << match[2] << "\"";
28 } else {
29 // 尝试匹配格式 2
30 folly::Regex regex2(R"(timestamp=(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z) \| level=(\w+) \| component=(\w+) \| message=(.*), username=(.*))");
31 if (folly::regex_search(log, match, regex2)) {
32 jsonStream << "\"timestamp\": \"" << match[1] << "\",";
33 jsonStream << "\"level\": \"" << match[2] << "\",";
34 jsonStream << "\"component\": \"" << match[3] << "\",";
35 jsonStream << "\"message\": \"" << match[4] << "\",";
36 jsonStream << "\"username\": \"" << match[5] << "\"";
37 } else {
38 return "{}"; // 无法识别的格式,返回空 JSON
39 }
40 }
41 jsonStream << "}";
42 return jsonStream.str();
43}
44int main() {
45 std::vector<std::string> logs = {
46 "[Time: 10:00:00] - User: john.doe - Action: Login Success",
47 "timestamp=2023-10-27T10:05:00Z | level=INFO | component=auth | message=User authentication successful, username=jane.doe"
48 };
49 for (const auto& log : logs) {
50 std::cout << "Original Log: " << log << std::endl;
51 std::cout << "JSON Format: " << formatToJson(log) << std::endl;
52 std::cout << "--------------------" << std::endl;
53 }
54 return 0;
55}

代码解析
formatToJson() 函数接收日志字符串作为输入,并返回 JSON 格式的字符串。
② 函数内部首先尝试使用 regex1 (针对旧格式) 进行匹配。如果匹配成功,则提取时间、用户名和动作,并将其转换为 JSON 格式。
▮▮▮▮⚝ 旧格式的时间需要转换为 ISO 8601 UTC 格式,使用了 <sstream><iomanip> 库进行时间格式化。
▮▮▮▮⚝ 旧格式日志级别和组件信息缺失,这里假设级别为 "INFO",组件为 "unknown"。
③ 如果 regex1 匹配失败,则尝试使用 regex2 (针对新格式) 进行匹配。如果匹配成功,则直接提取所有字段并转换为 JSON 格式。
④ 如果两种格式都无法匹配,则返回空 JSON 对象 {} 表示无法识别的日志格式。

输出结果

1.双击鼠标左键复制此行;2.单击复制所有代码。
1Original Log: [Time: 10:00:00] - User: john.doe - Action: Login Success
2JSON Format: {"timestamp": "1970-01-01T10:00:00Z","level": "INFO","component": "unknown","message": "Login Success","username": "john.doe"}
3--------------------
4Original Log: timestamp=2023-10-27T10:05:00Z | level=INFO | component=auth | message=User authentication successful, username=jane.doe
5JSON Format: {"timestamp": "2023-10-27T10:05:00Z","level": "INFO","component": "auth","message": "User authentication successful, username=jane.doe","username": "jane.doe"}
6--------------------

通过这个案例,我们演示了如何使用 folly/Regex.h 将不同格式的日志标准化为统一的 JSON 格式,这对于构建可维护和可扩展的日志分析系统至关重要。

7.2 网络数据包解析 (Network Packet Parsing)

网络数据包解析是网络协议分析、安全审计和网络性能监控等领域的关键技术。正则表达式可以用于解析网络数据包中的协议头部和有效载荷,提取关键信息,例如协议类型、源 IP 地址、目标端口、HTTP 请求方法等。folly/Regex.h 库可以高效地处理网络数据包的二进制数据,并从中提取文本信息进行正则匹配。

7.2.1 HTTP 协议头部解析 (HTTP Protocol Header Parsing)

HTTP 协议头部包含了丰富的元数据信息,例如请求方法、URL、User-Agent、Content-Type 等。使用正则表达式可以方便地从 HTTP 头部文本中提取这些信息。

案例描述
假设我们捕获到一个 HTTP 请求数据包,需要解析出请求方法和 URL。

HTTP 请求头部示例

1.双击鼠标左键复制此行;2.单击复制所有代码。
1GET /index.html HTTP/1.1
2Host: www.example.com
3User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
4Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
5Accept-Language: en-US,en;q=0.8
6Connection: keep-alive

正则表达式设计
为了提取请求方法和 URL,我们可以设计如下正则表达式:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1(GET|POST|PUT|DELETE|OPTIONS|HEAD|TRACE|CONNECT) (\S+) HTTP\/1\.1

代码示例

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <iostream>
2#include <string>
3#include <folly/Regex.h>
4int main() {
5 std::string httpHeader =
6 "GET /index.html HTTP/1.1\r\n"
7 "Host: www.example.com\r\n"
8 "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36\r\n"
9 "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n"
10 "Accept-Language: en-US,en;q=0.8\r\n"
11 "Connection: keep-alive\r\n";
12 folly::Regex regex(R"((GET|POST|PUT|DELETE|OPTIONS|HEAD|TRACE|CONNECT) (\S+) HTTP\/1\.1)");
13 folly::smatch match;
14 if (folly::regex_search(httpHeader, match, regex)) {
15 std::cout << "Request Method: " << match[1] << std::endl;
16 std::cout << "URL: " << match[2] << std::endl;
17 } else {
18 std::cout << "Failed to parse HTTP header." << std::endl;
19 }
20 return 0;
21}

代码解析
① 我们定义了一个包含 HTTP 请求头部的字符串 httpHeader,注意 \r\n 表示 HTTP 头部的行尾分隔符。
② 使用 folly::Regex 创建正则表达式对象。
③ 正则表达式 (GET|POST|PUT|DELETE|OPTIONS|HEAD|TRACE|CONNECT) (\S+) HTTP\/1\.1 包含两个捕获组:
▮▮▮▮⚝ 第一个捕获组 (GET|POST|PUT|DELETE|OPTIONS|HEAD|TRACE|CONNECT) 匹配常见的 HTTP 请求方法。
▮▮▮▮⚝ 第二个捕获组 (\S+) 匹配 URL,\S+ 表示一个或多个非空白字符。
folly::regex_search() 函数用于在 HTTP 头部字符串中查找匹配项。
⑤ 如果找到匹配项,则可以通过 match[1]match[2] 访问捕获组的内容,分别对应请求方法和 URL。

输出结果

1.双击鼠标左键复制此行;2.单击复制所有代码。
1Request Method: GET
2URL: /index.html

通过这个案例,我们展示了如何使用 folly/Regex.h 解析 HTTP 协议头部,提取关键的请求方法和 URL 信息。

7.2.2 自定义协议解析 (Custom Protocol Parsing)

除了标准协议,在很多场景下,我们需要处理自定义的网络协议。正则表达式同样可以用于解析自定义协议的数据包,提取协议字段和数据内容。

案例描述
假设我们有一个自定义的文本协议,数据包格式如下:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1[Packet Type: DATA] [Sequence: 123] [Payload Length: 10] Payload Data: <10 bytes of data>
2[Packet Type: ACK] [Sequence: 123]
3[Packet Type: ERROR] [Code: 500] [Message: Invalid data format]

我们需要根据数据包类型,解析出不同的字段。

正则表达式设计 (针对 DATA 包)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1\[Packet Type: DATA\] \[Sequence: (\d+)\] \[Payload Length: (\d+)\] Payload Data: (.{%payload_length%})

注意:%{payload_length%} 需要动态替换为实际的 payload length 值

正则表达式设计 (针对 ACK 包)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1\[Packet Type: ACK\] \[Sequence: (\d+)\]

正则表达式设计 (针对 ERROR 包)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1\[Packet Type: ERROR\] \[Code: (\d+)\] \[Message: (.*)\]

代码示例 (简化版,仅处理 DATA 包)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <iostream>
2#include <string>
3#include <folly/Regex.h>
4int main() {
5 std::string dataPacket = "[Packet Type: DATA] [Sequence: 456] [Payload Length: 15] Payload Data: This is payload";
6 folly::Regex regex(R"(\[Packet Type: DATA\] \[Sequence: (\d+)\] \[Payload Length: (\d+)\] Payload Data: (.*))");
7 folly::smatch match;
8 if (folly::regex_search(dataPacket, match, regex)) {
9 std::cout << "Packet Type: DATA" << std::endl;
10 std::cout << "Sequence: " << match[1] << std::endl;
11 std::cout << "Payload Length: " << match[2] << std::endl;
12 std::cout << "Payload Data: " << match[3] << std::endl;
13 } else {
14 std::cout << "Failed to parse data packet." << std::endl;
15 }
16 return 0;
17}

代码解析
① 我们定义了一个自定义数据包字符串 dataPacket
② 使用 folly::Regex 创建正则表达式对象,用于解析 DATA 包。
③ 正则表达式 \[Packet Type: DATA\] \[Sequence: (\d+)\] \[Payload Length: (\d+)\] Payload Data: (.*) 包含三个捕获组:
▮▮▮▮⚝ 第一个捕获组 (\d+) 匹配序列号。
▮▮▮▮⚝ 第二个捕获组 (\d+) 匹配 payload 长度。
▮▮▮▮⚝ 第三个捕获组 (.*) 匹配 payload 数据。
folly::regex_search() 函数用于在数据包字符串中查找匹配项。
⑤ 如果找到匹配项,则可以通过 match[1]match[2]match[3] 访问捕获组的内容,分别对应序列号、payload 长度和 payload 数据。

输出结果

1.双击鼠标左键复制此行;2.单击复制所有代码。
1Packet Type: DATA
2Sequence: 456
3Payload Length: 15
4Payload Data: This is payload

更复杂的场景
在实际的自定义协议解析中,可能需要根据 "Payload Length" 字段动态构建正则表达式,以正确匹配 Payload Data。这可能需要更复杂的逻辑,例如先提取 "Payload Length",然后根据长度动态构建正则表达式并再次进行匹配。folly/Regex.h 提供了强大的正则表达式功能,可以应对各种复杂的协议解析需求。

通过这个案例,我们展示了如何使用 folly/Regex.h 解析自定义的网络协议数据包,提取协议字段和数据内容。正则表达式在处理各种网络协议解析任务中都具有重要的应用价值。

7.3 文本编辑器中的正则表达式应用 (Regular Expression Applications in Text Editors)

正则表达式在文本编辑器中被广泛应用,用于实现强大的查找、替换和代码重构功能,极大地提升了文本处理效率和代码编辑效率。

7.3.1 查找与替换 (Find and Replace)

查找与替换是文本编辑器最基本的功能之一,而正则表达式的引入,使得查找与替换功能变得异常强大和灵活。用户可以使用正则表达式定义复杂的查找模式,并使用捕获组在替换操作中引用匹配到的内容。

案例描述
假设我们需要将 Markdown 文件中的所有一级标题 (以 # 开头) 替换为二级标题 (以 ## 开头)。

查找模式 (正则表达式)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1^# (.*)

替换模式

1.双击鼠标左键复制此行;2.单击复制所有代码。
1## \1

解释
① 查找模式 ^# (.*)
▮▮▮▮⚝ ^ 匹配行首。
▮▮▮▮⚝ # 匹配 # 字符和一个空格。
▮▮▮▮⚝ (.*) 匹配任意字符 (除换行符外) 零次或多次,并将其捕获到第一个分组 \1 中。
② 替换模式 ## \1
▮▮▮▮⚝ ## 替换为 ## 和一个空格,表示二级标题。
▮▮▮▮⚝ \1 引用查找模式中第一个捕获组的内容,即原始一级标题的文本。

操作步骤 (在支持正则表达式的文本编辑器中)
① 打开查找与替换对话框。
② 在查找框中输入正则表达式 ^# (.*)
③ 在替换框中输入 ## \1
④ 启用正则表达式模式。
⑤ 执行替换操作 (全部替换或逐个替换)。

示例 Markdown 文本

1.双击鼠标左键复制此行;2.单击复制所有代码。
1# 一级标题 1
2这是一段普通的文本。
3# 一级标题 2
4这是另一段文本。

替换后的 Markdown 文本

1.双击鼠标左键复制此行;2.单击复制所有代码。
1## 一级标题 1
2这是一段普通的文本。
3## 一级标题 2
4这是另一段文本。

通过这个案例,我们展示了如何使用正则表达式在文本编辑器中进行强大的查找与替换操作,批量修改文本格式,提高编辑效率。

7.3.2 代码重构 (Code Refactoring)

在代码重构过程中,经常需要批量修改代码结构、变量名、函数调用等。正则表达式可以帮助开发者快速定位和修改代码,实现高效的代码重构。

案例描述
假设我们需要将 C++ 代码中的所有 std::vector<int> 类型的变量声明,更改为使用 folly::Vector<int>

查找模式 (正则表达式)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1std::vector<int> (\w+)

替换模式

1.双击鼠标左键复制此行;2.单击复制所有代码。
1folly::Vector<int> \1

解释
① 查找模式 std::vector<int> (\w+)
▮▮▮▮⚝ std::vector<int> 匹配字面字符串 std::vector<int>
▮▮▮▮⚝ (\w+) 匹配一个或多个单词字符 (字母、数字、下划线),并将其捕获到第一个分组 \1 中,通常是变量名。
② 替换模式 folly::Vector<int> \1
▮▮▮▮⚝ folly::Vector<int> 替换为 folly::Vector<int>
▮▮▮▮⚝ \1 引用查找模式中第一个捕获组的内容,即变量名,保持变量名不变。

操作步骤 (在支持正则表达式的文本编辑器或 IDE 中)
① 打开查找与替换对话框 (通常在 IDE 中有更强大的重构工具,但底层原理类似)。
② 在查找框中输入正则表达式 std::vector<int> (\w+)
③ 在替换框中输入 folly::Vector<int> \1
④ 启用正则表达式模式。
⑤ 执行替换操作 (通常在 IDE 中可以预览和确认修改)。

示例 C++ 代码 (重构前)

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <vector>
2int main() {
3 std::vector<int> numbers;
4 std::vector<int> indices;
5 // ...
6 return 0;
7}

重构后的 C++ 代码

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <vector>
2#include <folly/Vector.h>
3int main() {
4 folly::Vector<int> numbers;
5 folly::Vector<int> indices;
6 // ...
7 return 0;
8}

更复杂的代码重构场景
正则表达式还可以用于更复杂的代码重构场景,例如:
函数重命名:查找旧函数名,替换为新函数名,同时可能需要处理函数调用处的参数和作用域。
代码结构调整:例如将一段代码块移动到新的函数或类中,可以使用正则表达式辅助定位和提取代码块。
API 迁移:当 API 发生变化时,可以使用正则表达式批量更新代码中的 API 调用。

在代码重构中,正则表达式通常与 IDE 的代码分析和重构工具结合使用,可以实现更安全、更智能的代码修改。folly/Regex.h 库的强大功能,也可以在构建自定义的代码重构工具中发挥重要作用。

通过本章的实战案例分析,我们深入了解了 folly/Regex.h 库在日志分析、网络数据包解析和文本编辑器应用等领域的强大功能和应用价值。正则表达式作为一种强大的文本处理工具,结合 folly/Regex.h 库的高性能和易用性,可以极大地提升我们在各种实际工作场景中的效率和能力。

END_OF_CHAPTER

8. chapter 8: 性能优化与最佳实践 (Performance Optimization and Best Practices)

8.1 正则表达式性能考量 (Regular Expression Performance Considerations)

8.1.1 回溯与性能陷阱 (Backtracking and Performance Pitfalls)

正则表达式的强大之处在于其灵活性和强大的模式匹配能力,但这种能力背后隐藏着性能陷阱,尤其是在处理复杂模式和大型文本时。回溯(Backtracking) 是正则表达式引擎实现匹配的核心机制之一,但如果理解不足或使用不当,就可能导致性能急剧下降,甚至出现所谓的 灾难性回溯(Catastrophic Backtracking)

什么是回溯 (What is Backtracking)

回溯是正则表达式引擎在尝试匹配输入文本时,当一个匹配分支失败后,会退回到之前的状态,尝试其他的匹配路径的过程。可以将其形象地比喻为在一个迷宫中探索路径,当走到死胡同时,需要原路返回,尝试其他岔路。

例如,考虑正则表达式 a*b 和输入字符串 aaac

  1. 引擎首先尝试用 a* 匹配尽可能多的 a,直到字符串末尾,即匹配了 aaa。此时,正则表达式剩余部分是 b,而字符串剩余部分是 c,匹配失败。
  2. 发生回溯,引擎释放最后一个 a 的匹配,a* 现在只匹配 aa。正则表达式剩余部分仍然是 b,字符串剩余部分变为 ac,仍然不匹配。
  3. 再次回溯,a* 现在只匹配 a。正则表达式剩余部分是 b,字符串剩余部分变为 aac,还是不匹配。
  4. 最后回溯,a* 匹配空字符串。正则表达式剩余部分是 b,字符串剩余部分变为 aaac,仍然不匹配。
  5. 由于 a* 可以匹配零个 a,引擎会继续尝试匹配 b,但输入字符串的开头是 a,匹配再次失败。
  6. 整个匹配过程失败。

在这个简单的例子中,回溯是线性的,性能影响不大。但当正则表达式变得复杂,特别是包含嵌套的量词和选择分支时,回溯的路径数量可能呈指数级增长,导致性能急剧下降。

灾难性回溯 (Catastrophic Backtracking)

灾难性回溯发生在当正则表达式引擎陷入大量的回溯尝试中,导致匹配时间急剧增加,甚至程序失去响应。这种情况通常由以下模式引起:

嵌套量词 (Nested Quantifiers):例如 (a+)+(a*)* 这样的模式,内部和外部量词都允许重复,容易产生大量的回溯路径。
选择分支与量词的结合 (Combination of Alternation and Quantifiers):例如 (a|b)+c 匹配 aaaa...aaaa 这样的字符串,a|b 的每个分支都可能被量词重复多次,导致回溯。
重叠的匹配模式 (Overlapping Matching Patterns):模式的不同部分可以匹配相同的内容,导致引擎在不同匹配路径之间反复尝试。

考虑一个经典的灾难性回溯的例子:正则表达式 (a+)+b 和输入字符串 aaaaaaaaaaaaaaaaaaaaac (21个 'a' 和一个 'c')。

这个正则表达式的意图是匹配一个或多个 'a' 组成的组,重复一次或多次,最后以 'b' 结尾。然而,当输入字符串以 'c' 结尾而不是 'b' 时,就会触发灾难性回溯。

(a+)+ 会尝试各种方式来分组和重复匹配 'a'。例如,对于输入 aaaaa,它可以被解析为 (a)(aaaa), (aa)(aaa), (aaa)(aa), (aaaa)(a), (a)(a)(aaa), ... 等等。
⚝ 当匹配到字符串末尾的 'c' 时,b 无法匹配,引擎开始回溯。
⚝ 回溯会尝试减少 (a+)+ 匹配的 'a' 的数量,并尝试其他的分组方式。对于每个减少的 'a',引擎都需要重新尝试所有可能的匹配路径。
⚝ 随着输入字符串中 'a' 的数量增加,回溯的路径数量呈指数级增长,导致匹配时间变得非常长。

可以用如下 Python 代码简单演示灾难性回溯:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1import re
2import time
3regex = r"(a+)+b"
4text = "a" * 21 + "c"
5start_time = time.time()
6match = re.match(regex, text)
7end_time = time.time()
8print(f"Match: {match}")
9print(f"Time taken: {end_time - start_time:.4f} seconds")

在某些正则表达式引擎中,运行这段代码可能会花费很长时间,甚至导致程序卡死。

避免回溯陷阱 (Avoiding Backtracking Pitfalls)

为了避免回溯陷阱,可以采取以下策略:

减少嵌套量词的使用 (Minimize Nested Quantifiers):尽量避免使用如 (a+)+(a*)* 这样的嵌套量词。如果可能,尝试使用更简单的模式或使用非回溯的正则表达式引擎(如果 folly/Regex.h 提供相关选项,后续章节会讨论)。
使用固化分组或占有优先量词 (Use Atomic Grouping or Possessive Quantifiers):某些正则表达式引擎(但并非所有,需要检查 folly/Regex.h 的支持情况)提供固化分组 (?>...) 或占有优先量词 *+++?+{n,m}+。这些结构会阻止引擎在匹配失败时回溯,从而提高性能。例如,将 (a+)+b 改写为 (?>a+)+ba*+b (如果语义允许) 可以避免灾难性回溯。
明确模式的边界 (Define Clear Boundaries for Patterns):确保正则表达式的各个部分匹配的文本范围是明确的,避免模式之间产生不必要的重叠和歧义。
具体化字符类 (Be Specific with Character Classes):使用更具体的字符类,而不是过于宽泛的 .。例如,如果只想匹配字母,使用 [a-zA-Z] 而不是 .
限制量词的作用范围 (Limit the Scope of Quantifiers):尽量限制量词的作用范围,避免让量词匹配过多的文本。例如,使用 a{1,5}? 这样的惰性量词,或者使用环视断言来约束匹配的上下文。
测试和性能分析 (Testing and Performance Analysis):在实际应用中,使用各种输入数据(包括正常情况和边界情况)测试正则表达式的性能。可以使用性能分析工具来定位性能瓶颈,并根据分析结果优化正则表达式。

理解回溯机制和潜在的性能陷阱是编写高效正则表达式的关键。在设计正则表达式时,应尽量保持模式的简洁和明确,避免使用可能导致大量回溯的复杂结构。

8.1.2 避免低效的正则表达式 (Avoiding Inefficient Regular Expressions)

除了回溯问题,还有一些常见的正则表达式模式和用法也可能导致效率低下。了解这些低效模式并学会避免它们,可以显著提升正则表达式的性能。

过度使用 . 字符 (Overuse of . Character)

. 字符可以匹配任意字符(除了换行符,除非使用了单行模式),这使得它非常灵活,但也容易被滥用。过度使用 . 会导致正则表达式匹配的范围过大,增加不必要的匹配尝试和回溯。

例如,假设要从一段文本中提取日期,日期格式为 YYYY-MM-DD。如果使用正则表达式 .*-.*-.*,虽然可以匹配到日期,但效率很低,并且可能匹配到错误的结果。例如,对于输入 invoice-2023-10-26-report.pdf,这个正则表达式会匹配到 invoice-2023-10-26-report,而不是预期的 2023-10-26

更高效和准确的做法是使用更具体的模式,例如 \d{4}-\d{2}-\d{2}。这个模式明确指定了日期的格式,只匹配数字和连字符,避免了不必要的匹配和回溯,同时也提高了匹配的准确性。

在循环中使用未编译的正则表达式 (Using Uncompiled Regular Expressions in Loops)

在循环中重复使用同一个正则表达式时,如果每次循环都重新编译正则表达式,会造成很大的性能开销。正则表达式的编译是一个相对耗时的过程,涉及到语法分析、优化等步骤。

例如,以下代码在循环中多次使用正则表达式匹配:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <iostream>
2#include <string>
3#include <regex>
4#include <chrono>
5int main() {
6 std::string text = "This is a test string with numbers 123, 456, and 789.";
7 std::string pattern = "\\d+";
8 auto start_time = std::chrono::high_resolution_clock::now();
9 for (int i = 0; i < 10000; ++i) {
10 std::regex re(pattern); // 每次循环都重新编译
11 std::smatch match;
12 std::regex_search(text, match, re);
13 }
14 auto end_time = std::chrono::high_resolution_clock::now();
15 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
16 std::cout << "Time taken: " << duration.count() << " milliseconds" << std::endl;
17 return 0;
18}

这段代码每次循环都创建新的 std::regex 对象,导致正则表达式被重复编译。为了提高性能,应该在循环外部编译正则表达式,然后在循环内部重复使用编译后的对象。

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <iostream>
2#include <string>
3#include <regex>
4#include <chrono>
5int main() {
6 std::string text = "This is a test string with numbers 123, 456, and 789.";
7 std::string pattern = "\\d+";
8 std::regex re(pattern); // 在循环外部编译
9 auto start_time = std::chrono::high_resolution_clock::now();
10 for (int i = 0; i < 10000; ++i) {
11 std::smatch match;
12 std::regex_search(text, match, re); // 重复使用编译后的对象
13 }
14 auto end_time = std::chrono::high_resolution_clock::now();
15 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
16 std::cout << "Time taken: " << duration.count() << " milliseconds" << std::endl;
17 return 0;
18}

将正则表达式的编译移到循环外部后,性能会得到显著提升。在 folly/Regex.h 中,folly::Regex 对象的创建也涉及到编译过程,因此在循环中重复使用 folly::Regex 对象同样可以提高性能。

不必要的捕获分组 (Unnecessary Capturing Groups)

捕获分组 () 用于提取匹配的子字符串。如果正则表达式中包含大量的捕获分组,但实际上并不需要使用这些捕获的子字符串,那么捕获分组就会成为性能负担。正则表达式引擎需要额外的资源来记录和存储捕获的子字符串。

如果只是需要判断是否匹配,或者只需要整个匹配结果,而不需要子匹配,应该使用非捕获分组 (?:...)。非捕获分组与捕获分组的功能类似,但不会捕获匹配的子字符串,从而提高性能。

例如,如果只想验证字符串是否符合某种模式,可以使用非捕获分组:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1import re
2regex_capture = r"^((\d{4})-(\d{2})-(\d{2}))$" # 捕获分组
3regex_non_capture = r"^(?:\d{4}-\d{2}-\d{2})$" # 非捕获分组
4text = "2023-10-26"
5match_capture = re.match(regex_capture, text)
6match_non_capture = re.match(regex_non_capture, text)
7print(f"Capture match: {match_capture}") # 可以访问捕获的子组
8print(f"Non-capture match: {match_non_capture}") # 只能判断是否匹配

在性能敏感的场景中,如果不需要子匹配结果,应尽量使用非捕获分组。

复杂的选择分支 (Complex Alternation)

选择分支 | 提供了强大的多模式匹配能力,但过多的选择分支或复杂的选择分支也可能影响性能。正则表达式引擎在处理选择分支时,需要尝试每个分支进行匹配,如果分支数量过多或分支模式复杂,会增加匹配的时间。

例如,正则表达式 (a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z)+ 匹配一个或多个小写字母。虽然这个正则表达式功能正确,但效率不高。更高效的做法是使用字符类 [a-z]+,字符类在引擎内部通常会进行优化,匹配效率更高。

在设计正则表达式时,应尽量使用字符类、范围、预定义字符类等更简洁高效的结构,而不是过度依赖选择分支。

前缀不确定锚点 (Unanchored Patterns with Leading Wildcards)

当正则表达式以通配符 ..* 开头,并且没有使用 ^ 锚定到字符串开头时,引擎需要从字符串的每个位置开始尝试匹配,这会显著降低性能。

例如,正则表达式 .*abc 匹配包含 abc 的字符串。对于输入字符串 ...xyz...abc...,引擎会首先从字符串的开头尝试匹配 .*abc,失败后会从第二个字符开始尝试,依此类推,直到在某个位置找到 abc。这种从每个位置开始尝试匹配的方式称为 非锚定匹配(Unanchored Matching)

如果明确知道要匹配的模式出现在字符串的开头,应该使用 ^ 锚定模式到字符串开头,例如 ^abc。如果模式出现在字符串的结尾,可以使用 $ 锚定到结尾,例如 abc$。锚定模式可以显著减少引擎的匹配尝试次数,提高性能。

如果必须使用非锚定匹配,应尽量使模式的前缀部分更具体,减少通配符的使用。例如,将 .*abc 改写为 [a-zA-Z0-9_]*abc,如果预期 abc 前面是字母、数字或下划线,这样可以缩小匹配范围,提高效率。

总结 (Summary)

避免低效正则表达式的关键在于:

具体化模式 (Be Specific):使用更具体的字符类、范围、锚点,避免过度使用通配符 .
减少回溯 (Reduce Backtracking):避免嵌套量词、复杂的选择分支,使用非捕获分组、固化分组等技巧。
预编译正则表达式 (Pre-compile Regular Expressions):在循环或重复使用正则表达式的场景中,预先编译正则表达式对象。
选择合适的工具 (Choose the Right Tool):在某些简单的字符串查找场景中,可能使用 std::string::find 等字符串查找函数比正则表达式更高效。正则表达式虽然强大,但并非所有字符串处理任务的最佳选择。

通过理解这些低效模式和优化策略,可以编写出更高效、更健壮的正则表达式,提升程序的整体性能。

8.2 folly/Regex.h 的性能优化技巧 (Performance Optimization Techniques for folly/Regex.h)

8.2.1 预编译正则表达式 (Pre-compiling Regular Expressions)

正如 8.1.2 节中讨论的,正则表达式的编译是一个相对耗时的过程。folly/Regex.h 库中的 folly::Regex 对象在构造时会进行正则表达式的编译。为了避免重复编译的开销,预编译(Pre-compiling) 正则表达式是一种重要的性能优化技巧。

folly::Regex 的编译与重用 (Compilation and Reuse of folly::Regex)

folly::Regex 类的构造函数接受正则表达式字符串作为参数,并在构造对象时编译该正则表达式。编译后的正则表达式会存储在 folly::Regex 对象内部,供后续的匹配操作使用。

因此,如果需要在程序中多次使用同一个正则表达式,应该只编译一次,并将编译后的 folly::Regex 对象保存下来,在需要时重复使用。

例如,假设需要在循环中多次使用正则表达式 \d+ 查找数字:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/regex.h>
2#include <iostream>
3#include <string>
4#include <chrono>
5int main() {
6 std::string text = "This is a test string with numbers 123, 456, and 789.";
7 std::string pattern = "\\d+";
8 auto start_time = std::chrono::high_resolution_clock::now();
9 for (int i = 0; i < 10000; ++i) {
10 folly::Regex re(pattern); // 每次循环都重新编译
11 folly::smatch match;
12 folly::regex_search(text, re, match);
13 }
14 auto end_time = std::chrono::high_resolution_clock::now();
15 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
16 std::cout << "Time taken (without pre-compilation): " << duration.count() << " milliseconds" << std::endl;
17 start_time = std::chrono::high_resolution_clock::now();
18 folly::Regex precompiled_re(pattern); // 预编译一次
19 for (int i = 0; i < 10000; ++i) {
20 folly::smatch match;
21 folly::regex_search(text, precompiled_re, match); // 重复使用预编译对象
22 }
23 end_time = std::chrono::high_resolution_clock::now();
24 duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
25 std::cout << "Time taken (with pre-compilation): " << duration.count() << " milliseconds" << std::endl;
26 return 0;
27}

在这个例子中,第一个循环每次都创建新的 folly::Regex 对象,导致正则表达式被重复编译。第二个循环在循环外部创建 folly::Regex 对象 precompiled_re,并在循环内部重复使用这个预编译的对象。运行结果会显示,使用预编译的版本性能明显提升。

静态 folly::Regex 对象 (Static folly::Regex Objects)

对于全局使用的正则表达式,可以将其声明为静态 folly::Regex 对象。静态对象在程序启动时只初始化一次,确保正则表达式只被编译一次,并在程序的整个生命周期内都可以重复使用。

例如,假设需要一个全局使用的邮箱地址验证正则表达式:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/regex.h>
2#include <string>
3#include <iostream>
4// 静态全局正则表达式对象
5static const folly::Regex emailRegex(R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})");
6bool isValidEmail(const std::string& email) {
7 return folly::regex_match(email, emailRegex);
8}
9int main() {
10 std::cout << "Is 'test@example.com' a valid email? " << (isValidEmail("test@example.com") ? "Yes" : "No") << std::endl;
11 std::cout << "Is 'invalid-email' a valid email? " << (isValidEmail("invalid-email") ? "Yes" : "No") << std::endl;
12 return 0;
13}

在这个例子中,emailRegex 被声明为静态常量 folly::Regex 对象,它在程序启动时被初始化和编译一次。isValidEmail 函数可以直接使用这个预编译的正则表达式对象进行匹配,避免了重复编译的开销。

线程安全 (Thread Safety)

folly::Regex 对象是线程安全的,多个线程可以同时使用同一个 folly::Regex 对象进行匹配操作,而无需额外的同步措施。这意味着可以放心地在多线程环境中使用预编译的静态 folly::Regex 对象,进一步提高程序的并发性能。

总结 (Summary)

预编译正则表达式是 folly/Regex.h 性能优化的基本技巧。通过在循环外部、静态全局变量或类成员变量中创建和保存 folly::Regex 对象,可以避免重复编译的开销,显著提升正则表达式的匹配性能。尤其是在高频使用的正则表达式场景中,预编译带来的性能提升非常明显。

8.2.2 选择合适的匹配算法 (Choosing the Right Matching Algorithm) (如果 folly/Regex.h 提供选项)

不同的正则表达式引擎和库可能提供不同的匹配算法选项,这些算法在性能和特性上有所差异。了解 folly/Regex.h 提供的匹配算法选项(如果有),并根据实际应用场景选择合适的算法,可以进一步优化性能。

常见的正则表达式匹配算法 (Common Regular Expression Matching Algorithms)

回溯算法 (Backtracking Algorithm):这是最常见的正则表达式匹配算法,例如 Perl、Python 的 re 模块、Java 的 java.util.regex 等都使用回溯算法。回溯算法的优点是支持丰富的正则表达式语法特性,包括反向引用、环视断言等。缺点是容易出现回溯陷阱,性能不稳定,最坏情况下时间复杂度可能达到指数级别。
Thompson NFA 算法 (Thompson Nondeterministic Finite Automaton Algorithm):也称为 NFA 算法。Thompson NFA 算法将正则表达式编译成 NFA,然后模拟 NFA 的运行进行匹配。NFA 算法的优点是匹配速度快且稳定,时间复杂度与输入文本长度和正则表达式长度成线性关系,不会出现回溯陷阱。缺点是通常不支持反向引用等一些高级特性。
POSIX NFA/DFA 算法 (POSIX Nondeterministic/Deterministic Finite Automaton Algorithm):POSIX 标准定义了两种正则表达式引擎:基本正则表达式 (BRE) 和扩展正则表达式 (ERE)。POSIX 引擎通常使用 NFA 或 DFA 算法实现。DFA (Deterministic Finite Automaton) 算法将正则表达式编译成 DFA,DFA 的匹配速度非常快,时间复杂度也是线性的,但编译时间较长,且 DFA 的状态空间可能很大,占用较多内存。POSIX NFA 算法在特性和性能上介于回溯算法和 Thompson NFA 算法之间。

folly/Regex.h 的算法选项 (Algorithm Options in folly/Regex.h)

需要查阅 folly/Regex.h 的官方文档或源代码,确认 folly/Regex.h 是否提供了选择匹配算法的选项。一些高性能的正则表达式库可能会提供多种算法选项,例如:

指定使用 NFA 算法:如果 folly/Regex.h 提供了 NFA 算法选项,并且应用场景不需要反向引用等回溯算法特有的高级特性,可以考虑选择 NFA 算法。NFA 算法通常具有更好的性能和稳定性,尤其是在处理大型文本和复杂模式时。
指定使用 DFA 算法:如果 folly/Regex.h 提供了 DFA 算法选项,并且对匹配速度有极高的要求,可以考虑选择 DFA 算法。DFA 算法的匹配速度非常快,但可能会增加编译时间和内存占用。
选择不同的回溯优化策略:即使 folly/Regex.h 主要使用回溯算法,也可能提供一些选项来调整回溯算法的优化策略,例如限制回溯深度、启用或禁用某些优化规则等。这些选项可以在一定程度上缓解回溯陷阱问题,提高回溯算法的性能。

如何选择算法 (How to Choose Algorithm)

选择合适的匹配算法需要根据具体的应用场景和需求进行权衡:

性能要求:如果对匹配速度有极高的要求,并且正则表达式的语法特性允许,可以优先考虑 DFA 算法或 NFA 算法。如果性能要求不是最关键的,回溯算法通常也能满足需求。
特性需求:如果正则表达式需要使用反向引用、环视断言等回溯算法特有的高级特性,则只能选择回溯算法。NFA 和 DFA 算法通常不支持这些高级特性。
正则表达式的复杂度:对于简单的正则表达式,不同算法之间的性能差异可能不明显。但对于复杂的正则表达式,回溯算法可能更容易出现性能问题,而 NFA 和 DFA 算法的性能更稳定。
测试和基准测试:在实际应用中,应该使用不同的算法选项(如果 folly/Regex.h 提供)进行测试和基准测试,比较不同算法在实际数据上的性能表现,选择最适合的算法。

查看 folly/Regex.h 文档 (Check folly/Regex.h Documentation)

要了解 folly/Regex.h 是否提供匹配算法选项,以及如何配置这些选项,最可靠的方法是查阅 folly/Regex.h 的官方文档或源代码。文档通常会详细说明库的特性、API 和性能优化建议。

如果在 folly/Regex.h 的文档中没有找到关于匹配算法选项的信息,则可能意味着 folly/Regex.h 默认只使用一种匹配算法,或者其算法选择是自动进行的,不需要用户手动配置。在这种情况下,性能优化的重点应该放在正则表达式模式的设计和预编译等其他方面。

8.3 最佳实践总结 (Summary of Best Practices)

为了在使用 folly/Regex.h 时获得最佳的性能和可靠性,以下是一些最佳实践的总结:

预编译正则表达式 (Pre-compile Regular Expressions)

⚝ 对于需要重复使用的正则表达式,务必进行预编译。将 folly::Regex 对象创建在循环外部、静态全局变量或类成员变量中,避免重复编译的开销。
⚝ 静态 folly::Regex 对象是全局共享正则表达式的有效方式,尤其适用于常用的、固定的正则表达式模式。

设计高效的正则表达式模式 (Design Efficient Regular Expression Patterns)

具体化模式:使用具体的字符类、范围、锚点,避免过度使用通配符 .。例如,使用 \d 代替 [0-9],使用 [a-z] 代替 [abcdefghijklmnopqrstuvwxyz]
减少回溯:避免嵌套量词(如 (a+)+),复杂的选择分支,尽量使用非捕获分组 (?:...)
锚定模式:如果可能,使用 ^$ 锚定模式到字符串的开头和结尾,减少引擎的匹配尝试次数。
限制量词的作用范围:使用惰性量词 ???*?+? 或明确指定量词的最大次数 {n,m},避免量词匹配过多的文本。
使用字符类代替选择分支:例如,使用 [abc] 代替 (a|b|c),字符类通常更高效。

选择合适的匹配函数 (Choose the Right Matching Function)

⚝ 根据实际需求选择合适的匹配函数。如果只需要判断是否匹配,使用 folly::Regex::isMatch()folly::Regex::find()folly::Regex::findAll() 更高效。
⚝ 如果只需要查找第一个匹配项,使用 folly::Regex::find(),避免使用 folly::Regex::findAll() 获取所有匹配项。
⚝ 使用迭代器 folly::Regex::findEach() 处理大型文本,可以避免一次性加载所有匹配结果到内存中,节省内存并提高处理效率。

错误处理 (Error Handling)

⚝ 使用 try-catch 块捕获 folly::RegexException 异常,处理正则表达式编译或匹配过程中可能出现的错误。
⚝ 在开发阶段,可以开启更严格的正则表达式语法检查选项(如果 folly/RegexOptions 提供),尽早发现和修复正则表达式模式中的错误。

性能测试和分析 (Performance Testing and Analysis)

⚝ 在实际应用场景中,对正则表达式的性能进行测试和基准测试,评估正则表达式的性能是否满足需求。
⚝ 使用性能分析工具(如 profiler)定位正则表达式的性能瓶颈,根据分析结果优化正则表达式模式或代码。
⚝ 针对不同的输入数据(包括正常情况、边界情况、异常情况),进行充分的测试,确保正则表达式的健壮性和可靠性。

考虑其他字符串处理方法 (Consider Alternative String Processing Methods)

⚝ 对于简单的字符串查找、替换等操作,可以考虑使用 std::string 提供的成员函数(如 findreplace 等),这些函数通常比正则表达式更高效。
⚝ 正则表达式虽然强大,但并非所有字符串处理任务的最佳选择。在选择使用正则表达式之前,评估是否可以使用更简单、更高效的方法。

持续学习和实践 (Continuous Learning and Practice)

⚝ 深入理解正则表达式的语法、特性和引擎的工作原理,不断学习和掌握更高级的正则表达式技巧。
⚝ 通过实践编写和优化正则表达式,积累经验,提高正则表达式的应用水平。
⚝ 关注 folly/Regex.h 库的更新和发展,了解最新的特性和性能优化技巧。

遵循这些最佳实践,可以帮助开发者充分利用 folly/Regex.h 库的强大功能,编写出高效、可靠的正则表达式应用,提升程序的整体性能和用户体验。

END_OF_CHAPTER

9. chapter 9: folly/Regex.h 与其他 Folly 库的集成 (Integration of folly/Regex.h with Other Folly Libraries)

9.1 与 folly::StringPiece 的配合使用 (Using with folly::StringPiece)

folly::StringPiece 是 Folly 库中一个非常重要的组件,它被设计用来高效地处理字符串,尤其是在避免不必要的字符串拷贝方面表现出色。StringPiece 本质上是一个轻量级的字符串视图(string view),它仅仅包含一个指向字符数据的指针和一个长度,而不拥有底层的字符数据。这意味着使用 StringPiece 传递和操作字符串时,不会发生数据的复制,从而显著提高性能,尤其是在处理大型字符串或频繁进行字符串操作的场景下。

在正则表达式处理中,我们经常需要对字符串进行匹配、查找等操作。如果每次操作都涉及字符串的复制,那么性能损耗将非常可观。folly/Regex.h 库的设计充分考虑了性能,因此能够与 folly::StringPiece 无缝集成,直接对 StringPiece 对象进行正则表达式操作,避免了额外的字符串拷贝开销。

优势 (Advantages):

零拷贝 (Zero-copy): folly::Regex 可以直接接受 folly::StringPiece 作为输入,无需将 StringPiece 转换为 std::string 或其他字符串类型,从而避免了数据拷贝,提高了效率。
高效处理大型字符串 (Efficiently handling large strings): 对于大型文本的处理,使用 StringPiece 可以显著减少内存占用和 CPU 时间,因为不需要为中间字符串创建副本。
与 Folly 库的良好集成 (Good integration with Folly library): 在 Folly 生态系统中,StringPiece 被广泛使用,folly/Regex.hStringPiece 的支持使得正则表达式功能可以更好地融入到现有的 Folly 代码库中。

使用方法 (Usage):

folly::Regex 的接口设计非常灵活,可以接受多种类型的字符串输入,包括 std::string, C-style 字符串,以及 folly::StringPiece。 当传入 folly::StringPiece 对象时,folly::Regex 会直接操作 StringPiece 指向的字符串数据,而不会进行任何拷贝。

以下代码示例展示了如何使用 folly::Regexfolly::StringPiece 进行正则表达式匹配和查找操作:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/FBString.h>
2#include <folly/Regex.h>
3#include <folly/StringPiece.h>
4#include <iostream>
5int main() {
6 folly::FBString fbStr = "This is a test string with some numbers 123 and 456.";
7 folly::StringPiece sp = fbStr; // StringPiece 引用 fbStr 的数据,不拷贝
8 folly::Regex regex("\\d+"); // 匹配一个或多个数字
9 // 使用 isMatch() 判断是否匹配
10 if (regex.isMatch(sp)) {
11 std::cout << "StringPiece matches the regex." << std::endl;
12 } else {
13 std::cout << "StringPiece does not match the regex." << std::endl;
14 }
15 // 使用 find() 查找第一个匹配项
16 auto match = regex.find(sp);
17 if (match.hasValue()) {
18 std::cout << "First match: " << match->str() << ", at position: " << match->begin() << std::endl;
19 }
20 // 使用 findAll() 查找所有匹配项
21 auto allMatches = regex.findAll(sp);
22 std::cout << "All matches:" << std::endl;
23 for (const auto& m : allMatches) {
24 std::cout << "- " << m.str() << ", at position: " << m.begin() << std::endl;
25 }
26 // 使用 findEach() 迭代匹配结果
27 std::cout << "Iterating matches:" << std::endl;
28 regex.findEach(sp, [](const folly::RegexMatch& m) {
29 std::cout << "- " << m.str() << ", at position: " << m.begin() << std::endl;
30 });
31 return 0;
32}

代码解析 (Code Explanation):

folly::FBString fbStr = ...;: 首先,我们创建了一个 folly::FBString 对象 fbStr,用于存储字符串数据。FBString 是 Folly 提供的字符串类,具有高效的内存管理和 COW (Copy-On-Write) 优化。
folly::StringPiece sp = fbStr;: 然后,我们创建了一个 folly::StringPiece 对象 sp,并将 fbStr 赋值给它。 关键在于,这里并没有发生字符串的拷贝sp 只是指向了 fbStr 内部的字符数据。
folly::Regex regex("\\d+");: 创建一个 folly::Regex 对象,用于匹配一个或多个数字 (\d+)。
regex.isMatch(sp), regex.find(sp), regex.findAll(sp), regex.findEach(sp): 这些 folly::Regex 的成员函数都直接接受 StringPiece 对象 sp 作为输入。 在这些操作过程中,folly::Regex 库直接操作 sp 指向的原始字符串数据,避免了任何不必要的拷贝。

总结 (Summary):

通过与 folly::StringPiece 的配合使用,folly/Regex.h 能够在处理字符串时实现零拷贝,显著提升性能,尤其是在处理大型文本和需要频繁进行正则表达式操作的场景下。这使得 folly/Regex.h 成为构建高性能 Folly 应用的理想选择。

9.2 与 folly::FBString 的配合使用 (Using with folly::FBString)

folly::FBString 是 Folly 库提供的另一个重要的字符串类,它在 std::string 的基础上进行了优化,旨在提供更高的性能和更低的内存开销。FBString 具有以下关键特性:

小字符串优化 (Small String Optimization, SSO): 对于较短的字符串,FBString 会直接在对象内部的缓冲区存储字符串数据,避免了动态内存分配的开销。这对于常见的短字符串操作非常高效。
Copy-On-Write (COW): FBString 采用了写时复制技术。当多个 FBString 对象共享同一份字符串数据时,只有在其中一个对象尝试修改字符串内容时,才会发生数据复制。这在字符串复制频繁但修改较少的场景下可以节省大量内存和拷贝开销。
与 Folly 库的集成 (Integration with Folly library): FBString 与 Folly 库的其他组件(包括 folly::Regex)紧密集成,能够更好地协同工作。

folly/Regex.h 可以很好地与 folly::FBString 协同工作。你可以直接使用 FBString 对象作为 folly::Regex 的输入,进行各种正则表达式操作。

使用方法 (Usage):

由于 FBString 本身就是一种字符串类型,因此它可以像 std::string 一样直接用于 folly::Regex 的各种接口。 folly::Regex 能够识别并高效处理 FBString 对象。

以下代码示例展示了如何使用 folly::Regexfolly::FBString 进行正则表达式操作:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/FBString.h>
2#include <folly/Regex.h>
3#include <iostream>
4int main() {
5 folly::FBString fbStr = "Contact us at support@example.com or sales@example.com";
6 folly::Regex emailRegex("([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})"); // 邮箱地址正则表达式
7 // 使用 isMatch() 判断是否匹配
8 if (emailRegex.isMatch(fbStr)) {
9 std::cout << "FBString contains email addresses." << std::endl;
10 }
11 // 使用 findAll() 查找所有匹配的邮箱地址
12 auto emailMatches = emailRegex.findAll(fbStr);
13 std::cout << "Email addresses found:" << std::endl;
14 for (const auto& match : emailMatches) {
15 std::cout << "- " << match.str() << std::endl;
16 }
17 // 使用 replaceAll() 替换所有匹配项
18 folly::FBString replacedStr = emailRegex.replaceAll(fbStr, "[EMAIL_REMOVED]");
19 std::cout << "String after replacement: " << replacedStr.toStdString() << std::endl;
20 return 0;
21}

代码解析 (Code Explanation):

folly::FBString fbStr = ...;: 创建一个 folly::FBString 对象 fbStr,存储包含邮箱地址的字符串。
folly::Regex emailRegex(...): 创建一个 folly::Regex 对象 emailRegex,用于匹配邮箱地址。
emailRegex.isMatch(fbStr), emailRegex.findAll(fbStr), emailRegex.replaceAll(fbStr, ...): 这些 folly::Regex 的成员函数都直接接受 FBString 对象 fbStr 作为输入。 folly/Regex.h 能够直接处理 FBString,并利用 FBString 的高效特性。
emailRegex.replaceAll(fbStr, "[EMAIL_REMOVED]"): replaceAll() 函数返回一个新的 FBString 对象 replacedStr,其中所有匹配到的邮箱地址都被替换为 "[EMAIL_REMOVED]"。 由于 FBString 的 COW 特性,如果原始字符串 fbStr 在后续没有被修改,那么替换操作可能会部分或全部利用 COW 优化,减少不必要的拷贝。
replacedStr.toStdString(): 由于 std::cout 默认接受 std::string,所以我们使用 replacedStr.toStdString()FBString 转换为 std::string 进行输出。

总结 (Summary):

folly/Regex.hfolly::FBString 的结合使用,可以充分利用 FBString 的性能优势,例如小字符串优化和写时复制,从而在处理字符串和正则表达式操作时获得更高的效率和更低的资源消耗。 在 Folly 项目中,推荐使用 FBString 作为主要的字符串类型,并结合 folly/Regex.h 进行正则表达式处理,以构建高性能的应用程序。

9.3 在 Folly 异步编程中的应用 (Application in Folly Asynchronous Programming)

Folly 库提供了强大的异步编程框架,主要基于 folly::Futurefolly::Promise 等组件。 在异步编程环境中,正则表达式操作同样非常常见,例如在处理网络请求、日志分析、数据流处理等场景中,可能需要在异步任务中执行正则表达式匹配、查找或替换等操作。

folly/Regex.h 本身是线程安全的,这意味着你可以在多线程环境中使用 folly::Regex 对象,包括在 Folly 的异步任务中。 然而,需要注意的是,folly::RegexMatch 对象不是线程安全的。 如果需要在多个线程之间传递匹配结果,或者在异步任务的回调函数中访问匹配结果,需要进行适当的同步措施,或者避免在线程之间共享 RegexMatch 对象。 通常,最佳实践是在每个异步任务中独立地执行正则表达式操作,并处理该任务内部的匹配结果。

在异步任务中使用 folly::Regex (Using folly::Regex in Asynchronous Tasks):

在 Folly 异步编程中,你可以像在同步代码中一样使用 folly::Regex。 例如,你可以在一个 folly::Future 的回调函数中执行正则表达式操作,或者在一个异步任务中使用 folly::EventBasefolly::coro::co_await 来执行正则表达式操作。

以下代码示例展示了如何在 Folly 异步任务中使用 folly::Regexfolly::Future

1.双击鼠标左键复制此行;2.单击复制所有代码。
1#include <folly/FBString.h>
2#include <folly/Future.h>
3#include <folly/Promise.h>
4#include <folly/Regex.h>
5#include <folly/executors/IOThreadPoolExecutor.h>
6#include <iostream>
7using namespace folly;
8Future<std::vector<std::string>> asyncFindEmails(Executor& executor, StringPiece text) {
9 Promise<std::vector<std::string>> promise;
10 auto future = promise.getFuture();
11 executor.add([promise = std::move(promise), text]() mutable {
12 Regex emailRegex("([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})");
13 std::vector<std::string> emails;
14 for (const auto& match : emailRegex.findAll(text)) {
15 emails.push_back(match.str().toStdString());
16 }
17 promise.setValue(std::move(emails)); // 在异步任务中设置 Future 的结果
18 });
19 return future;
20}
21int main() {
22 IOThreadPoolExecutor executor(4); // 创建一个 IO 线程池执行器
23 FBString text = "Emails: user1@example.com, user2@test.net, invalid-email";
24 auto emailsFuture = asyncFindEmails(executor, text); // 异步查找邮箱地址
25 emailsFuture.thenValue([](const std::vector<std::string>& emails) {
26 std::cout << "Found emails in async task:" << std::endl;
27 for (const auto& email : emails) {
28 std::cout << "- " << email << std::endl;
29 }
30 }).get(); // 获取 Future 的结果并处理
31 executor.shutdown();
32 executor.join();
33 return 0;
34}

代码解析 (Code Explanation):

asyncFindEmails(Executor& executor, StringPiece text) 函数: 这是一个异步函数,接受一个 Executor 对象和一个 StringPiece 文本作为输入,返回一个 Future<std::vector<std::string>>,表示异步查找邮箱地址的结果。
Promise<std::vector<std::string>> promise;, auto future = promise.getFuture();: 创建一个 Promise 和对应的 FuturePromise 用于在异步任务中设置结果,Future 用于在主线程中获取结果。
executor.add([promise = std::move(promise), text]() mutable { ... });: 使用 Executor 将一个 lambda 函数提交到线程池中异步执行。 这个 lambda 函数就是实际的异步任务。
Regex emailRegex(...), emailRegex.findAll(text): 在异步任务中,创建一个 Regex 对象并使用 findAll() 查找所有匹配的邮箱地址。 注意,Regex 对象的创建和使用都在异步任务内部,是线程安全的
promise.setValue(std::move(emails));: 在异步任务完成时,使用 promise.setValue() 设置 Future 的结果,将找到的邮箱地址列表传递给 Future
emailsFuture.thenValue([](const std::vector<std::string>& emails) { ... }).get();: 在主线程中,使用 thenValue() 添加一个回调函数,当 emailsFuture 完成时,回调函数会被执行,输出找到的邮箱地址。 get() 函数会阻塞当前线程,直到 Future 完成并返回结果。
executor.shutdown();, executor.join();: 在程序结束前,需要关闭线程池执行器并等待所有任务完成。

线程安全注意事项 (Thread Safety Considerations):

folly::Regex 对象是线程安全的: 可以被多个线程同时访问和使用,只要正则表达式模式在编译后不被修改。
folly::RegexMatch 对象不是线程安全的: 不应该在多个线程之间共享 RegexMatch 对象。 每个线程应该独立地进行正则表达式匹配操作,并处理自己的匹配结果。
避免在多线程环境共享可变状态: 尽量避免在多个线程之间共享可变的状态,包括正则表达式相关的对象和数据。 如果必须共享,需要使用适当的同步机制(例如互斥锁、原子操作等)来保护共享状态的访问。

总结 (Summary):

folly/Regex.h 可以安全地在 Folly 异步编程环境中使用。 你可以在异步任务中创建和使用 folly::Regex 对象,执行正则表达式操作。 为了保证线程安全,需要注意避免在线程之间共享 folly::RegexMatch 对象,并在必要时采取适当的同步措施。 通过在异步任务中利用 folly/Regex.h,可以构建高效、响应迅速的异步应用程序,处理各种需要正则表达式操作的场景。

END_OF_CHAPTER

10. chapter 10: 深入 folly/Regex.h 源码 (Deep Dive into folly/Regex.h Source Code) (可选,针对高级读者)

10.1 folly/Regex.h 的内部实现机制 (Internal Implementation Mechanism of folly/Regex.h)

folly/Regex.h 库作为 Facebook Folly 库的一部分,旨在提供高效且易于使用的正则表达式功能。为了实现这一目标,folly/Regex.h 在设计和实现上采取了一系列关键策略。本节将深入探讨其内部实现机制,帮助高级读者理解其背后的原理。

底层引擎选择folly/Regex.h 的核心是其底层的正则表达式引擎。虽然具体实现细节可能随 Folly 库的版本更新而有所变化,但通常会选择高性能的正则表达式引擎作为基础。常见的选择包括:
▮▮▮▮ⓑ RE2:RE2 是 Google 开发的开源正则表达式引擎,以其线性时间复杂度的匹配性能而闻名,避免了传统回溯引擎可能出现的灾难性回溯问题。RE2 专注于安全性和性能,牺牲了一些 POSIX 正则表达式的全部功能,例如反向引用(backreferences)和环视断言(lookaround assertions)的部分功能。如果 folly/Regex.h 基于 RE2,那么它将继承 RE2 的这些特性,这意味着在处理大型文本或需要高并发的场景下,folly/Regex.h 能够提供更稳定和可预测的性能。
▮▮▮▮ⓒ 其他引擎:在某些情况下,folly/Regex.h 也可能基于其他正则表达式引擎,例如 PCRE (Perl Compatible Regular Expressions) 或 std::regex (C++ 标准库的正则表达式)。选择不同的引擎会直接影响 folly/Regex.h 的功能特性和性能表现。例如,如果使用 PCRE,则可能支持更丰富的正则表达式语法,包括完整的反向引用和环视断言,但同时也可能面临回溯的性能风险。而如果使用 std::regex,则可以更好地与 C++ 标准库集成,但性能可能不如专门优化的 RE2 或 PCRE。

编译与匹配流程:无论底层引擎如何,folly/Regex.h 的使用流程通常都包含两个主要阶段:编译(Compilation)和匹配(Matching)。
▮▮▮▮ⓑ 编译阶段:当使用正则表达式字符串创建 folly::Regex 对象时,会触发编译阶段。在这个阶段,folly/Regex.h 会将正则表达式字符串解析成引擎能够理解的内部表示形式,例如有限自动机(Finite Automaton)或抽象语法树(Abstract Syntax Tree)。编译阶段的目的是为了优化匹配性能,将正则表达式预处理成一种更高效的结构。编译的耗时与正则表达式的复杂度有关,但通常只需要执行一次,后续的匹配操作可以复用编译结果。
▮▮▮▮ⓒ 匹配阶段:当调用 folly::Regex 对象的匹配函数(如 isMatch(), find(), findAll() 等)时,会进入匹配阶段。在这个阶段,底层引擎会使用编译阶段生成的内部表示形式,在输入文本中搜索与正则表达式匹配的子串。匹配算法的效率直接影响整体性能。例如,如果底层引擎是 RE2,则会使用基于 Thompson 构造和 Glushkov 构造等算法实现的自动机匹配,保证线性时间复杂度。

内存管理folly/Regex.h 作为 Folly 库的一部分,通常会遵循 Folly 的内存管理策略,例如使用 folly::Arena 或其他内存池技术来优化内存分配和释放。这有助于减少内存碎片,提高内存使用效率,尤其是在需要频繁创建和销毁 folly::Regex 对象的场景下。

异常处理folly/Regex.h 使用 folly::RegexException 类来处理正则表达式相关的错误,例如语法错误、编译错误或运行时错误。通过抛出异常,folly/Regex.h 可以清晰地向用户报告错误信息,并提供结构化的错误处理机制。

Unicode 支持:现代正则表达式库通常需要支持 Unicode 字符集,以处理各种语言的文本。folly/Regex.h 可能会提供对 Unicode 的支持,包括 Unicode 字符属性、Unicode 模式匹配等功能。具体的 Unicode 支持程度取决于底层引擎的能力以及 folly/Regex.h 的实现细节。

线程安全:在多线程环境下,线程安全是非常重要的。folly/Regex.h 需要考虑线程安全问题,确保多个线程可以同时安全地使用同一个 folly::Regex 对象或进行匹配操作。通常,正则表达式对象本身应该是线程安全的,或者通过采用线程局部存储(Thread-Local Storage)等技术来保证线程安全。

理解 folly/Regex.h 的内部实现机制有助于高级用户更好地利用这个库,例如:
性能调优:了解底层引擎的特性可以帮助用户编写更高效的正则表达式,避免性能陷阱。例如,如果底层是 RE2,则应避免使用反向引用等可能导致回溯的功能,或者考虑使用其他更适合 RE2 优化的正则表达式写法。
错误排查:当遇到正则表达式相关的错误时,了解异常处理机制可以帮助用户更快地定位问题,并采取相应的解决措施。
功能扩展:对于有特殊需求的用户,理解内部实现可以为扩展 folly/Regex.h 的功能提供参考,例如自定义匹配算法或集成其他正则表达式引擎。

总而言之,folly/Regex.h 的内部实现是一个复杂而精巧的系统,它结合了高性能的底层引擎、优化的编译和匹配流程、高效的内存管理、完善的异常处理、Unicode 支持以及线程安全考虑。深入理解这些机制,可以帮助高级读者更好地掌握和应用 folly/Regex.h 库。

10.2 关键源码分析 (Analysis of Key Source Code)

由于 folly/Regex.h 是 Folly 库的一部分,其源码通常位于 Folly 库的 GitHub 仓库中。要进行深入的源码分析,首先需要获取 Folly 库的源码。可以通过以下步骤获取并浏览源码:

克隆 Folly 仓库

1.双击鼠标左键复制此行;2.单击复制所有代码。
1git clone https://github.com/facebook/folly.git
2cd folly

定位 Regex.h 文件
在 Folly 源码目录中,folly/Regex.h 文件通常位于 folly 子目录下,即 folly/folly/Regex.h。可以使用文件浏览器或命令行工具找到该文件。

源码阅读工具
为了更方便地阅读和分析源码,建议使用代码编辑器或 IDE,例如 VSCode, CLion 等。这些工具通常提供代码高亮、代码跳转、代码搜索等功能,可以大大提高源码阅读效率。

接下来,我们将分析 folly/Regex.h 中的一些关键源码片段,以深入了解其实现细节。由于 folly/Regex.h 的具体实现可能依赖于其所选择的底层正则表达式引擎,并且 Folly 库的代码库可能会随着时间推移而更新,以下分析是基于对 folly/Regex.h 常见实现方式的推测和解读,具体细节请参考您所使用的 Folly 版本对应的源码。

(1)folly::Regex 类的定义

folly::Regex 类是 folly/Regex.h 库的核心类,它封装了正则表达式的编译和匹配功能。以下是一个简化的 folly::Regex 类定义示例(实际源码可能更复杂):

1.双击鼠标左键复制此行;2.单击复制所有代码。
1namespace folly {
2class Regex {
3 public:
4 // 构造函数,接受正则表达式字符串和选项
5 explicit Regex(StringPiece pattern, RegexOptions options = RegexOptions());
6 // 析构函数
7 ~Regex();
8 // 匹配判断
9 bool isMatch(StringPiece text) const;
10 // 查找第一个匹配
11 Optional<RegexMatch> find(StringPiece text) const;
12 // 查找所有匹配
13 std::vector<RegexMatch> findAll(StringPiece text) const;
14 // 迭代查找匹配
15 void findEach(StringPiece text, FunctionRef<void(const RegexMatch&)> callback) const;
16 private:
17 // 底层正则表达式引擎的句柄或对象
18 void* engine_;
19 // 正则表达式选项
20 RegexOptions options_;
21 // ... 其他内部成员 ...
22};
23} // namespace folly

构造函数 Regex(StringPiece pattern, RegexOptions options = RegexOptions()): 构造函数接受正则表达式字符串 pattern 和选项 options 作为参数。在构造函数内部,会调用底层正则表达式引擎的编译接口,将 pattern 编译成引擎内部的表示形式,并将编译结果存储在 engine_ 成员变量中。RegexOptions 参数用于配置正则表达式的匹配模式,例如是否忽略大小写、是否支持 Unicode 等。
析构函数 ~Regex(): 析构函数负责释放 Regex 对象所占用的资源,包括底层正则表达式引擎的句柄或对象。
匹配函数 isMatch(), find(), findAll(), findEach(): 这些函数是 folly::Regex 类的核心功能接口,用于执行正则表达式匹配操作。它们都接受输入文本 text 作为参数,并返回匹配结果。这些函数内部会调用底层正则表达式引擎的匹配接口,使用之前编译好的正则表达式模式在 text 中进行搜索。

(2)folly::Regex::isMatch() 的实现

folly::Regex::isMatch() 函数用于判断输入文本是否与正则表达式完全匹配。以下是一个简化的 isMatch() 函数实现示例:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1bool Regex::isMatch(StringPiece text) const {
2 // 调用底层引擎的匹配函数
3 return engine_->match(text.data(), text.size(), /* ... 其他参数 ... */);
4}

isMatch() 函数的实现非常简洁,它直接调用底层正则表达式引擎的 match() 函数,并将输入文本 text 传递给引擎。底层引擎负责执行实际的匹配操作,并返回匹配结果(true 或 false)。

(3)folly::Regex::find()folly::Regex::findAll() 的实现

folly::Regex::find() 函数用于查找输入文本中第一个匹配正则表达式的子串,而 folly::Regex::findAll() 函数用于查找所有匹配的子串。这两个函数的实现会稍微复杂一些,因为它们需要处理匹配结果的位置和子匹配信息。以下是一个简化的 find() 函数实现示例:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1Optional<RegexMatch> Regex::find(StringPiece text) const {
2 // 调用底层引擎的查找函数,获取匹配结果
3 MatchResult result = engine_->find(text.data(), text.size(), /* ... 其他参数 ... */);
4 if (result.matched) {
5 // 如果找到匹配,则创建 RegexMatch 对象并返回
6 return RegexMatch(text, result.start, result.end, result.submatches);
7 } else {
8 // 如果没有找到匹配,则返回空 Optional
9 return None;
10 }
11}

find() 函数首先调用底层引擎的 find() 函数,获取匹配结果 resultresult 包含了匹配是否成功、匹配子串的起始位置和结束位置、以及子匹配信息等。
⚝ 如果 result.matched 为 true,表示找到了匹配,find() 函数会创建一个 RegexMatch 对象,并将匹配结果信息填充到 RegexMatch 对象中,然后返回包含 RegexMatch 对象的 Optional
⚝ 如果 result.matched 为 false,表示没有找到匹配,find() 函数返回空的 Optional

folly::Regex::findAll() 函数的实现与 find() 函数类似,但它会在输入文本中循环查找所有匹配的子串,并将所有匹配结果存储在一个 std::vector<RegexMatch> 中返回。

(4)folly::RegexOptions 类的定义

folly::RegexOptions 类用于配置正则表达式的匹配选项,例如忽略大小写、多行模式等。以下是一个简化的 folly::RegexOptions 类定义示例:

1.双击鼠标左键复制此行;2.单击复制所有代码。
1namespace folly {
2class RegexOptions {
3 public:
4 // 默认构造函数
5 RegexOptions();
6 // 设置忽略大小写选项
7 RegexOptions& setIgnoreCase(bool ignoreCase);
8 // 设置多行模式选项
9 RegexOptions& setMultiline(bool multiline);
10 // ... 其他选项设置函数 ...
11 private:
12 int flags_; // 使用一个整数来存储选项标志位
13 // ... 其他内部成员 ...
14};
15} // namespace folly

RegexOptions 类使用一个整数 flags_ 来存储选项标志位。每个选项对应一个标志位,例如忽略大小写选项对应 kIgnoreCase 标志位,多行模式选项对应 kMultiline 标志位。
setIgnoreCase(), setMultiline() 等函数用于设置相应的选项标志位。在创建 Regex 对象时,会将 RegexOptions 对象传递给构造函数,构造函数会将选项标志位传递给底层正则表达式引擎,从而配置引擎的匹配模式。

总结

通过对 folly::Regex.h 关键源码的分析,我们可以看到 folly/Regex.h 的实现主要分为以下几个层次:

接口层folly::Regex, folly::RegexMatch, folly::RegexOptions, folly::RegexException 等类构成了 folly/Regex.h 库的对外接口,提供了易于使用的正则表达式功能。
桥接层folly/Regex.h 内部需要桥接到底层正则表达式引擎。桥接层负责将 folly/Regex.h 的接口调用转换为底层引擎的接口调用,并处理引擎返回的结果。
引擎层:底层正则表达式引擎(例如 RE2, PCRE, std::regex)负责执行实际的正则表达式编译和匹配操作。

理解 folly/Regex.h 的源码结构和实现细节,可以帮助高级读者更好地掌握其使用方法,并进行更深入的定制和扩展。建议读者结合自己使用的 Folly 版本,深入阅读 folly/Regex.h 的源码,以获得更准确和全面的理解。

11. chapter 11: 附录 (Appendix)

11.1 正则表达式语法速查表 (Regular Expression Syntax Cheat Sheet)

类别 (Category) 符号 (Symbol) 描述 (Description) 示例 (Example)
元字符 (Metacharacters) . 匹配任意单个字符,除了换行符(在某些模式下) (Matches any single character except newline (in some modes)) a.b 匹配 "acb", "a b", "a\tb" 等
^ 匹配字符串的开始位置 (Matches the beginning of the string) ^abc 匹配以 "abc" 开头的字符串
$ 匹配字符串的结束位置 (Matches the end of the string) xyz$ 匹配以 "xyz" 结尾的字符串
[] 字符类,匹配方括号内的任意字符 (Character class, matches any character within the brackets) [aeiou] 匹配任意一个元音字母
\ 转义字符,用于转义元字符或表示特殊字符 (Escape character, used to escape metacharacters or represent special characters) \. 匹配句点 ".",\n 匹配换行符
量词 (Quantifiers) * 匹配零次或多次 (Matches zero or more times) a*b 匹配 "b", "ab", "aab", "aaab" 等
+ 匹配一次或多次 (Matches one or more times) a+b 匹配 "ab", "aab", "aaab" 等,但不匹配 "b"
? 匹配零次或一次 (Matches zero or one time) a?b 匹配 "b", "ab",但不匹配 "aab"
{n} 匹配恰好 n 次 (Matches exactly n times) a{3}b 匹配 "aaab"
{n,} 匹配至少 n 次 (Matches at least n times) a{2,}b 匹配 "aab", "aaab", "aaaab" 等
{n,m} 匹配至少 n 次,至多 m 次 (Matches at least n times, but not more than m times) a{1,3}b 匹配 "ab", "aab", "aaab"
分组与捕获 (Grouping and Capturing) () 分组,将括号内的表达式作为一个整体 (Grouping, treats the expression inside parentheses as a whole unit) (ab)+c 匹配 "abc", "ababc", "abababc" 等
(?:...) 非捕获组,仅分组,不捕获匹配的文本 (Non-capturing group, groups but does not capture the matched text) (?:ab)+c 功能同 (ab)+c,但不捕获 "ab"
选择 (Alternation) \| 或操作符,匹配 \| 两侧的任意一个表达式 (OR operator, matches either the expression on the left or the right) a\|b 匹配 "a" 或 "b"
字符类简写 (Character Class Shorthands) \d 匹配任意数字 (Digit)
\D 匹配任意非数字 (Non-digit)
\w 匹配任意单词字符(字母、数字、下划线)(Word character: letters, digits, underscore)
\W 匹配任意非单词字符 (Non-word character)
\s 匹配任意空白字符(空格、制表符、换行符等)(Whitespace character: space, tab, newline, etc.)
\S 匹配任意非空白字符 (Non-whitespace character)
环视断言 (Lookaround Assertions) (?=...) 正向肯定环视,断言当前位置后面匹配 ... 的表达式 (Positive lookahead, asserts that the expression ... matches after the current position) a(?=b) 匹配 "a",仅当 "a" 后面跟着 "b"
(?!...) 正向否定环视,断言当前位置后面不匹配 ... 的表达式 (Negative lookahead, asserts that the expression ... does not match after the current position) a(?!b) 匹配 "a",仅当 "a" 后面不跟着 "b"
(?<=...) 反向肯定环视,断言当前位置前面匹配 ... 的表达式 (Positive lookbehind, asserts that the expression ... matches before the current position) (?<=a)b 匹配 "b",仅当 "b" 前面跟着 "a"
(?<!...) 反向否定环视,断言当前位置前面不匹配 ... 的表达式 (Negative lookbehind, asserts that the expression ... does not match before the current position) (?<!a)b 匹配 "b",仅当 "b" 前面不跟着 "a"

注意:此速查表并非 folly/Regex.h 支持的所有正则表达式语法的完整列表。具体支持的语法和特性可能会因底层正则表达式引擎而异。请参考 folly/Regex.h 的官方文档和底层引擎的文档以获取更详细的信息。

11.2 常用正则表达式模式 (Common Regular Expression Patterns)

模式名称 (Pattern Name) 正则表达式 (Regular Expression) 描述 (Description) 示例 (Example)
邮箱地址验证 (Email Address Validation) ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ 验证邮箱地址的基本格式 (Validates basic email address format) test@example.com, user.name@domain.co.uk
URL 提取 (URL Extraction) (https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)) 提取 URL (Extracts URLs) https://www.example.com/path, http://example.org
日期格式 (YYYY-MM-DD) 验证 (Date Format (YYYY-MM-DD) Validation) ^\d{4}-\d{2}-\d{2}$ 验证 YYYY-MM-DD 日期格式 (Validates YYYY-MM-DD date format) 2023-10-27, 2024-01-01
IP 地址 (IPv4) 验证 (IP Address (IPv4) Validation) ^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$ 验证 IPv4 地址 (Validates IPv4 addresses) 192.168.1.1, 10.0.0.5
手机号码 (中国大陆) 验证 (Mobile Phone Number (Mainland China) Validation) ^1[3456789]\d{9}$ 验证中国大陆手机号码 (Validates mobile phone numbers in Mainland China) 13800138000, 15912345678
HTML 标签去除 (HTML Tag Removal) <[^>]+> 移除 HTML 标签 (Removes HTML tags)
空白行去除 (Blank Line Removal) ^\s*$\n (多行模式) 移除空白行 (Removes blank lines)

注意:以上正则表达式模式仅为示例,可能需要根据实际应用场景进行调整和完善。例如,邮箱地址和 URL 的验证是一个非常复杂的问题,上述模式仅能覆盖常见的简单情况。对于更严格的验证需求,可能需要使用更复杂的正则表达式或结合其他验证方法。

11.3 术语表 (Glossary)

术语 (Term) 英文 (English) 解释 (Explanation)
正则表达式 Regular Expression (Regex/RegExp) 一种用于描述字符串模式的强大工具,可以用于匹配、查找、替换文本。 (A powerful tool for describing string patterns, used for matching, searching, and replacing text.)
模式 Pattern 正则表达式定义的字符串规则或模板。 (The string rule or template defined by a regular expression.)
匹配 Match 在文本中找到符合正则表达式模式的子字符串。 (Finding a substring in the text that conforms to the regular expression pattern.)
元字符 Metacharacter 在正则表达式中具有特殊含义的字符,例如 .^$*+?[]\(){}\|。 (Characters with special meanings in regular expressions, such as ., ^, $, *, +, ?, [], \, (), {}, |.)
量词 Quantifier 用于指定模式元素重复次数的符号,例如 *+?{n}{n,}{n,m}。 (Symbols used to specify the number of repetitions of a pattern element, such as *, +, ?, {n}, {n,}, {n,m}.)
字符类 Character Class 用方括号 [] 定义的字符集合,匹配集合中的任意一个字符。 (A set of characters defined in square brackets [], matching any single character in the set.)
转义字符 Escape Character 反斜杠字符 \,用于取消元字符的特殊含义或表示特殊字符。 (The backslash character \, used to remove the special meaning of metacharacters or represent special characters.)
分组 Grouping 用圆括号 () 将正则表达式的一部分括起来,作为一个整体进行处理。 (Enclosing part of a regular expression in parentheses () to treat it as a whole unit.)
捕获组 Capturing Group 分组的一种形式,会将括号内匹配的文本捕获到内存中,以便后续引用。 (A form of grouping that captures the matched text within the parentheses into memory for later reference.)
非捕获组 Non-capturing Group 使用 (?:...) 语法定义的分组,仅用于分组,不捕获匹配的文本。 (A group defined using the (?:...) syntax, used only for grouping and not for capturing the matched text.)
选择 Alternation 使用 \| 运算符表示“或”关系,匹配多个模式中的任意一个。 (Using the | operator to represent an "OR" relationship, matching any one of multiple patterns.)
环视断言 Lookaround Assertion 一种零宽断言,用于在匹配过程中检查当前位置的前后环境,但不消耗字符。包括正向肯定环视 (?=...)、正向否定环视 (?!...)、反向肯定环视 (?<=...)、反向否定环视 (?<!...)。 (A zero-width assertion used to check the context before or after the current position during matching, without consuming characters. Includes positive lookahead (?=...), negative lookahead (?!...), positive lookbehind (?<=...), and negative lookbehind (?<!...).)
回溯 Backtracking 正则表达式引擎在匹配过程中,由于某些量词或选择结构,可能需要回退到之前的状态并尝试其他匹配路径。过度回溯可能导致性能问题。 (During regular expression matching, due to certain quantifiers or alternation structures, the engine may need to backtrack to previous states and try other matching paths. Excessive backtracking can lead to performance issues.)
预编译 Pre-compilation 将正则表达式模式预先编译成引擎内部的表示形式,以提高后续匹配操作的性能。 (Pre-compiling the regular expression pattern into the engine's internal representation to improve the performance of subsequent matching operations.)

注意:术语表中的解释旨在提供简明扼要的定义,更详细的解释请参考本书相关章节或正则表达式的专业文档。

END_OF_CHAPTER