web-dev-qa-db-fra.com

Vérifier si une chaîne est analysable en Long sans try-catch?

Long.parseLong("string") renvoie une erreur si string n'est pas analysable en long . Existe-t-il un moyen de valider la chaîne plus rapidement que d'utiliser try-catch? Merci

58
Serg

Vous pouvez créer une expression régulière assez complexe, mais cela ne vaut pas la peine. L'utilisation d'exceptions ici est absolument normale.

C'est une situation exceptionnelle: vous supposez qu'il y a un entier dans la chaîne mais en réalité il y a autre chose. Les exceptions doivent être levées et traitées correctement.

Si vous examinez le code parseLong, vous constaterez qu'il existe de nombreuses vérifications et opérations différentes. Si vous voulez faire tout ce travail avant de l’analyser, cela diminuera les performances (s’il est question d’analyser des millions de nombres, sinon cela n’a aucune importance). Ainsi, la seule chose que vous puissiez faire si vous en avez vraiment besoin pour améliorer les performances en évitant les exceptions est la suivante: copiez la mise en œuvre parseLong dans votre propre fonction et retournez NaN au lieu de lancer des exceptions dans tous les cas correspondants. 

46
Roman

De commons-lang StringUtils:

public static boolean isNumeric(String str) {
    if (str == null) {
        return false;
    }
    int sz = str.length();
    for (int i = 0; i < sz; i++) {
        if (Character.isDigit(str.charAt(i)) == false) {
            return false;
        }
    }
    return true;
}
26
lexicore

Vous pourriez faire quelque chose comme

if(s.matches("\\d*")){
}

Utilisation d’expressions régulières - pour vérifier si String s est plein de chiffres . Mais qu’avez-vous à gagner? un autre si condition?

10
ring bearer

Vous pouvez utiliser Java.util.Scanner

Scanner sc = new Scanner(s);
if (sc.hasNextLong()) {
   long num = sc.nextLong();
}

Cela va de vérification etc, aussi. Bien sûr, cela indiquera que "99 bottles of beer"hasNextLong(), de sorte que si vous voulez vous assurer qu'il seulement a une long, vous devrez effectuer des vérifications supplémentaires.

5

Cette question est valide car il est parfois nécessaire de déduire le type de données représenté dans une chaîne. Par exemple, vous devrez peut-être importer un fichier CSV volumineux dans une base de données et représenter les types de données avec précision. Dans de tels cas, appeler Long.parseLong et intercepter une exception peut être trop lent.

Le code suivant ne gère que les décimales ASCII:

public class LongParser {
    // Since tryParseLong represents the value as negative during processing, we
    // counter-intuitively want to keep the sign if the result is negative and
    // negate it if it is positive.
    private static final int MULTIPLIER_FOR_NEGATIVE_RESULT = 1;
    private static final int MULTIPLIER_FOR_POSITIVE_RESULT = -1;

    private static final int FIRST_CHARACTER_POSITION = 0;
    private static final int SECOND_CHARACTER_POSITION = 1;
    private static final char NEGATIVE_SIGN_CHARACTER = '-';
    private static final char POSITIVE_SIGN_CHARACTER = '+';
    private static final int DIGIT_MAX_VALUE = 9;
    private static final int DIGIT_MIN_VALUE = 0;
    private static final char ZERO_CHARACTER = '0';
    private static final int RADIX = 10;

