news 2026/9/17 3:54:14

MongoDB 仓库中的 RE2 正则引擎:线性时间匹配、C++ API 与 Bazel 构建全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MongoDB 仓库中的 RE2 正则引擎:线性时间匹配、C++ API 与 Bazel 构建全解析

MongoDB 仓库中的 RE2 正则引擎:线性时间匹配、C++ API 与 Bazel 构建全解析

【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo

RE2 是一款以安全性为首要目标的正则表达式库,自 2006 年起在 Google 及众多公司生产环境中服役,其核心承诺是"匹配时间与输入长度成线性关系",即使面对不可信用户构造的恶意正则也不会发生灾难性回溯。本文以 MongoDB 仓库中随源码分发的 RE2(位于 src/third_party/re2/dist/README.md)为主体,完整讲解其设计哲学、语法边界、C++ 匹配接口、子匹配提取、预编译对象、Options 配置、Unicode 语义、多引擎实现原理与三种构建方式,并对照仓库内的 BUILD.bazel、re2.h 与测试源码给出源码级佐证,帮助读者在 MongoDB 工程体系内正确编译、链接与使用 RE2。

一、设计哲学:为什么说"安全是 RE2 的第一目标"

RE2 的诞生背景是:传统回溯型正则引擎(如 PCRE/Perl)在处理(a|a)*b这类模式与恶意输入组合时,匹配耗时可能随输入长度指数级增长(即灾难性回溯,Catastrophic Backtracking),从而成为 DoS 攻击面。RE2 从根上规避了这个问题,其保证可以归纳为三条:

  1. 线性时间保证:匹配时间与输入字符串长度渐近线性相关,不受正则表达式结构的影响;
  2. 受控内存预算:解析器、编译器和执行引擎都在一个可配置的内存预算内工作,预算耗尽时优雅失败(fail gracefully),而不是耗尽进程内存;
  3. 杜绝递归:实现全程避免递归,从根本上防止栈溢出。

README 用一句非常精辟的对比总结了 RE2 与回溯引擎的气质差异:

RE2 是"悲观"的,回溯引擎是"乐观"的。回溯引擎逐个尝试每个备选分支,当第一个分支经常命中时会很快;而 RE2并行评估所有分支,避免了"最后一个分支才命中"时的性能惩罚,代价是固定开销。正是这种悲观,成就了 RE2 的安全。

同时 README 也坦诚声明了两个非目标

  • 并非在所有场景下都比其他引擎快——更复杂的表达式会带来更大的常数因子,更长的表达式会提高安全处理所需的开销;
  • 并非实现 Perl/PCRE 的全部特性——凡已知只有回溯方案才能实现的构造一律不支持,因此反向引用(backreferences)和环视断言(look-around)被明确排除(子匹配提取仍然支持,见下文)。

仓库中的证据

RE2 以第三方库形式随 MongoDB 仓库源码分发,目录为src/third_party/re2/dist/,包含LICENSECONTRIBUTING.mdSECURITY.mdREADME.mdMODULE.bazel(Bazel Bzlmod 模块描述)以及re2/util/两个源码子目录。其多引擎实现文件分布在 re2/ 下,包括nfa.cc(NFA 执行)、dfa.cc(DFA 执行)、onepass.cc(OnePass 专用路径)、bitstate.cc(位状态模拟)、parse.cc(语法解析)、compile.cc(程序编译)、prog.cc/h(编译产物 Prog)、simplify.cc(正则化简)等,这些正是"线性时间、无递归"承诺的具体承载者,我们将在"多引擎架构"一节展开。

二、语法支持:POSIX 模式与 Perl 模式

RE2 支持两套语法模式,默认是 Perl 模式

模式语法来源匹配语义
Perl 模式(默认)大多数 Perl 操作符与 Perl 选择相同的匹配结果
POSIX 模式标准 POSIX(egrep)语法最左最长(leftmost-longest)匹配

被排除的只有那些需要回溯才能实现、因而可能带来指数级运行时间的构造,典型就是反向引用与广义断言(generalized assertions)。re2.h开头的注释进一步补充了语法速览,例如\w(单词字符)、\d(数字)、\s(空白)、\b(词边界)、(?i)(忽略大小写)、.*?(最小匹配)等都是支持的;同时明确\Z这类断言也不可用。

