在Java中检查字符串是否可以解析为Double的最快方法

12

我知道有许多种方法可以实现这个目标,但哪种方法最快?此操作应包含科学计数法。

注意:我不想将该值转换为Double类型,我只是想知道是否可能。例如:private boolean isDouble(String value)


据我所知,在Double.parseDouble(String)上操作,如果它不以数字开头,则会抛出异常。(这里是泛化的描述)。如果你想要使用正则表达式并剥离前导非数字字符,那就是另一回事了。 - Rcunn87
1
我赞同Rcunn87的正则表达式想法,但请确保编译它并静态存储,以便您可以反复使用它。 - Thomas Dignan
1
@JHollanti肯定是,我想知道这里是否有人在考虑“开发时间”而不是CPU时间。 - Thomas Dignan
是的,我不确定parseDouble()是否可以处理科学计数法。因此,在这种情况下,正则表达式是唯一适用于您的方法。或者使用其他库也可以。 - Rcunn87
@JHollanti,除非你确切知道需要多快,否则我认为Double.parseDouble已经足够快了。如果真的必须是最快的,你需要编写自己的定制解析器来精确解析你想要的内容。它可以比正则表达式快10倍,但代码量会更多,特别是如果你不是真的需要这个功能。你能说出需要多快吗?例如,需要多少纳秒? - Peter Lawrey
显示剩余6条评论
7个回答

6

Apache Commons Lang中有一个方便的NumberUtils#isNumber方法。它可以判断十六进制、科学计数法和带类型限定符(例如123L)的数字是否有效。

有效数字包括使用0x标识的十六进制,科学计数法以及带类型限定符的数字(例如123L)。

但我猜它可能比正则表达式或抛出和捕获异常更快。


你看过那个方法的源代码吗?我不明白为什么它会比正则表达式更快 - 它是一堆循环,比较,标志...可能是正则表达式底层所做的事情,但它看起来真的很丑。 - Paul
@Paul:我快速浏览了一下(现在我后悔了;-)),但只要它能工作,我就不在乎。我也不知道它是否比正则表达式更快。请记住,正则表达式是一个动态生成的状态机(虽然可能非常优化)。 - Tomasz Nurkiewicz

6
您可以使用与Double类相同的正则表达式来检查它。这里有详细的文档:http://docs.oracle.com/javase/6/docs/api/java/lang/Double.html#valueOf%28java.lang.String%29 以下是代码部分:
为了避免在无效字符串上调用此方法并引发NumberFormatException异常,可以使用下面的正则表达式对输入字符串进行筛选:
  final String Digits     = "(\\p{Digit}+)";
  final String HexDigits  = "(\\p{XDigit}+)";

        // an exponent is 'e' or 'E' followed by an optionally 
        // signed decimal integer.
        final String Exp        = "[eE][+-]?"+Digits;
        final String fpRegex    =
            ("[\\x00-\\x20]*"+  // Optional leading "whitespace"
             "[+-]?(" + // Optional sign character
             "NaN|" +           // "NaN" string
             "Infinity|" +      // "Infinity" string

             // A decimal floating-point string representing a finite positive
             // number without a leading sign has at most five basic pieces:
             // Digits . Digits ExponentPart FloatTypeSuffix
             // 
             // Since this method allows integer-only strings as input
             // in addition to strings of floating-point literals, the
             // two sub-patterns below are simplifications of the grammar
             // productions from the Java Language Specification, 2nd 
             // edition, section 3.10.2.

             // Digits ._opt Digits_opt ExponentPart_opt FloatTypeSuffix_opt
             "((("+Digits+"(\\.)?("+Digits+"?)("+Exp+")?)|"+

             // . Digits ExponentPart_opt FloatTypeSuffix_opt
             "(\\.("+Digits+")("+Exp+")?)|"+

       // Hexadecimal strings
       "((" +
        // 0[xX] HexDigits ._opt BinaryExponent FloatTypeSuffix_opt
        "(0[xX]" + HexDigits + "(\\.)?)|" +

        // 0[xX] HexDigits_opt . HexDigits BinaryExponent FloatTypeSuffix_opt
        "(0[xX]" + HexDigits + "?(\\.)" + HexDigits + ")" +

        ")[pP][+-]?" + Digits + "))" +
             "[fFdD]?))" +
             "[\\x00-\\x20]*");// Optional trailing "whitespace"

  if (Pattern.matches(fpRegex, myString))
            Double.valueOf(myString); // Will not throw NumberFormatException
        else {
            // Perform suitable alternative action
        }

