杰瑞科技汇

Java正则如何精准提取数字?

Java正则表达式终极指南:从入门到精通,轻松提取各种数字(附代码案例)

** 本文是Java程序员必备的实战指南,深入浅出地讲解如何使用正则表达式高效提取字符串中的数字,无论你是需要提取简单的整数、浮点数,还是处理复杂的科学计数法或带千分位的数字,本文都将提供精准的解决方案和可直接运行的代码示例,助你彻底掌握Java数字提取技巧。

Java正则如何精准提取数字?-图1
(图片来源网络,侵删)

引言:为什么Java正则表达式是提取数字的利器?

在数据处理和信息抓取的日常工作中,我们经常遇到从一个杂乱的字符串中提取出特定数字的需求。

  • “您的订单号是:12345,总金额为:¥998.50元” 中提取订单号 12345 和金额 50
  • “CPU使用率:15.6%,内存占用:2048MB” 中提取 62048
  • “产品版本:v2.1.0-beta3” 中提取版本号 2, 1, 0, 3

手动遍历字符串字符进行判断虽然可行,但代码冗长、效率低下且容易出错。Java正则表达式 便成为了我们最强大的工具,它以其简洁、高效和灵活的特性,能以极少的代码量完成复杂的模式匹配任务,是每一位Java开发者必须精通的核心技能。

本文将带你系统学习如何利用Java正则表达式,轻松应对各种数字提取场景。

Java正则表达式基础:核心元字符速览

在开始实战之前,我们先快速回顾一下与数字提取相关的核心正则表达式元字符,如果你已经熟悉,可以跳过本节,直接进入实战案例。