在 C++ 字符串字面量中写正则需要双重转义(如"(\\w+):(\\d+)"),而使用 C++11 原始字符串字面量则不需要:

R"(hello (\w+) world)" // \w 匹配单词字符 R"(version (\d+))" // \d 匹配数字 R"((?i)hello)" // (?i) 开启忽略大小写 R"(/\*(.*?)\*/)" // .*? 最小匹配

从源码看,语法解析集中在 parse.cc,它还配套了 mimics_pcre.cc,该文件用于判断某个正则是否"行为上与 PCRE 相同"(对应仓库中的mimics_pcre_test),这从侧面印证了 RE2 在语法子集上对 PCRE 的刻意对齐与差异化管理。

三、C++ 匹配接口:FullMatch 与 PartialMatch

RE2 的原生语言是 C++,核心只有两个基本操作符

  • RE2::FullMatch:要求正则与整个输入文本完全匹配;
  • RE2::PartialMatch:在输入文本的子串中寻找匹配;POSIX 模式下返回最左最长匹配,Perl 模式下返回与 Perl 相同选择的结果。

README 给出的最小示例:

assert(RE2::FullMatch("hello", "h.*o")) assert(!RE2::FullMatch("hello", "e")) assert(RE2::PartialMatch("hello", "h.*o")) assert(RE2::PartialMatch("hello", "e"))

注意FullMatch("hello", "e")失败,而PartialMatch("hello", "e")成功——这正是两个操作符语义差异的直观体现。re2.h中还给出了"提取第一个数字"的经典用例:

int number; ABSL_CHECK(RE2::PartialMatch("x*100 + 20", "(\\d+)", &number)); ABSL_CHECK_EQ(number, 100);

错误码枚举

re2.h 定义了完整的ErrorCode枚举,编译失败后可通过re.error()获取,常见值包括:

  • ErrorBadEscape:非法转义序列
  • ErrorBadCharClass/ErrorBadCharRange:字符类或其区间非法
  • ErrorMissingBracket/ErrorMissingParen:缺少闭合的])
  • ErrorUnexpectedParen:多余的闭合)
  • ErrorTrailingBackslash:正则末尾孤立的\
  • ErrorRepeatArgument/ErrorRepeatSize/ErrorRepeatOp:重复操作符参数缺失、参数非法或操作符非法
  • ErrorBadPerlOp:非法 Perl 操作符
  • ErrorBadUTF8:正则中含非法 UTF-8
  • ErrorBadNamedCapture:命名捕获组非法
  • ErrorPatternTooLarge:模式过大,编译失败

四、子匹配提取:string / 整数 / string_view

两个匹配函数都接受额外的输出参数用于存放子匹配(submatch),参数类型可以是string*、整数类型指针,或absl::string_view*。README 特别解释:absl::string_viewstd::string_view非常相似,只是出于历史原因 RE2 使用前者;它是一个"指向原始输入文本的指针 + 长度",行为像字符串但不持有自己的存储,因此一旦原始文本被删除或超出作用域,就不能再使用这个 view——与裸指针的注意事项一致。

README 给出的完整示例(保留全部细节):

// 解析成功。 int i; string s; assert(RE2::FullMatch("ruby:1234", "(\\w+):(\\d+)", &s, &i)); assert(s == "ruby"); assert(i == 1234); // 失败:"ruby" 无法被解析为整数。 assert(!RE2::FullMatch("ruby", "(.+)", &i)); // 成功;不提取数字。 assert(RE2::FullMatch("ruby:1234", "(\\w+):(\\d+)", &s)); // 成功;跳过 NULL 参数。 assert(RE2::FullMatch("ruby:1234", "(\\w+):(\\d+)", (void*)NULL, &i)); // 失败:整数溢出导致 i 中不保存值。 assert(!RE2::FullMatch("ruby:123456789123", "(\\w+):(\\d+)", &s, &i));

re2.h对提取语义补充了三条重要细节:

  1. 失败时不改动:匹配失败时,任何输出对象都不会被修改;
  2. 转换失败即中止:匹配成功后按顺序把各子匹配转换并赋给输出对象,直到某个转换失败为止;对string/string_view这类不检查内容的类型,转换不会失败,因此常见情况下失败原因就是"匹配失败";
  3. 整数溢出算失败:目标文本无法被解析为对应整数类型(如超出范围)时,FullMatch/PartialMatch返回false——这正是上面最后一个断言的设计意图。