实际上,在我的情况下,最快的解决方案是使用标志和其他方法通过整个字符串进行 if-else 判断。但这是因为在我的情况下,该字符串通常非常小(例如 3 或 4 个字符)。然而,作为一种通用解决方案,我认为这是最好的。 - JHollanti

2

Apache Commons NumberUtil非常快。我猜它比任何正则表达式实现都要快。


3
你能提供一个基准来用硬性事实代替这个猜测吗? - joergl
1
我也看到了org.apache.commons.lang.math.NumberUtils中的isDigitsisNumber,但没有检查isDouble的方法。那么你建议使用哪种方法呢? - David Dossot
isNumber检查所有数字(请查看文档...)有效数字包括带有0x限定符的十六进制数、科学计数法和带有类型限定符(例如123L)的数字。 - Seega
如果您只需要双精度数,您不希望您的验证器对所有其他类型的数字返回true。 - Asu

2
我使用以下代码来检查一个字符串是否可以解析为双精度浮点数:
public static boolean isDouble(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;
    }
    int integerPartSize = 0;
    int exponentPartSize = -1;
    while (i < length) {
        char c = str.charAt(i);
        if (c < '0' || c > '9') {
            if (c == '.' && integerPartSize > 0 && exponentPartSize == -1) {
                exponentPartSize = 0;
            } else {
                return false;
            }
        } else if (exponentPartSize > -1) {
            ++exponentPartSize;
        } else {
            ++integerPartSize;
        }
        ++i;
    }
    if ((str.charAt(0) == '0' && i > 1 && exponentPartSize < 1)
            || exponentPartSize == 0 || (str.charAt(length - 1) == '.')) {
        return false;
    }
    return true;
}

我知道这个方法输出的结果和Double类中的正则表达式不完全相同,但是这个方法速度更快,而且对我的需求来说结果已经足够好了。以下是我编写的针对该方法的单元测试。

@Test
public void shouldReturnTrueIfStringIsDouble() {
    assertThat(Utils.isDouble("0.0")).isTrue();
    assertThat(Utils.isDouble("0.1")).isTrue();
    assertThat(Utils.isDouble("-0.0")).isTrue();
    assertThat(Utils.isDouble("-0.1")).isTrue();
    assertThat(Utils.isDouble("1.0067890")).isTrue();
    assertThat(Utils.isDouble("0")).isTrue();
    assertThat(Utils.isDouble("1")).isTrue();
}

@Test
public void shouldReturnFalseIfStringIsNotDouble() {
    assertThat(Utils.isDouble(".01")).isFalse();
    assertThat(Utils.isDouble("0.1f")).isFalse();
    assertThat(Utils.isDouble("a")).isFalse();
    assertThat(Utils.isDouble("-")).isFalse();
    assertThat(Utils.isDouble("-1.")).isFalse();
    assertThat(Utils.isDouble("-.1")).isFalse();
    assertThat(Utils.isDouble("123.")).isFalse();
    assertThat(Utils.isDouble("1.2.3")).isFalse();
    assertThat(Utils.isDouble("1,3")).isFalse();
}

谢谢!我已经使用这种方法代替正则表达式版本,并且获得了巨大的性能提升。使用Java分析器,我可以看到我从仅在调用isDouble函数时的27,000毫秒降至使用您的方法的97毫秒 - 调用次数相同。 - Martin Holland

0

我认为尝试将其转换为double并捕获异常是最快的检查方法...另一种我能想到的方法是通过句点('.')拆分字符串,然后检查拆分数组的每个部分是否仅包含整数...但我认为第一种方法会更快。


