Java中提取字符串数字:split()与正则表达式效率对比
在 Java 中,从路径类字符串(如 `/good/123/bad/456`)中提取数字时,直接使用正则 `Matcher` 配合 `Long.parseLong(CharSequence, int, int, int)`(Java 9+)比 `String.split()` 或 `Pattern.matcher().group()` 更高效,可避免不必要的字符串拷贝和对象创建,从而提升性能。
从路径类字符串(比如 /good/123/bad/456)里高效提取数字,到底哪种方法才是最优选?先给出结论:在 Java 9 及更高版本中,直接使用预编译的正则 Matcher 配合 Long.parseLong(CharSequence, int, int, int) 这套组合拳,比我们常用的 String.split() 或 Pattern.matcher().group() 要高效得多。核心原因在于,它能从根本上避免掉不必要的字符串拷贝和对象创建,从而显著降低内存开销与 GC 压力。
如果场景是高频调用或性能敏感(比如日志解析、路由匹配、协议解码),底层那些字符串操作的细节就绝不能忽视。咱们就拿一个具体例子来拆解——比如要解析 "/good/312321312/bad/3213122131" 这个字符串,目标是干净利落地取出两个数字 312321312 和 3213122131。很多人第一反应是用 split("/"),代码写起来确实很直观:
String[] parts = s.split("/");// 然后遍历 parts 找数字 → 低效:创建 5 个新 String 对象,含 "/good"、""、"312321312" 等
问题就藏在这里。 split() 内部会为每一个分割出来的片段都调用 substring(),而在 Java 9+ 里,substring() 虽然已经改成复制字符数组了,但还是要重新分配新的字符串对象并拷贝对应的字节。想想看,我们只是想要那两个数字,结果却白白地多创建了 "good"、"bad" 这些中间对象,对于数值提取来说,这是纯粹的额外负担,也会拖慢整体性能。
那么,更优的方案是什么?关键思路是:复用预编译的 Pattern,结合 Matcher 定位数字的起止索引,然后用零拷贝的方式直接从原始字符串中读取目标片段。代码实践起来是这样的:
private static final Pattern DIGIT_PATTERN = Pattern.compile("\\d+");public static long[] extractNumbers(String s) { Matcher m = DIGIT_PATTERN.matcher(s); LongStream.Builder builder = LongStream.builder(); while (m.find()) { // Java 9+ 新增重载:无需创建子串,直接解析 char 序列区间 long num = Long.parseLong(s, m.start(), m.end(), 10); builder.add(num); } return builder.build().toArray();}
这才是关键优化所在,我们一处一处来看:
- 复用预编译的 Pattern:将
Pattern声明为static final,避免了每次调用方法时都重新编译正则的巨大开销。那其实是String.split(String)内部隐式干的事情,在这里被我们绕过去了。 - 零字符串创建:
Long.parseLong(CharSequence, int, int, int)这个重载的绝妙之处在于,它直接操作原字符串的逻辑区间进行解析,完全绕过了m.group()生成临时字符串这一步骤。 - 减少 GC 压力:因为没有产生任何中间 String 对象(尤其是像
"good"、"bad"这些非数字部分,压根不会被实例化),垃圾回收的压力自然就小了很多,这对高并发环境尤为重要。 - 更强的可控性:使用
Matcher.find()可以方便地按需提取前 N 个匹配结果,比如场景明确要求“只取前两个数字”时,这种写法就非常灵活,无需遍历所有分割片段。
不过,这个方案也有一些需要注意的地方:
- 它要求运行在 JDK 9 或更高版本上,因为这个四参数的
Long.parseLong重载是 Java 9 才加入的特性,低版本无法直接使用。 - 如果项目必须兼容 JDK 8,可以退而求其次,用
Long.parseLong(m.group())来做。虽然这比split()方案要好上不少(至少只创建了数字部分的字符串对象),但依然会多出少量字符串对象,性能稍逊一筹。 - 对于超长的字符串或者海量调用的场景,还可以进一步优化:把
Matcher实例也缓存起来(通过m.reset(s)复用),这样可以进一步减少对象分配的次数,提升吞吐量。 - 如果你的字符串中,数字的位置是固定的(比如永远都是第二个和第四个路径段),那么手动用
indexOf加substring定位也不失为一种选择。但相比之下,正则方案在可维护性和健壮性上更胜一筹——它能自动跳过非数字的干扰项,适应各种格式变化。
总结一下,在务实且追求性能的工程实践中,正则匹配配合区间解析这一套方法论,在效率、可读性和可扩展性上取得了很好的平衡,确实是一个真正优秀的选择。而 split() 要怎么做?把它留在那些逻辑简单、调用频率很低,或者确实需要拿到所有分段语义的场景里去用,才是最合适的。
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
如何在ThinkPHP中实现定时任务与命令行调度方法
用ThinkPHP实现定时任务时,很多开发者第一步就卡在命令行报错上,直接输入php think your:command却无法识别——这种情况绝大多数是因为命令类的注册方式存在问题。下面先梳理几个核心要点。 ThinkPHP 6 中 think 命令如何正确触发自定义指令 直接运行 php thi
ThinkPHP API接口防重放攻击实现方法
先说几个核心判断:API防重放攻击这件事,做对了是道防火墙,做错了就是个心理安慰。很多开发者到踩坑了才明白——验签这东西,放错位置、漏掉字段、存错nonce,每一环都能让整个安全体系直接归零。 验签必须放在中间件里,不能在控制器里写 ThinkPHP 的请求生命周期中,中间件是唯一能在路由匹配、参数
ThinkPHP文件上传必须验证扩展名安全必要性分析
在使用ThinkPHP进行文件上传时,ext扩展名验证通常是开发者首先接触的关键环节。但你真的了解它的实际工作原理吗?它仅比对文件名后缀,而不读取文件内容,甚至对空格和大小写都极其敏感。更为重要的是——它是TP文件上传验证五层防线中不可忽视的第一道关卡,一旦配置遗漏,整个validate验证链将直接
ThinkPHP关联模型自动写入与更新使用教程
需要明确的是,ThinkPHP关联模型并没有提供所谓的“自动写入 更新”魔法开关。所谓的“自动”功能,实际上都需要开发者手动编写配置逻辑才能生效。核心原则在于:主模型和从模型必须分开独立处理,时间戳字段和业务字段需依靠修改器或钩子接管;批量操作则要规规矩矩地绕过模型逻辑来执行——只有理解透彻这些要点
BoxLayout中仅居中一个组件其他默认左对齐
在 Java Swing 中使用 BoxLayout 的 Y_AXIS 方向布局时,很多初学者容易掉进一个常见陷阱:希望将某个组件单独设置为中心对齐,但当调用 `setAlignmentX(CENTER_ALIGNMENT)` 后,却发现其他组件也跟着发生了偏移,完全达不到预期效果。实际上,关键之处
- 日榜
- 周榜
- 月榜
相关攻略
2026-07-04 06:55
2026-07-04 06:55
2026-07-04 06:55
2026-07-04 06:55
2026-07-04 06:54
2026-07-04 06:54
2026-07-04 06:54
2026-07-04 06:54
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程
热门话题