另外,re2.h注释给出了一条性能经验:请求子匹配会让成功匹配明显变慢(目前甚至慢于 PCRE),但失败匹配与不提取子匹配的匹配则非常快——这与 RE2"并行评估所有分支、快速否决"的悲观设计一脉相承。

运行时参数个数:FullMatchN 与 Arg

当参数个数在运行时才能确定(例如正则本身是动态计算的)时,可以改用N系列操作:

const RE2::Arg* args[10]; int n; // ... 用 RE2::Arg 对象的指针填充 args ... // ... 将 n 设为 RE2::Arg 对象的个数 ... bool match = RE2::FullMatchN(input, pattern, args, n);

上面的调用等价于RE2::FullMatch(input, pattern, *args[0], ..., *args[n-1])RE2::Arg类在re2.h中定义,是"把用户传入的指针包装成特殊 Arg 对象"的机制。

进制解析:Hex / Octal / CRadix

默认情况下,传入数值指针时对应文本按十进制解析。通过RE2::Hex()RE2::Octal()RE2::CRadix()包装指针可以改变进制,其中CRadix按 C 风格识别0x(十六进制)与0前缀(八进制),无前缀则回退到十进制:

int a, b, c, d; ABSL_CHECK(RE2::FullMatch("100 40 0100 0x40", "(.*) (.*) (.*) (.*)", RE2::Octal(&a), RE2::Hex(&b), RE2::CRadix(&c), RE2::CRadix(&d)); // 结果:a == b == c == d == 64

五、预编译正则对象:RE2 re("...")

前面所有示例在每次调用时都重新编译正则。高频路径下应把编译结果缓存为RE2对象,编译一次、复用多次:

RE2 re("(\\w+):(\\d+)"); assert(re.ok()); // 编译成功;若失败,查看 re.error() assert(RE2::FullMatch("ruby:1234", re, &s, &i)); assert(RE2::FullMatch("ruby:1234", re, &s)); assert(RE2::FullMatch("ruby:1234", re, (void*)NULL, &i)); assert(!RE2::FullMatch("ruby:123456789123", re, &s, &i));

关键事实(来自 re2.h):

  • RE2对象即"预编译的正则表达式",对应一个内部Prog程序(见下文"编译流水线");
  • RE2对象可被多个线程安全地并发使用——这在多线程服务端场景(如 MongoDB 这类数据库的查询/校验路径)中是重要的可用性保证;
  • re.ok()判断编译是否成功,re.error()返回失败详情(ErrorCode枚举 + 可读文本)。

六、Options:Quiet、Latin1、POSIX 与自定义选项

构造函数接受可选的第二个参数用于覆盖默认选项。三个最常用的预置选项:

预置选项作用
RE2::Quiet静默"正则解析失败"时通常打印到 stderr 的错误信息
RE2::Latin1禁用 UTF-8,按 Latin-1 解释模式与输入
RE2::POSIX使用 POSIX 语法与最左最长匹配

Quiet的典型用法——解析非法模式而不污染 stderr:

RE2 re("(ab", RE2::Quiet); // 解析失败时不要写 stderr assert(!re.ok()); // 可通过 re.error() 查看细节

完整的可配置项(大小写折叠、最大内存、最长匹配、日志级别、encodingnever_nldot_nlone_linelongest_match等)定义在RE2::Options类中,位于 re2.h 的class Options。README 明确建议:可以自行声明一个RE2::Options对象并按需配置,例如:

RE2::Options opts; opts.set_encoding(RE2::Options::EncodingLatin1); // 或 EncodingUTF8 opts.set_log_errors(false); // 等价于 Quiet RE2 re(pattern, opts);

(上述set_encoding/set_log_errorsOptions提供的典型 setter,完整清单以 re2.h 为准。)

七、Unicode 处理:基于码点,不做归一化

RE2 在Unicode 码点(code point)层面工作,不进行任何归一化(normalization)。README 给出的例子非常典型:

  • 正则/ü/(U+00FC,带分音符的 u)不能匹配输入"ü"(U+0075 U+0308,u 后跟组合分音符)。

因为前者是单个预组合码点,后者是两个码点序列,二者在码点层面不相等。归一化本身是个庞大复杂的主题(涉及 NFC/NFD 等),RE2 的立场是不越俎代庖。README 给出的实用建议是:如果确实需要这类匹配,请在预处理阶段把"正则表达式"和"输入文本"都归一化后再交给 RE2

re2.h还补充了一条与 UTF-8 相关的语义:使用 UTF-8 编码时,忽略大小写匹配执行的是简单大小写折叠(simple case folding),而非完整折叠(full case folding)——两者在个别语言字符(如涉及多字符映射的情况)上行为不同,需要精确语义时应查阅 Unicode 标准(UAX #15 等资料)。

八、增量扫描:Consume 与 FindAndConsume

除了FullMatch/PartialMatchre2.h还记录了两种适合"流式/增量"解析的操作:

  • RE2::Consume:把匹配锚定在字符串开头,匹配成功后推进absl::string_view越过已匹配文本,适合逐行解析var = value这类固定格式:
std::string contents = ...; // 填充字符串 absl::string_view input(contents); // 用 string_view 包一层 std::string var; int value; while (RE2::Consume(&input, "(\\w+) = (\\d+)\n", &var, &value)) { // ...处理 var/value... }

注意:若正则可能匹配空串,input将不前进(推进 0 字节),循环体必须检查该情况并手动前进或跳出,否则会死循环。

  • RE2::FindAndConsume:与Consume类似但不把匹配锚定在开头,例如反复提取字符串中的所有单词:
RE2::FindAndConsume(&input, "(\\w+)", &word)

九、构建与安装:make / CMake / Bazel 三条路径

RE2 支持 GNU make、CMake、Bazel 三种构建方式。构建 RE2 本身需要 C++17 编译器与 Abseil 库;构建测试和基准还需要 GoogleTest 与 Google Benchmark。

9.1 GNU make 方式(最简)

make make test make benchmark make install make testinstall

9.2 依赖获取

  • Linux:apt install libabsl-dev libgtest-dev libbenchmark-dev
  • macOS:brew install abseil googletest google-benchmark pkg-config-wrapper
  • Windows:vcpkg install abseil gtest benchmarkvcpkg add port abseil gtest benchmark

9.3 CMake 方式

如果标准 Makefile 在查找依赖时遇到问题,切换到 CMake 往往能解决:

rm -rf build cmake -DRE2_TEST=ON -DRE2_BENCHMARK=ON -S . -B build cd build make make test make install

CMake 相关的几个要点:

  • 启用 benchmark 时,make test会构建并运行测试二进制,同时构建regexp_benchmark二进制但不运行它;
  • 不需要测试/基准时,省略对应-D参数即可,此时也不需要 GoogleTest/Benchmark 依赖
  • -DRE2_USE_ICU=ON会引入 ICU Unicode 库依赖,同时扩展\p\P模式可用的属性名列表(如更多 Unicode 属性类别);
  • CMake 还能生成 Visual Studio、Xcode 工程以及 Cygwin、MinGW、MSYS makefile。Visual Studio 用户需要 2019 或更高版本;Cygwin 用户必须从 Cygwin 命令行(而非 Windows 命令行)运行 CMake。

作为依赖集成进自有 CMake 工程时,有两种方式:add_subdirectory()(依赖的源码位于你工程的子目录中)与find_package()(依赖的二进制已构建并安装到系统)。两种方式下target_link_libraries(... re2::re2)都应"开箱即用"。

9.4 Bazel 方式(MongoDB 仓库的集成形态)

独立使用 Bazel 构建 RE2 时,Bazel 会自动处理依赖(仍需自行下载 Bazel,可通过 Bazelisk 管理版本);在仓库内执行:

bazelisk build :all bazelisk test :all

在 MongoDB 仓库中,RE2 已经通过 Bazel 完整接入,见 src/third_party/re2/dist/BUILD.bazel。该文件的核心是一个cc_library(name = "re2")目标,从中可以读出非常具体的集成信息:

  • 源码清单:包含re2/re2.ccre2/compile.ccre2/dfa.ccre2/nfa.ccre2/onepass.ccre2/bitstate.ccre2/parse.ccre2/prog.ccre2/regexp.ccre2/simplify.ccre2/filtered_re2.ccre2/set.ccre2/mimics_pcre.ccre2/tostring.ccre2/unicode_casefold.ccre2/unicode_groups.ccre2/perl_groups.cc以及util/rune.ccutil/strutil.cc等;
  • 公共头文件re2/filtered_re2.hre2/re2.hre2/set.hre2/stringpiece.h
  • 依赖:一组 Abseil 目标(@abseil-cpp//absl/...,包括stringshashcontainer:flat_hash_map/setcontainer:inlined_vectorcontainer:fixed_arraysynchronizationtypes:spantypes:optionallogbase等)——与 README"构建需要 Abseil"的说明完全对应;
  • 平台差异copts/linkopts通过select按平台调整-pthread——macOS 与 WebAssembly(wasm32/wasm64/emscripten/wasi)以及 Windows 不传-pthread,其余平台默认加上,注释明确指出"WebAssembly 的线程支持在每一层都很棘手";
  • 可见性visibility = ["//visibility:public"],即仓库内其他 Bazel 目标可以公开依赖该库。

依赖方工程若通过 Bazel 使用 RE2,需保证编译标准不低于C++17(README 明确要求,并指向.bazelrc作为示例)。

十、测试体系:从单元测试到穷举测试

BUILD.bazel中暴露了完整且分层清晰的测试矩阵,全部位于 re2/testing/:

  • 小规模单元测试size = "small"):charclass_test(字符类)、compile_test(编译)、parse_test(语法解析)、simplify_test(正则化简)、regexp_testre2_test(核心 API)、re2_arg_test(Arg 参数机制)、search_testset_test(批量匹配 Set)、filtered_re2_test(预过滤)、possible_match_test(可能的匹配前缀)、required_prefix_test(必要前缀)、mimics_pcre_test(与 PCRE 行为对齐度)、string_generator_test(测试用字符串生成器);
  • 大规模测试size = "large"):dfa_test(DFA 引擎专项)、exhaustive_testexhaustive1/2/3_test穷举测试:对生成的正则与字符串集合做全量比对,这是验证"与参考语义一致"的关键手段)、random_test(随机模糊测试);
  • 基准regexp_benchmarktestonly = 1cc_binary,依赖@google_benchmark)。