Java正则如何精准提取数字?-图2
(图片来源网络,侵删)
元字符 描述 示例
\d 匹配任意一个数字字符(等同于 [0-9] \d 可以匹配 1, 2, 9
\D 匹配任意一个非数字字符(等同于 [^0-9] \D 可以匹配 a, , 空格
匹配前面的子表达式一次或多次 \d+ 可以匹配 123, 45
匹配前面的子表达式零次或多次 \d* 可以匹配 123,
匹配前面的子表达式零次或一次 \d? 可以匹配 1,
匹配除换行符以外的任意单个字符 2 可以匹配 12, 1a2
[] 字符集,匹配方括号内的任意一个字符 [135] 可以匹配 1, 3, 5
分组,将括号内的表达式作为一个整体 (\d+) 将连续的数字视为一个组
^ 匹配字符串的开始位置 ^\d+ 匹配字符串开头的数字
匹配字符串的结束位置 \d+$ 匹配字符串结尾的数字
\. 转义字符,匹配 字符本身 3\.14 匹配 14 而不是 314

核心工具类:

  • java.util.regex.Pattern:正则表达式的编译表示。
  • java.util.regex.Matcher:对输入字符串进行匹配操作的引擎。

实战案例:从简单到复杂,逐个击破

案例1:提取所有连续的整数

需求: 从字符串 "我有3个苹果,12个橙子,和253个香蕉。" 中提取所有整数。

分析: 我们需要找到所有由一个或多个数字组成的连续序列。\d+ 正是为此而生。

代码实现:

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ExtractIntegers {
    public static void main(String[] args) {
        String text = "我有3个苹果,12个橙子,和253个香蕉。";
        // 1. 定义正则表达式:\d+ 表示一个或多个数字
        String regex = "\\d+"; // 注意:在Java字符串中,\需要转义为\\
        // 2. 编译正则表达式
        Pattern pattern = Pattern.compile(regex);
        // 3. 创建Matcher对象
        Matcher matcher = pattern.matcher(text);
        // 4. 查找所有匹配项
        List<String> numbers = new ArrayList<>();
        while (matcher.find()) {
            // group() 方法返回匹配到的子序列
            numbers.add(matcher.group());
        }
        // 5. 输出结果
        System.out.println("原始文本: " + text);
        System.out.println("提取到的整数: " + numbers); // 输出: [3, 12, 253]
    }
}

代码解读:

  • Pattern.compile("\\d+"):将 \d+ 编译成一个模式对象。
  • pattern.matcher(text):创建一个匹配器,用于在 text 中查找模式。
  • matcher.find():尝试查找下一个匹配子序列,如果找到,返回 true
  • matcher.group():返回当前匹配到的子字符串。

案例2:提取所有浮点数(包括小数)

需求: 从字符串 "温度:23.5°C,湿度:65.78%,气压:1013.25hPa" 中提取所有浮点数。

分析: 浮点数由整数部分、小数点和可选的小数部分组成,我们可以用 \d+\.\d+ 来匹配。

  • \d+:匹配整数部分(一个或多个数字)。
  • \.:匹配小数点本身( 是特殊字符,需要转义)。
  • \d+:匹配小数部分(一个或多个数字)。

代码实现:

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ExtractDecimals {
    public static void main(String[] args) {
        String text = "温度:23.5°C,湿度:65.78%,气压:1013.25hPa";
        // \d+\.\d+ 匹配一个或多个数字,后跟一个小数点,再跟一个或多个数字
        String regex = "\\d+\\.\\d+";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);
        List<String> decimals = new ArrayList<>();
        while (matcher.find()) {
            decimals.add(matcher.group());
        }
        System.out.println("原始文本: " + text);
        System.out.println("提取到的浮点数: " + decimals); // 输出: [23.5, 65.78, 1013.25]
    }
}

案例3:提取所有数字(整数和浮点数)

需求: 合并案例1和案例2,从 "总价100元,单价25.5元,折扣0.8" 中提取所有数字。

分析: 我们需要一个能同时匹配 1005 的模式,可以这样构建: (\d+\.\d+|\d+)

  • 表示“或”逻辑。
  • \d+\.\d+:匹配浮点数。
  • \d+:匹配整数。
  • 整个表达式表示“匹配浮点数,或者匹配整数”。

代码实现:

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ExtractAllNumbers {
    public static void main(String[] args) {
        String text = "总价100元,单价25.5元,折扣0.8";
        // 匹配整数或浮点数
        String regex = "\\d+\\.\\d+|\\d+";
        // 更简洁的写法:\\d*\\.?\\d+
        // \d* 零个或多个数字
        // \.? 零个或一个小数点
        // \d+ 一个或多个数字
        // 这个模式可以匹配 "123", "123.", ".123", "123.45"
        // 但我们通常只想匹配有数字的,所以上面的第一种更精确
        String regexOptimized = "\\d+(\\.\\d+)?"; // 匹配整数或带小数部分的浮点数
        Pattern pattern = Pattern.compile(regexOptimized);
        Matcher matcher = pattern.matcher(text);
        List<String> allNumbers = new ArrayList<>();
        while (matcher.find()) {
            allNumbers.add(matcher.group());
        }
        System.out.println("原始文本: " + text);
        System.out.println("提取到的所有数字: " + allNumbers); // 输出: [100, 25.5, 0.8]
    }
}

注意: \d+(\\.\\d+)? 是一个更优且更常见的写法,它通过 创建一个分组, 表示这个分组(小数点部分)是可选的。


案例4:处理带千分位的数字(如:1,000,000)

需求:"项目预算:1,500,000.50元" 中提取 50

分析: 千分位由逗号分隔,我们需要匹配 1,500,000.50 这样的模式,并最终去除逗号,模式为 \d{1,3}(,\d{3})*(\.\d+)?

  • \d{1,3}:1到3位数字(如 1500)。
  • (,\d{3})*:零次或多次的“逗号+3位数字”组合(如 ,000,500,000)。
  • (\.\d+)?:可选的小数部分。

代码实现:

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ExtractFormattedNumber {
    public static void main(String[] args) {
        String text = "项目预算:1,500,000.50元";
        // 匹配带千分位的数字
        String regex = "\\d{1,3}(,\\d{3})*(\\.\\d+)?";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);
        List<String> formattedNumbers = new ArrayList<>();
        while (matcher.find()) {
            formattedNumbers.add(matcher.group());
        }
        System.out.println("原始文本: " + text);
        System.out.println("提取到的带千分位数字: " + formattedNumbers); // 输出: [1,500,000.50]
        // 进阶:去除逗号,转换为纯数字
        if (!formattedNumbers.isEmpty()) {
            String formattedNum = formattedNumbers.get(0);
            String pureNumber = formattedNum.replace(",", "");
            System.out.println("去除逗号后的纯数字: " + pureNumber); // 输出: 1500000.50
        }
    }
}