    /**
     * Parses a string representation of a long significantly faster than
     * <code>Long.ParseLong</code>, and avoids the noteworthy overhead of
     * throwing an exception on failure. Based on the parseInt code from
     * http://nadeausoftware.com/articles/2009/08/Java_tip_how_parse_integers_quickly
     *
     * @param stringToParse
     *            The string to try to parse as a <code>long</code>.
     *
     * @return the boxed <code>long</code> value if the string was a valid
     *         representation of a long; otherwise <code>null</code>.
     */
    public static Long tryParseLong(final String stringToParse) {
        if (stringToParse == null || stringToParse.isEmpty()) {
            return null;
        }

        final int inputStringLength = stringToParse.length();
        long value = 0;

        /*
         * The absolute value of Long.MIN_VALUE is greater than the absolute
         * value of Long.MAX_VALUE, so during processing we'll use a negative
         * value, then we'll multiply it by signMultiplier before returning it.
         * This allows us to avoid a conditional add/subtract inside the loop.
         */

        int signMultiplier = MULTIPLIER_FOR_POSITIVE_RESULT;

        // Get the first character.
        char firstCharacter = stringToParse.charAt(FIRST_CHARACTER_POSITION);

        if (firstCharacter == NEGATIVE_SIGN_CHARACTER) {
            // The first character is a negative sign.
            if (inputStringLength == 1) {
                // There are no digits.
                // The string is not a valid representation of a long value.
                return null;
            }

            signMultiplier = MULTIPLIER_FOR_NEGATIVE_RESULT;
        } else if (firstCharacter == POSITIVE_SIGN_CHARACTER) {
            // The first character is a positive sign.
            if (inputStringLength == 1) {
                // There are no digits.
                // The string is not a valid representation of a long value.
                return null;
            }
        } else {
            // Store the (negative) digit (although we aren't sure yet if it's
            // actually a digit).
            value = -(firstCharacter - ZERO_CHARACTER);
            if (value > DIGIT_MIN_VALUE || value < -DIGIT_MAX_VALUE) {
                // The first character is not a digit (or a negative sign).
                // The string is not a valid representation of a long value.
                return null;
            }
        }

        // Establish the "maximum" value (actually minimum since we're working
        // with negatives).
        final long rangeLimit = (signMultiplier == MULTIPLIER_FOR_POSITIVE_RESULT)
            ? -Long.MAX_VALUE
            : Long.MIN_VALUE;

        // Capture the maximum value that we can multiply by the radix without
        // overflowing.
        final long maxLongNegatedPriorToMultiplyingByRadix = rangeLimit / RADIX;

        for (int currentCharacterPosition = SECOND_CHARACTER_POSITION;
            currentCharacterPosition < inputStringLength;
            currentCharacterPosition++) {
            // Get the current digit (although we aren't sure yet if it's
            // actually a digit).
            long digit = stringToParse.charAt(currentCharacterPosition)
                    - ZERO_CHARACTER;

            if (digit < DIGIT_MIN_VALUE || digit > DIGIT_MAX_VALUE) {
                // The current character is not a digit.
                // The string is not a valid representation of a long value.
                return null;
            }

            if (value < maxLongNegatedPriorToMultiplyingByRadix) {
                // The value will be out of range if we multiply by the radix.
                // The string is not a valid representation of a long value.
                return null;
            }

            // Multiply by the radix to slide all the previously parsed digits.
            value *= RADIX;

            if (value < (rangeLimit + digit)) {
                // The value would be out of range if we "added" the current
                // digit.
                return null;
            }

            // "Add" the digit to the value.
            value -= digit;
        }

        // Return the value (adjusting the sign if needed).
        return value * signMultiplier;
    }
}
5
Woody

org.Apache.commons.lang3.math.NumberUtils.isParsable (yourString) déterminera si la chaîne peut être analysée par l'un des éléments suivants: Integer.parseInt (String), Long.parseLong (String), Float.parseFloat (String) ou Double .parseDouble (String)

Puisque vous êtes intéressé par Longs, vous pourriez avoir une condition qui recherche isParsable et ne contient pas de décimale.

if (NumberUtils.isParsable(yourString) && !StringUtils.contains(yourString,".")){ ...
4
Andrew Norman

Ce cas est commun aux formulaires et aux programmes où vous avez le champ de saisie et ne savez pas si la chaîne est un nombre valide. Donc, utiliser try/catch avec votre fonction Java est la meilleure chose à faire si vous comprenez le fonctionnement de try/catch par rapport à l'écriture de la fonction vous-même. Afin de configurer le bloc catch catch dans la machine virtuelle .NET, il n'y a aucune instruction de surcharge et il en va probablement de même pour Java. Si des instructions sont utilisées au niveau du mot clé try, elles seront minimes et la majeure partie des instructions sera utilisée dans la partie catch, ce qui ne se produit que dans les rares cas où le numéro n'est pas valide.

Donc, bien que cela "semble" comme si vous pouviez écrire une fonction plus rapide vous-même, vous auriez à l'optimiser mieux que le compilateur Java pour vaincre le mécanisme try/catch que vous utilisez déjà, et vous profiterez des avantages d'une fonction plus optimisée être très minime car l'analyse des nombres est assez générique.

Si vous exécutez des tests de synchronisation avec votre compilateur et le mécanisme de capture Java que vous avez déjà décrit, vous ne remarquerez probablement aucun ralentissement au-dessus de la limite, et marginal, je veux dire, cela ne devrait presque rien.

Faites en sorte que la spécification du langage Java comprenne davantage les exceptions et vous verrez que l’utilisation d’une telle technique est parfaitement acceptable dans ce cas car elle englobe une fonction assez volumineuse et complexe. L'ajout de ces quelques instructions supplémentaires dans la CPU pour la partie try ne va pas être un gros problème.

3
Bob Holmes

Il existe des moyens beaucoup plus rapides pour analyser un long que Long.parseLong. Si vous voulez voir un exemple de méthode optimisée not, vous devriez regarder parseLong :)