这套"单元 + 穷举 + 随机 + 基准"的测试结构,正是 RE2 敢在安全敏感场景(不可信正则 + 不可信输入)下承诺线性时间的重要底气。另有一个testing库(testonly = 1)把backtrack.cc(参考回溯实现,用于对照验证)、exhaustive_tester.ccregexp_generator.ccstring_generator.ccutil/pcre.cc组织起来,作为"参照实现"支撑穷举测试。

十一、核心实现原理:编译流水线与多引擎架构

结合 re2/ 目录源码,可以把 RE2 的工作方式拆成"编译期"与"执行期"两段:

编译流水线(源码级)

  1. parse.cc:把正则字符串解析为 AST(Regexp对象树,定义于regexp.h),同时执行语法校验并生成上文提到的ErrorCode
  2. simplify.cc:对Regexp做等价化简(如折叠嵌套、规并字符类、re2内部 normalization),缩小后续编译规模;
  3. compile.cc:把化简后的Regexp编译成指令形式的程序Progprog.h/prog.cc),这是 NFA/DFA 等执行引擎统一消费的中间表示;
  4. 配套的unicode_casefold.cc/unicode_groups.cc/perl_groups.cc是由make_unicode_casefold.pymake_unicode_groups.pymake_perl_groups.pl等脚本生成的 Unicode 数据表,支撑(?i)折叠、\p{...}属性与 Perl 字符类。

