【问题标题】:Fastest way to check if a String can be parsed to Double in Java检查字符串是否可以在 Java 中解析为 Double 的最快方法
【发布时间】:2017-02-10 23:10:01
【问题描述】:

我知道有上百万种方法可以做到这一点,但最快的方法是什么?这应该包括科学记数法。

注意:我对将值转换为 Double 不感兴趣,我只想知道它是否可能。即private boolean isDouble(String value)

【问题讨论】:

  • AFAIK,对其执行 Double.parseDouble(String) ,如果它不以数字开头,则会引发异常。 (这里概括)。如果你想做正则表达式并去掉前导的非数字字符,那就另当别论了。
  • 好吧,AFAIK,try-catch 往往相当慢。
  • 我将在 Rcunn87 上讨论正则表达式的想法,但请确保您对其进行编译并静态存储它,以便您可以一次又一次地重复使用它。
  • @JHollanti 当然是,我想知道这里是否有人在考虑“开发人员时间”而不是 CPU 时间。
  • @JHollanti 相当慢也足够快。

标签: java string double


【解决方案1】:

您可以使用 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 个字符)。不过,作为一般解决方案,我认为这是最好的。
【解决方案2】:

Apache Commons Lang 中有一个方便的NumberUtils#isNumber。这有点牵强:

有效数字包括标有 0x 限定符的十六进制数、科学记数法和标有类型限定符的数字(例如 123L)。

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

【讨论】:

  • 你看过那个方法的源代码吗?我不明白为什么它会比正则表达式更快 - 它是循环、比较、标志的混乱......可能是正则表达式的底层发生了什么,但看起来肯定很难看。
  • @Paul:我在那里快速浏览了一下(我现在后悔了 ;-))但只要它有效,我不在乎。我也不知道它是否会比正则表达式更快。请记住,正则表达式是动态生成的状态机(尽管可能非常优化)。
【解决方案3】:

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

【讨论】:

  • 您能否提供一个基准,用确凿的事实取代这种猜测?
  • 我在org.apache.commons.lang.math.NumberUtils 中也看到了isDigitsisNumber,但没有什么可以检查isDouble。那么你建议使用什么方法呢?
  • isNumber 检查所有数字(查看文档...)Valid numbers include hexadecimal marked with the 0x qualifier, scientific notation and numbers marked with a type qualifier (e.g. 123L)
  • 如果您只需要双精度数,您不希望验证器对所有其他类型的数字返回 true。
【解决方案4】:

我使用下面的代码来检查一个字符串是否可以被解析为双精度:

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();
}

【讨论】:

  • 谢谢!我已经实现了这个方法而不是 reg exp 版本,并且性能有了很大的提升。使用java分析器我可以看到我已经从调用reg exp isDouble函数的27,000ms到使用你的97ms - 调用次数相同。
【解决方案5】:

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

【讨论】:

  • 抛接速度怎么样?更不用说坏习惯了?并且使用句点不是区域安全的。
【解决方案6】:

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

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

【讨论】:

  • 你不能依赖做一次来确定基准。可能会发生太多变化,而您不知道毫时钟的分辨率。
  • @glowcoder 你说得对,可能的变化太多了,也可能是硬件。关于毫秒:它不是一个长值,包括自 1.1.1970 以来的所有毫秒?
  • @glowcoder 所说的 - 使用预编译模式执行一百万次,然后回复我们。
  • 尝试使用System.nanoTime() 而不是currentTimeMillis()
  • 是的,在 Java 中它是从纪元开始的毫秒。但这不是我所说的分辨率。考虑以下问题:ideone.com/KOOP3注意时间是如何增加 1 的?现在复制该代码并在您的机器上运行它。在我的身上,它们每滴答声上升 15-16 倍。
【解决方案7】:

不应将异常用于流控制,尽管 Java 的作者很难不以这种方式使用 NumberFormatException

java.util.Scanner 类有一个方法 hasNextDouble 来检查 String 是否可以读取为双精度。

在后台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);
}

【讨论】:

    猜你喜欢
    • 2013-05-26
    • 2011-08-27
    • 2019-06-29
    • 1970-01-01
    • 2021-10-11
    • 2018-05-04
    • 1970-01-01
    相关资源
    最近更新 更多