
本文介绍一种基于负向先行断言的正则表达式方案,用于安全移除字符串末尾的括号及其内容,同时保留符合 (YYYY-YYYY) 格式的日期范围不被误删。
本文介绍一种基于负向先行断言的正则表达式方案,用于安全移除字符串末尾的括号及其内容,同时保留符合 `(yyyy-yyyy)` 格式的日期范围不被误删。
在文本处理中,常需清理末尾冗余括号内容(如注释、标签等),但又必须避免误删具有语义的日期范围(如 (1920-1988))。直接匹配 (.+) 会导致过度删除;而简单锚定 $ 又无法区分合法日期与普通括号。解决方案是结合位置锚定 + 负向先行断言 + 非贪婪安全匹配。
✅ 推荐正则表达式
s*( (?!d{4}p{Pd}d{4})) [^()]+ )s*$- s*(:匹配行尾前可选空格和左括号
- (?!d{4}p{Pd}d{4})):关键! 负向先行断言——确保括号后不是“4位数字 + Unicode 连字符(支持 -、–、— 等)+ 4位数字 + 右括号”
- [^()]+:安全匹配括号内任意非括号字符(避免嵌套误匹配)
- )s*$:匹配右括号及后续空格,并严格锚定到行尾
? p{Pd} 是 Unicode 类别,涵盖所有短横线类符号(如 U+002D, U+2013, U+2014),比硬编码 - 更健壮。
? 使用示例(Java)
String input = "foo bar (blah)";
String cleaned = input.replaceAll("\s*\( (?!\d{4}\p{Pd}\d{4}\)) [^()]+ \)\s*$", "");
System.out.println(cleaned); // 输出: "foo bar"
// 多组测试
List<String> testCases = Arrays.asList(
"foo bar (blah)", // → "foo bar"
"foo bar (blah) blah (xyz)", // → "foo bar (blah) blah"
"some data (1920-1977)", // → 不变(日期范围被保护)
"text (123-4567)", // → 不变(位数不符,不触发排除逻辑)
"item (extra) " // → "item"(自动清理尾部空格)
);⚠️ 注意事项
- 不匹配嵌套括号:((a)) 或 (a(b)) 均不匹配,因 [^()]+ 明确禁止括号字符;
- 空括号被忽略:( ) 或 () 因 [^()]+ 要求至少一个非括号字符,故不匹配;
- 跨行无效:$ 默认匹配行尾(非字符串尾),如需全文本处理,添加 Pattern.DOTALL 并用 Z 替代 $;
- 性能提示:该正则为线性扫描,无回溯风险,适用于大规模文本清洗。
✅ 总结
该方案通过精准的位置控制($)、语义排除(负向断言)和内容约束([^()]+),在保持简洁的同时实现高可靠性。它既规避了传统贪婪匹配的过删问题,又比多次 replaceAll 更高效——一次正则即可完成语义感知的末尾括号清理。

















