在Java中,检查一个字符串是否表示一个整数的最佳方法是什么?

252

我通常使用以下习语来检查字符串是否可以转换为整数。

public boolean isInteger( String input ) {
    try {
        Integer.parseInt( input );
        return true;
    }
    catch( Exception e ) {
        return false;
    }
}

只是我还是这个方法看起来有点差劲?有更好的方式吗?


请查看我的回答(基于CodingWithSpike早期回答的基准测试),了解我为什么改变了立场并接受了Jonas Klemming的答案。我认为大多数人将使用这个原始代码,因为它更快速实现和更易于维护,但是当提供非整数数据时,它会慢上数个数量级。

40个回答

194

如果您不关心潜在的溢出问题,那么此函数的性能将比使用 Integer.parseInt() 快20-30倍。

public static boolean isInteger(String str) {
    if (str == null) {
        return false;
    }
    int length = str.length();
    if (length == 0) {
        return false;
    }
    int i = 0;
    if (str.charAt(0) == '-') {
        if (length == 1) {
            return false;
        }
        i = 1;
    }
    for (; i < length; i++) {
        char c = str.charAt(i);
        if (c < '0' || c > '9') {
            return false;
        }
    }
    return true;
}

52
(c <= '/' || c >= ':') 看起来有点奇怪,我会使用 (c < '0' || c > '9')... 在Java中,<= 和 >= 运算符更快吗? - Anonymous
3
为什么不使用正则表达式?返回值为str.matches("^-?\d+$")是否与上面的代码等同? - Maglob
15
为了性能,我会使用这种方法或者之前问题中不使用正则表达式的原始方法。为了实现速度和易于维护,我会使用原始方法。正则表达式解决方案并没有什么优势。 - Bill the Lizard
4
我担心溢出问题,但这种方法可以适用于 BigInts(大整数),而且仍然比其他方法快得多。如果有人想知道为什么我会花这么多精力来解决这样一个简单的问题,那是因为我正在创建一个帮助解决欧拉计划问题的库。 - Bill the Lizard
2
如果你担心能否将字符串解析为int或long类型,你还需要检查该字符串所表示的整数是否适合这些数据类型。 - Jonas K
显示剩余10条评论

68

你已经拥有它,但你只需要捕获NumberFormatException异常。


9
捕获比你需要的异常更多被认为是不良习惯。 - Chris
你说得对。NFE是唯一可以抛出的异常,但这仍然是一个不好的习惯。 - Bill the Lizard
我认为如果输入为空,可能会抛出NPE异常,因此您的方法应该明确地处理它,无论您想采取哪种方式。 - Dov Wasserman
@Dov:你说得对,NPE和NFE都应该被明确地捕获。 - Bill the Lizard
在我看来,这些评论的顺序实际上为捕获java.lang.Exception(或Throwable)提出了很好的案例,证明错过其中一个太容易了。 - Reto Höhener
踩一下是因为捕获异常与编写高效代码的目的相反 :P - rubdottocom

44

我做了一个快速的基准测试。除非您开始在多个方法中弹出回退并且JVM必须执行大量工作以使执行堆栈到位,否则异常实际上并不那么昂贵。如果保持在同一个方法中,它们不是糟糕的表现者。

 public void RunTests()
 {
     String str = "1234567890";

     long startTime = System.currentTimeMillis();
     for(int i = 0; i < 100000; i++)
         IsInt_ByException(str);
     long endTime = System.currentTimeMillis();
     System.out.print("ByException: ");
     System.out.println(endTime - startTime);

     startTime = System.currentTimeMillis();
     for(int i = 0; i < 100000; i++)
         IsInt_ByRegex(str);
     endTime = System.currentTimeMillis();
     System.out.print("ByRegex: ");
     System.out.println(endTime - startTime);

     startTime = System.currentTimeMillis();
     for(int i = 0; i < 100000; i++)
         IsInt_ByJonas(str);
     endTime = System.currentTimeMillis();
     System.out.print("ByJonas: ");
     System.out.println(endTime - startTime);
 }

 private boolean IsInt_ByException(String str)
 {
     try
     {
         Integer.parseInt(str);
         return true;
     }
     catch(NumberFormatException nfe)
     {
         return false;
     }
 }

 private boolean IsInt_ByRegex(String str)
 {
     return str.matches("^-?\\d+$");
 }

 public boolean IsInt_ByJonas(String str)
 {
     if (str == null) {
             return false;
     }
     int length = str.length();
     if (length == 0) {
             return false;
     }
     int i = 0;
     if (str.charAt(0) == '-') {
             if (length == 1) {
                     return false;
             }
             i = 1;
     }
     for (; i < length; i++) {
             char c = str.charAt(i);
             if (c <= '/' || c >= ':') {
                     return false;
             }
     }
     return true;
 }