抛出和捕获的速度如何?更不用说这是一种不好的做法了。而且使用句号并不是本地化安全的。 - JHollanti

0

我已经尝试了下面的代码块,似乎抛出异常更快了。

String a = "123f15512551";
        System.out.println(System.currentTimeMillis());
        a.matches("^\\d+\\.\\d+$");
        System.out.println(System.currentTimeMillis());

        try{
            Double.valueOf(a);
        }catch(Exception e){
            System.out.println(System.currentTimeMillis());
        }

输出:

1324316024735
1324316024737
1324316024737

你不能仅依靠一次性的操作来确定基准。可能会发生太多变化,而你也不知道毫秒时钟的分辨率。 - corsiKa
@glowcoder 你是对的,有太多可能的变化,也可能会涉及硬件问题。关于 milli's:它不是一个包括自1970年1月1日以来所有毫秒值的长整型吗? - HRgiger
@glowcoder所说的-使用预编译模式重复执行一百万次,然后回到我们这里。 - Paul
1
尝试使用System.nanoTime()代替currentTimeMillis() - Paul
1
在Java中,是从纪元开始的毫秒数。但这不是我所说的分辨率。请考虑以下内容:http://ideone.com/KOOP3 注意时间毫秒如何每次增加1?现在复制该代码并在您的计算机上运行它。在我的计算机上,它们每个滴答之间增加15-16。 - corsiKa
@glowcoder @Paul 现在清楚了:) 对于误解我感到抱歉,这将是另一个话题。同时感谢您提供的新信息! - HRgiger

0

异常不应该用于流程控制,尽管Java的作者使得不使用NumberFormatException很困难。

java.util.Scanner类有一个方法hasNextDouble,用于检查是否可以将String读取为double类型。

在底层,Scanner使用正则表达式(通过预编译模式)来确定是否可以将String转换为整数或浮点数。这些模式是在方法buildFloatAndDecimalPattern中编译的,您可以在GrepCode here中查看。

预编译模式的附加好处是比使用try/catch块更快。

如果GrepCode有一天消失了,这里引用了上面提到的方法:

private void buildFloatAndDecimalPattern() {
    // \\p{javaDigit} may not be perfect, see above
    String digit = "([0-9]|(\\p{javaDigit}))";
    String exponent = "([eE][+-]?"+digit+"+)?";
    String groupedNumeral = "("+non0Digit+digit+"?"+digit+"?("+
                            groupSeparator+digit+digit+digit+")+)";
    // Once again digit++ is used for performance, as above
    String numeral = "(("+digit+"++)|"+groupedNumeral+")";
    String decimalNumeral = "("+numeral+"|"+numeral +
        decimalSeparator + digit + "*+|"+ decimalSeparator +
        digit + "++)";
    String nonNumber = "(NaN|"+nanString+"|Infinity|"+
                           infinityString+")";
    String positiveFloat = "(" + positivePrefix + decimalNumeral +
                        positiveSuffix + exponent + ")";
    String negativeFloat = "(" + negativePrefix + decimalNumeral +
                        negativeSuffix + exponent + ")";
    String decimal = "(([-+]?" + decimalNumeral + exponent + ")|"+
        positiveFloat + "|" + negativeFloat + ")";
    String hexFloat =
        "[-+]?0[xX][0-9a-fA-F]*\\.[0-9a-fA-F]+([pP][-+]?[0-9]+)?";
    String positiveNonNumber = "(" + positivePrefix + nonNumber +
                        positiveSuffix + ")";
    String negativeNonNumber = "(" + negativePrefix + nonNumber +
                        negativeSuffix + ")";
    String signedNonNumber = "(([-+]?"+nonNumber+")|" +
                             positiveNonNumber + "|" +
                             negativeNonNumber + ")";
    floatPattern = Pattern.compile(decimal + "|" + hexFloat + "|" +
                                   signedNonNumber);
    decimalPattern = Pattern.compile(decimal);
}

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