多引擎执行(执行期)

RE2 内部按输入与正则特征在多个执行器间选择,对应文件:nfa.cc(NFA 模拟)、dfa.cc(DFA 构造与缓存)、onepass.cc(OnePass:无分支/少分支正则的专用快速路径)、bitstate.cc(位向量状态集模拟)。这种"多引擎 + 自动选择"的架构,配合前面提到的内存预算(预算耗尽时优雅降级/失败)与无递归约束,共同保证了最坏情况下的线性时间与可控资源占用。pod_array.hsparse_array.hsparse_set.hbitmap256.cc等数据结构文件则是这些引擎高效运行的基础设施。

批量与预过滤:Set 与 FilteredRE2

仓库还提供了两类面向规模化场景的 API:

  • RE2::Setset.cc/set.h):把大量正则一次性编译成一个集合,对一段输入同时测试"匹配了集合中的哪些正则",避免逐个编译、逐个匹配的开销;
  • FilteredRE2filtered_re2.cc/filtered_re2.h):在真正执行匹配前,先对所有正则做必要前缀/可能匹配前缀的预计算(prefilter.ccprefilter_tree.cc),先用廉价测试快速排除绝大多数不可能命中的正则,再对候选集做精确匹配——适合"数千条规则对一段文本做路由/分类"的服务端场景。