输出:

 

通过异常: 31

   

通过正则表达式: 453 (注意:每次重新编译模式)

   

通过Jonas: 16

我确实同意Jonas K的解决方案也是最健壮的。看起来他赢了 :)


14
非常好的想法,对所有三种方法进行基准测试。为了公平起见Regex和Jonas方法,您应该使用非整数字符串进行测试,因为那是Integer.parseInt方法真正会变得很慢的地方。 - Bill the Lizard
4
抱歉,但这个正则表达式测试不太好。(1)由于在“matches”中整个字符串必须与正则表达式匹配,因此您不需要使正则表达式引擎第二次检查“^”和“$”。(2)每次“str.matches”都必须创建自己的“Pattern”,这很费资源。出于性能原因,我们应该在此方法外部只创建一次这样的模式,并在内部使用它。(3)我们还可以创建一个匹配器对象,并使用其“reset(CharSequence)”传递用户数据并返回其“matches()”结果。 - Pshemo
像这样的编程代码 private final Matcher m = Pattern.compile("-?\\d+").matcher(""); private boolean byRegex(String str) { return m.reset(str).matches(); } 应该具有更好的性能。 - Pshemo
@Pshemo Integer.valueOf(" 1") 和 Integer.valueOf("1 ") 都会抛出异常,因此检查 ^ 和 $ 似乎是合理的。 - cquezel
1
@cquezel 是的,但这并不是必需的,因为“matches”隐式地添加了“^”和“$”。看一下“123”的结果。匹配(“\d +”)和“123”的结果。匹配(“\d +”)。你会看到“false”和“true”。返回“false”,因为字符串以空格开头,这会防止它被正则表达式完全匹配。 - Pshemo
“异常其实并不那么昂贵” - 这只是因为您测试的值是整数,所以异常从未被抛出。如果在非数字值上进行测试,则带有异常的版本将比正则表达式版本慢约两倍。 - Ancient Behemoth

41
org.apache.commons.lang.StringUtils.isNumeric 

虽然 Java 的标准库确实缺少这样的实用函数

我认为 Apache Commons 是每个 Java 程序员必备的工具集

可惜它还没有被移植到 Java5


1
这唯一的问题是溢出:S 我还是给你+1提到commons-lang :) - javamonkey79
2
另一个问题是负数,但我也加1,因为在我看来,这种方法最接近一个好的解决方案。 - sandris

37

鉴于仍有人可能会在基准测试后对正则表达式持有偏见,因此我将提供一个使用编译版本的更新版本基准测试。与之前的基准测试相反,此次测试显示正则表达式解决方案实际上具有一致良好的性能。

从Bill the Lizard复制并使用编译版本进行了更新:

private final Pattern pattern = Pattern.compile("^-?\\d+$");