案例5:提取特定格式的数字(如科学计数法 1.23E-10)

需求:"引力常数 G = 6.67430E-11 (m³ kg⁻¹ s⁻²)" 中提取 67430E-11

分析: 科学计数法格式为 [数字].[数字]E[正负号][数字],模式为 \d+\.\d+E[+-]?\d+

  • \d+\.\d+:尾数部分。
  • E:指数部分的标志。
  • [+-]?:可选的正号或负号。
  • \d+:指数值。

代码实现:

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ExtractScientificNotation {
    public static void main(String[] args) {
        String text = "引力常数 G = 6.67430E-11 (m³ kg⁻¹ s⁻²)";
        // 匹配科学计数法
        String regex = "\\d+\\.\\d+E[+-]?\\d+";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);
        List<String> sciNumbers = new ArrayList<>();
        while (matcher.find()) {
            sciNumbers.add(matcher.group());
        }
        System.out.println("原始文本: " + text);
        System.out.println("提取到的科学计数法数字: " + sciNumbers); // 输出: [6.67430E-11]
    }
}

进阶技巧与性能考量

使用预编译(Pattern.compile)提升性能

如果你的正则表达式在代码中被多次使用(在一个循环或高频调用的方法中),务必预编译它。Pattern.compile() 只执行一次,后续可以直接复用 Pattern 对象,避免了重复解析正则表达式字符串的开销。

错误示范(性能差):

for (int i = 0; i < 10000; i++) {
    // 每次循环都重新编译,性能低下!
    Matcher matcher = Pattern.compile("\\d+").matcher("some text 123");
    // ...
}

正确示范(性能高):

// 在循环外编译一次
Pattern pattern = Pattern.compile("\\d+");
for (int i = 0; i < 10000; i++) {
    Matcher matcher = pattern.matcher("some text 123");
    // ...
}

使用命名分组(Java 8+)提升代码可读性

当正则表达式变得复杂时,使用数字索引(如 matcher.group(1))会降低代码可读性,Java 8 引入了命名分组,可以通过给分组起名字来访问。

语法:(?<name>pattern),访问时使用 matcher.group("name")

示例:

import java.util.regex.*;
public class NamedGroupExample {
    public static void main(String[] args) {
        String text = "订单号:ORD-2025-10086";
        // (?<orderType>\w+) 是一个命名分组,名为 orderType
        String regex = "订单号:(?<orderType>\\w+)-(?<year>\\d+)-(?<id>\\d+)";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(text);
        if (matcher.find()) {
            // 使用名字访问,比 matcher.group(1) 更清晰
            String orderType = matcher.group("orderType");
            String year = matcher.group("year");
            String id = matcher.group("id");
            System.out.println("订单类型: " + orderType); // ORD
            System.out.println("年份: " + year);       // 2025
            System.out.println("ID: " + id);           // 10086
        }
    }
}

总结与最佳实践

本文系统地介绍了使用Java正则表达式提取数字的各种场景和技巧,作为程序员,请遵循以下最佳实践:

  1. 明确需求:在写正则之前,先想清楚你要匹配的数字格式边界在哪里。
  2. 从简到繁:先用简单的模式测试,逐步增加复杂性。
  3. 善用工具:利用在线正则表达式测试工具(如 Regex101)进行调试和验证。
  4. 预编译模式:在性能敏感的场景下,务必预编译你的正则表达式。
  5. 注重可读性:对于复杂正则,使用非贪婪量词(, )、注释(,Java不支持但其他引擎支持)和命名分组来保持代码清晰。
  6. 考虑边界:思考你的模式是否会误匹配。\\d+ 会匹配 a123b 中的 123,这是否是你想要的?如果只想匹配独立的数字,可以使用 (?<!\\d)\\d+(?!\\d) (负向零宽断言)来确保前后没有其他数字字符。

掌握Java正则表达式提取数字,将极大提升你处理文本数据的效率和代码质量,希望本指南能成为你开发路上的得力助手!


(文章结束)

分享:
扫描分享到社交APP
上一篇
下一篇