用纯正则实现可靠 Markdown 解析器不可行,因其无法处理嵌套、转义及上下文敏感语法;推荐使用 commonmark-java 或 flexmark-java 等成熟解析器。

为什么别用正则写 Markdown 解析器
直接说结论:用纯正则实现一个「可靠」的 Markdown 转 HTML 解析器,在实际项目中几乎不可行。不是写不出来,而是它会在嵌套、转义、边界情况上持续掉链子——比如 **bold *italic***、`code **inside`、或连续换行与列表缩进混用时,正则很快失去控制力。
常见错误现象:PatternSyntaxException 没出现,但输出 HTML 错乱(如标签没闭合、内容被截断、代码块里误替换);更隐蔽的是,某些输入能过测试用例,上线后遇到用户真实文本就崩。
根本原因在于:Markdown 是上下文敏感语法,而正则本质是正则文法(Regular Grammar),无法处理嵌套层级和状态回溯。哪怕用 java.util.regex.Pattern 的 DOTALL 或 UNICODE_CHARACTER_CLASS,也只缓解表层问题。
哪些场景下可以谨慎用正则做简单转换
如果你只是处理内部可控、格式极简的文本(比如 CMS 后台的短描述字段,且明确禁止嵌套、不支持代码块/表格/HTML 混排),正则可作为快速原型手段。但必须守住三条线:
立即学习“Java免费学习笔记(深入)”;
- 只处理单段落内、无嵌套的行内元素(如
**text**→<strong>text</strong>,但跳过**a *b* c**) - 所有匹配必须带非贪婪量词 + 明确边界(用
(? 避免转义星号,用 <code>(? 锚定段落开头) - 严格按顺序执行替换:先处理反斜杠转义,再处理代码片段(
`...`),最后处理强调、链接等——顺序错一步,`*x*`就可能被提前替换成<em>x</em>
示例(仅示意顺序与边界):
String html = text
.replaceAll("(?注意:这不处理换行、列表、标题,也不防 XSS,仅作演示。<h3>Java 里真正靠谱的轻量级替代方案</h3><p>别自己造轮子。用现成解析器,核心诉求是「小体积 + 无反射 + 不依赖 Guava/Spring」,推荐两个:</p>-
commonmark-java:标准兼容好,API 干净,Parser+HtmlRenderer两步到位,支持扩展(如自定义节点渲染)。默认不执行 JS,XSS 风险可控。 -
flexmark-java:更灵活(插件化设计),性能略优,但 jar 包稍大;若需解析 GitHub Flavored Markdown(GFM)表格、任务列表,它更稳。
关键差异点:commonmark-java 的 Parser.builder().build() 默认禁用 HTML 内联,而 flexmark-java 需显式调用 HtmlRenderer.render() 并配置 HtmlRenderer.SANITIZE_HTML 才安全。两者都不解析脚本,但若原始 Markdown 含 <script></script>,仍需额外过滤。
自己写解析器前必须想清的三件事
如果因合规、审计或学习目的必须手写,别从正则开始。先确认:
- 你的输入是否允许用户自由粘贴?如果答案是“是”,那必须引入状态机或递归下降解析——正则连
line
和~~~代码块起始都难区分。 - 是否要保留原始 AST?比如后续加目录生成、锚点提取。正则替换是破坏性的,无法回溯源位置。
- 性能瓶颈在哪?纯正则在千字以内可能比
commonmark-java快,但一旦加容错逻辑(如跳过不匹配的**)、处理 Unicode 变体(全角星号、零宽空格),维护成本指数上升。
最常被忽略的一点:Markdown 规范本身有多个版本(CommonMark、GFM、Pandoc),连 _emphasis_ 在词边界的行为都不同。没明确定义支持哪一版,写出来的解析器本质上就是个半成品。