public void runTests() {
    String big_int = "1234567890";
    String non_int = "1234XY7890";

    long startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
            IsInt_ByException(big_int);
    long endTime = System.currentTimeMillis();
    System.out.print("ByException - integer data: ");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
            IsInt_ByException(non_int);
    endTime = System.currentTimeMillis();
    System.out.print("ByException - non-integer data: ");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
            IsInt_ByRegex(big_int);
    endTime = System.currentTimeMillis();
    System.out.print("\nByRegex - integer data: ");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
            IsInt_ByRegex(non_int);
    endTime = System.currentTimeMillis();
    System.out.print("ByRegex - non-integer data: ");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for (int i = 0; i < 100000; i++)
            IsInt_ByCompiledRegex(big_int);
    endTime = System.currentTimeMillis();
    System.out.print("\nByCompiledRegex - integer data: ");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for (int i = 0; i < 100000; i++)
            IsInt_ByCompiledRegex(non_int);
    endTime = System.currentTimeMillis();
    System.out.print("ByCompiledRegex - non-integer data: ");
    System.out.println(endTime - startTime);


    startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
            IsInt_ByJonas(big_int);
    endTime = System.currentTimeMillis();
    System.out.print("\nByJonas - integer data: ");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
            IsInt_ByJonas(non_int);
    endTime = System.currentTimeMillis();
    System.out.print("ByJonas - non-integer data: ");
    System.out.println(endTime - startTime);
}

private boolean IsInt_ByException(String str)
{
    try
    {
        Integer.parseInt(str);
        return true;
    }
    catch(NumberFormatException nfe)
    {
        return false;
    }
}

private boolean IsInt_ByRegex(String str)
{
    return str.matches("^-?\\d+$");
}

private boolean IsInt_ByCompiledRegex(String str) {
    return pattern.matcher(str).find();
}

public boolean IsInt_ByJonas(String str)
{
    if (str == null) {
            return false;
    }
    int length = str.length();
    if (length == 0) {
            return false;
    }
    int i = 0;
    if (str.charAt(0) == '-') {
            if (length == 1) {
                    return false;
            }
            i = 1;
    }
    for (; i < length; i++) {
            char c = str.charAt(i);
            if (c <= '/' || c >= ':') {
                    return false;
            }
    }
    return true;
}

结果:

ByException - integer data: 45
ByException - non-integer data: 465

ByRegex - integer data: 272
ByRegex - non-integer data: 131

ByCompiledRegex - integer data: 45
ByCompiledRegex - non-integer data: 26

ByJonas - integer data: 8
ByJonas - non-integer data: 2

1
ByCompiledRegex 时间需要包括编译正则表达式在其时间测量中。 - Martin
2
@MartinCarney 我进行了修改并对模式编译进行了基准测试。很明显我的CPU/JIT更快,但如果我插值回来,编译时间为336 - tedder42
2
要明确的是,336(毫秒)是在进行100k次模式编译时发生的,就像所有其他行一样。这意味着它只会执行一次,所以它的时间基本上为零。 - tedder42
感谢您对编译正则表达式时间的纠正。 - LarsH
也许 "^[+-]?\\d+$" 会更好。 - Adam

24

这部分取决于您对“可以转换为整数”的定义。

如果您的意思是“在Java中可以转换为int”,那么Jonas的答案已经很不错了,但还没有完成全部工作。例如,它会通过999999999999999999999999999999。最后,我会在方法末尾添加普通的try / catch调用,以处理Java无法处理的情况。

逐个字符进行检查将有效地拒绝“根本不是整数”的情况,从而将“它是整数但Java无法处理”这种情况留给较慢的异常路由来捕获。您也可以手动完成这一部分,但这会变得更加复杂。


20

关于正则表达式,只有一点评论。这里提供的每个示例都是错误的!如果您想使用正则表达式,请不要忘记编译模式需要很长时间。这样写:

只是给正则表达式留言一句话。这里提供的所有示例都是错误的!如果您想使用正则表达式,请别忘了编译模式需要花费大量时间。这样做:

str.matches("^-?\\d+$")

还有这个:

Pattern.matches("-?\\d+", input);