Avez-vous vraiment besoin de prendre en compte les "chiffres" qui ne sont pas ASCII?

Avez-vous vraiment besoin de passer plusieurs méthodes en passant une base même si vous analysez probablement la base 10?

:)

Utiliser une expression rationnelle n'est pas la solution: il est plus difficile de déterminer si votre nombre est trop grand pour un long: comment utiliser une expression rationnelle pour déterminer que 9223372036854775807 peut être analysé comme un long mais que 9223372036854775907 ne le peut pas?

Cela dit, la réponse à une méthode d’analyse très rapide et longue est une machine à états et que vous souhaitiez vérifier si elle est analysable ou si elle doit être analysée. Simplement, ce n'est pas une machine d'état générique acceptant des expressions rationnelles complexes, mais une machine codée en dur.

Je peux vous écrire à la fois une méthode qui analyse un long et une autre qui détermine si un long peut être analysé et surperforme totalement Long.parseLong ().

Maintenant que veux-tu? Une méthode de test d'état? Dans ce cas, une méthode de test d'état peut ne pas être souhaitable si vous voulez éviter de calculer deux fois plus longtemps. 

Simplement envelopper votre appel dans un essai/attraper.

Et si vous voulez vraiment quelque chose de plus rapide que Long.parseLong par défaut, écrivez-en un qui est adapté à votre problème: base 10 si vous êtes en base 10, ne vérifiez pas les chiffres en dehors de ASCII ( parce que vous n'êtes probablement pas intéressé par le japonais Itchi-ni-yon-go, etc.).

2
SyntaxT3rr0r

J'espère que cela aide avec les valeurs positives. J'ai utilisé cette méthode une fois pour valider les clés primaires de la base de données.

private static final int MAX_LONG_STR_LEN = Long.toString(Long.MAX_VALUE).length();

public static boolean validId(final CharSequence id)
{
    //avoid null
    if (id == null)
    {
        return false;
    }

    int len = id.length();

    //avoid empty or oversize
    if (len < 1 || len > MAX_LONG_STR_LEN)
    {
        return false;
    }

    long result = 0;
    // ASCII '0' at position 48
    int digit = id.charAt(0) - 48;

    //first char cannot be '0' in my "id" case
    if (digit < 1 || digit > 9)
    {
        return false;
    }
    else
    {
        result += digit;
    }

    //start from 1, we already did the 0.
    for (int i = 1; i < len; i++)
    {
        // ASCII '0' at position 48
        digit = id.charAt(i) - 48;

        //only numbers
        if (digit < 0 || digit > 9)
        {
            return false;
        }

        result *= 10;
        result += digit;

        //if we hit 0x7fffffffffffffff
        // we are at 0x8000000000000000 + digit - 1
        // so negative
        if (result < 0)
        {
            //overflow
            return false;
        }
    }

    return true;
}
2
Hannes

Je pense que c'est le seul moyen de vérifier si une chaîne est une valeur longue valide. mais vous pouvez implémenter vous-même une méthode pour le faire, en gardant à l'esprit la plus grande valeur à long terme.

2
cd1

Vous pourriez essayer d’utiliser une expression régulière pour vérifier la forme de la chaîne avant de l’analyser?

0
Moonshield

Guava Longs.tryParse ("string") renvoie la valeur null au lieu de déclencher une exception si l'analyse échoue. Mais cette méthode est marquée comme Beta pour le moment. 

0
Anton Yuriev

Une implémentation simple pour valider un entier qui tient dans un long serait:

    public static boolean isValidLong(String str) {
        if( str==null ) return false;
        int len = str.length();
        if (str.charAt(0) == '+') {
            return str.matches("\\+\\d+") && (len < 20 || len == 20 && str.compareTo("+9223372036854775807") <= 0);
        } else if (str.charAt(0) == '-') {
            return str.matches("-\\d+") && (len < 20 || len == 20 && str.compareTo("-9223372036854775808") <= 0);
        } else {
            return str.matches("\\d+") && (len < 19 || len == 19 && str.compareTo("9223372036854775807") <= 0);
        }
    }

Il ne gère pas octal, préfixe 0x ou plus, mais c'est rarement une exigence.

Pour plus de rapidité, les expressions ".match" sont faciles à coder en boucle.

0
Florian F