十二、多语言生态:官方封装与同源移植

RE2 原生实现是 C++,但生态覆盖广泛:

  • 官方 Python 封装位于 RE2 仓库的python/目录,发布在 PyPI 上名为google-re2。README 特别提醒:PyPI 上还有一个re2包,但那并非 RE2 作者发布、且已不再维护,请务必使用google-re2
  • 其他语言的非官方封装包括:C(cre2)、D(re2d)、Erlang、Inferno、Node.js(npm 上的re2)、OCaml(Jane Street 维护)、Perl(re::engine::RE2,CPAN)、R、Ruby、WebAssembly(re2-wasm)等;
  • 同源移植RE2J是把 RE2 C++ 代码移植为纯 Java 的版本,RE2JS是 RE2J 到 JavaScript 的移植;
  • 同原理不同代码:Go 的regexp标准包与 Rust 的regexcrate 与 RE2不共享代码,但遵循相同原则、接受相同语法、提供相同的效率保证——也就是说,即使在其他语言栈中,也可以用同一套"线性时间、无灾难性回溯"的思维模型。

十三、在本仓库中的定位与使用建议

MongoDB 将 RE2 以随源码分发的第三方库形式放在src/third_party/re2/dist/,通过 Bazel 的cc_library(name = "re2")目标(BUILD.bazel)对外暴露,仓库内其他目标可以直接依赖使用。需要指出的是,MongoDB 服务端自身的通用正则表达式功能(如$regex查询)走的是PCRE2路线——src/mongo/util/pcre.h 明确说明它是"为 PCRE2 库封装的 MongoDB C++ 包装层",且刻意通过封装隔离了对 pcre2 头文件的直接依赖。因此在本仓库中,RE2 是作为独立、可选、安全优先的正则基础设施随仓库提供的:

  • 如果你的组件要处理不可信输入上的正则匹配(如用户提供的模式做匹配/过滤/校验),且能接受 RE2 的语法子集(不支持反向引用与环视),RE2 的线性时间保证是最合适的选择;
  • 如果需要 Perl/PCRE 全量语法(反向引用、环视等),则应使用服务端已有的 PCRE2 封装mongo::pcre
  • 无论选择哪条路径,本仓库的 Bazel 目标已经就绪,构建时只需正确声明依赖并保证 C++17 编译标准即可。

结语

从设计目标到多引擎实现,从FullMatch/PartialMatchConsume/FindAndConsume,从 make/CMake 到仓库内现成的 Bazel 目标,RE2 用"悲观但安全"的方式解决了正则引擎最棘手的可用性问题。对照 README.md 与 re2.h、BUILD.bazel 及 re2/testing 的测试源码,读者既可以在 MongoDB 的 Bazel 工程内直接复用这一安全正则基础设施,也可以把这套"线性时间 + 内存预算 + 无递归"的工程方法论迁移到自己的系统中。

【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 3:53:26

OLED显示器选购:场景匹配比参数更重要

1. OLED显示器选购不是参数竞赛,而是场景匹配游戏OLED显示器这几年从高端电视延伸到桌面显示领域,但很多人一看到“自发光”“无限对比度”就直接下单,结果买回来发现:看文档眼睛累、修图偏色、打游戏反而不如老LCD流畅——这不是…

作者头像 李华
网站建设 2026/9/17 3:52:02

有魔法还是用不了高级模型?TaoToken 这样改 HTTP/1.1

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 3:50:52

EasyExcel 2.2.10 指定行与指定列样式设置实战指南

1. 先说结论:EasyExcel 2.2.10 的样式机制,为什么单独的行和列这么难搞用 EasyExcel 2.2.10 做过导出的人应该都有这个感受:普通导出太简单了,实体类加几个注解,一行doWrite就把本地文件写出来了。但一旦涉及到"给…

作者头像 李华