在每个方法调用中都会引起模式的编译。要正确使用它,请遵循以下步骤:

import java.util.regex.Pattern;

/**
 * @author Rastislav Komara
 */
public class NaturalNumberChecker {
    public static final Pattern PATTERN = Pattern.compile("^\\d+$");

    boolean isNaturalNumber(CharSequence input) {
        return input != null && PATTERN.matcher(input).matches();
    }
}

5
你可以通过提前创建Matcher对象并使用其reset()方法将其应用于输入来挤出更多的性能。 - Alan Moore

16

有番石榴版本:

import com.google.common.primitives.Ints;

Integer intValue = Ints.tryParse(stringValue);

如果解析字符串失败,它会返回 null 而不是抛出异常。


4
在我看来,最好的答案是使用经过充分测试的库而不是自己编写解决方案。(此外,请参见这里的讨论。) - Olivier Cailloux
return Ints.tryParse(stringValue) != null; 的布尔结果等同于 OP 的 isInteger 方法。 - M. Justin

12

我复制了rally25rs的代码,并添加了一些非整数数据的测试。结果无可否认地支持Jonas Klemming发布的方法。当您拥有整数数据时,我最初发布的异常方法的结果非常好,但当您没有整数数据时,它们是最差的,而正则表达式解决方案(我打赌很多人都在使用)的结果一直很糟糕。请参见Felipe's answer中编译的正则表达式示例,速度要快得多。

public void runTests()
{
    String big_int = "1234567890";
    String non_int = "1234XY7890";

    long startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
        IsInt_ByException(big_int);
    long endTime = System.currentTimeMillis();
    System.out.print("ByException - integer data: ");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
        IsInt_ByException(non_int);
    endTime = System.currentTimeMillis();
    System.out.print("ByException - non-integer data: ");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
        IsInt_ByRegex(big_int);
    endTime = System.currentTimeMillis();
    System.out.print("\nByRegex - integer data: ");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
        IsInt_ByRegex(non_int);
    endTime = System.currentTimeMillis();
    System.out.print("ByRegex - non-integer data: ");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
        IsInt_ByJonas(big_int);
    endTime = System.currentTimeMillis();
    System.out.print("\nByJonas - integer data: ");
    System.out.println(endTime - startTime);

    startTime = System.currentTimeMillis();
    for(int i = 0; i < 100000; i++)
        IsInt_ByJonas(non_int);
    endTime = System.currentTimeMillis();
    System.out.print("ByJonas - non-integer data: ");
    System.out.println(endTime - startTime);
}

private boolean IsInt_ByException(String str)
{
    try
    {
        Integer.parseInt(str);
        return true;
    }
    catch(NumberFormatException nfe)
    {
        return false;
    }
}

private boolean IsInt_ByRegex(String str)
{
    return str.matches("^-?\\d+$");
}

public boolean IsInt_ByJonas(String str)
{
    if (str == null) {
            return false;
    }
    int length = str.length();
    if (length == 0) {
            return false;
    }
    int i = 0;
    if (str.charAt(0) == '-') {
            if (length == 1) {
                    return false;
            }
            i = 1;
    }
    for (; i < length; i++) {
            char c = str.charAt(i);
            if (c <= '/' || c >= ':') {
                    return false;
            }
    }
    return true;
}

结果:

ByException - integer data: 47
ByException - non-integer data: 547

ByRegex - integer data: 390
ByRegex - non-integer data: 313

ByJonas - integer data: 0
ByJonas - non-integer data: 16

6
你可以使用字符串类的matches方法。[0-9]代表它可以是所有值,+表示至少必须有一个字符长度,而*表示可以是零个或多个字符长度。
boolean isNumeric = yourString.matches("[0-9]+"); // 1 or more characters long, numbers only
boolean isNumeric = yourString.matches("[0-9]*"); // 0 or more characters long, numbers only

1
请注意,这不符合有效整数的标准(例如"+10"或"-10")。 - Tim Wintle

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接