2011-12-19 10 views
11

Sé que hay un millón de formas de hacerlo, pero ¿cuál es el más rápido? Esto debería incluir notación científica.La forma más rápida de verificar si se puede analizar una Cadena en Doblar en Java

NOTA: No estoy interesado en convertir el valor a Double, solo me interesa saber si es posible. es decir, private boolean isDouble(String value).

+0

AFAIK, haga una Double.parseDouble (String) en ella y arroja una excepción si no comienza con números. (Generalizando aquí). Si quiere hacer regExs y eliminar los principales caracteres no numéricos, esa es una historia diferente. – Rcunn87

+0

Bueno, AFAIK, try-catch tiende a ser bastante lento. – JHollanti

+1

Voy a utilizar el segundo Rcunn87 en la idea de expresión regular, pero asegúrese de compilarlo y almacenarlo estáticamente para que pueda volver a usarlo una y otra vez. –

Respuesta

6

Puede verificarlo utilizando la misma expresión regular que usa la clase doble. Está bien documentado aquí:

http://docs.oracle.com/javase/6/docs/api/java/lang/Double.html#valueOf%28java.lang.String%29

Aquí es la parte de código:

Para evitar llamar a este método en una cadena no válida y tener un NumberFormatException ser lanzada, la expresión regular a continuación se puede utilizar para detectar la cadena de entrada:

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 
     } 
+0

En realidad, en mi caso, la solución más rápida fue simplemente if-else a través de todo el String usando flags y whatnots. Pero eso se debe a que en mi caso el String suele ser muy, muy pequeño (como 3 o 4 caracteres). Sin embargo, como solución general, creo que es la mejor. – JHollanti

0

Creo que tratar de convertirlo en un doble y atrapar la excepción sería la forma más rápida de verificar ... Otra forma en que puedo pensar, es dividir la cadena por el período ('.') Y luego revisar que cada parte de la matriz dividida contiene solo enteros ... pero creo que la primera forma sería más rápida

+0

¿Cómo es el lanzamiento y la captura rápida? Sin mencionar malas prácticas? Y usar un punto no es seguro para la configuración regional. – JHollanti

5

Hay un práctico NumberUtils#isNumber en Apache Commons Lang. Es un poco descabellado:

Los números válidos incluyen el hexadecimal marcado con el calificador 0x, la notación científica y los números marcados con un calificador de tipo (por ejemplo, 123L).

pero supongo que podría ser más rápido que las expresiones regulares o lanzar y atrapar una excepción.

+0

¿Has mirado el código fuente de ese método? No veo por qué sería más rápido que una expresión regular: es un revoltijo de bucles, comparaciones, banderas ... probablemente lo que sucede bajo el capó con una expresión regular, pero es feo verlo. – Paul

+0

@Paul: Eché un vistazo rápido allí (lo lamento ahora ;-)) pero mientras funcione, no me importa. Tampoco sé si será más rápido que una expresión regular. Recuerde que Regex es una máquina de estado generada dinámicamente (aunque probablemente * muy * optimizada). –

0

he tratado a continuación bloque de código y parece como tirar excepción más rápido

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

Salida:

1324316024735 
1324316024737 
1324316024737 
+0

No puede confiar en hacerlo una vez para determinar un punto de referencia. Hay demasiada variación que podría suceder, y usted no sabe la resolución del reloj de la milli. – corsiKa

+0

@glowcoder Tienes muchas variaciones posibles, quizás también hardware. Acerca de milli's: ¿no es un valor largo que incluye todos los millis desde 1.1.1970? – HRgiger

+0

Lo que dijo @glowcoder: hágalo un millón de veces con un patrón precompilado y vuelva a nosotros. – Paul

0

excepciones no deben ser utilizados para el control de flujo, aunque los autores de Java hacen que sea difícil no utiliza NumberFormatException de esa manera.

La clase java.util.Scanner tiene un método para comprobar si un String se puede leer como un doble.

Debajo del capó Scanner utiliza expresiones regulares (a través de patrones precompilados) para determinar si un String se puede convertir en un número entero o en coma flotante. Los patrones se compilan en el método buildFloatAndDecimalPattern que se puede ver en GrepCode here.

Un patrón precompilado tiene la ventaja adicional de ser más rápido que el uso de un bloque try/catch.

Aquí está el método mencionado anteriormente, en el caso GrepCode desaparece un día:

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

El Apache Commons NumberUtil es en realidad bastante rápido.Supongo que es mucho más rápido que cualquier implementación de expresiones regulares .

+2

¿Puede proporcionar un punto de referencia que reemplace esta suposición con hechos concretos? – joergl

+1

También veo 'isDigits' y' isNumber' en 'org.apache.commons.lang.math.NumberUtils', pero nada para comprobar' isDouble'. Entonces, ¿qué método sugeriste usar? –

+0

isNumber comprueba todos los números (mira el documento ...) 'Los números válidos incluyen el hexadecimal marcado con el calificador 0x, la notación científica y los números marcados con un calificador de tipo (por ejemplo, 123L)' – Seega

2

uso el siguiente código para comprobar si una cadena se puede analizar a doble:

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

Soy consciente de que la salida no es exactamente el mismo que para la expresión regular en la clase doble, pero este método es mucho más rápido y el resultado es lo suficientemente bueno para mis necesidades. Estas son mis pruebas unitarias para el método.

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

¡Gracias! Implementé este método en lugar de la versión de reg exp y tuve una mejora masiva en el rendimiento. Usando un generador de perfiles de Java, puedo ver que he pasado de los 27,000 ms solo en llamadas al registro exp. Es función doble a 97ms usando el tuyo - con el mismo número de llamadas. –

Cuestiones relacionadas