Проверьте, можно ли разбирать строку на длинные без try-catch?


Long.parseLong("string") выдает ошибку, если строка не разбирается в long. Есть ли способ проверить строку быстрее, чем с помощью try-catch? Спасибо

13 56

13 ответов:

вы можете создать довольно сложное регулярное выражение, но оно того не стоит. Использование исключений здесь абсолютно нормально.

это естественная исключительная ситуация: вы предполагаете, что в строке есть целое число, но на самом деле есть что-то еще. Исключение должно быть брошено и обработано должным образом.

Если заглянуть внутрь parseLong код, вы увидите, что есть много различных проверок и операций. Если вы хотите сделать все это, прежде чем разбирать его уменьшите производительность (если речь идет о разборе миллионов чисел, потому что в противном случае это не имеет значения). Итак, единственное, что вы можете сделать если вам действительно нужны чтобы повысить производительность, избегая исключений: копировать parseLong реализация вашей собственной функции и возврат NaN вместо того, чтобы выбрасывать исключения во всех соответствующих случаях.

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

вы могли бы сделать что-то вроде

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

С помощью регулярного выражения, чтобы проверить, если строка s является полной цифр. Но что вы можете получить? другое условие если?

можно использовать java.util.Scanner

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

это диапазон слишком проверке и т. д. Конечно он скажет, что "99 bottles of beer"hasNextLong(), Так что если вы хотите, чтобы убедиться, что он только есть long вам придется сделать дополнительные проверки.

это правильный вопрос, потому что есть моменты, когда вам нужно сделать вывод, какой тип данных представляется в строке. Например, вам может потребоваться импортировать большой CSV в базу данных и точно представлять типы данных. В таких случаях звонят долго.parseLong и поймать исключение может быть слишком медленным.

следующий код обрабатывает только ASCII decimal:

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

org.апаш.палата общин.lang3.математика.Номер один.isParsable (yourString) определит, может ли строка быть проанализирована одним из: Integer.parseInt (String), Long.parseLong (строка), Float.parseFloat (строка) или двойной.parseDouble (String)

Так как вы заинтересованы в Longs вы могли бы иметь условие, которое проверяет isParsable и не содержит десятичной

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

этот случай является общим для форм и программ, где у вас есть поле ввода и не уверены, что строка является допустимым числом. Поэтому использование try / catch с вашей функцией java-это лучшее, что можно сделать, если вы понимаете, как работает try/catch по сравнению с попыткой написать функцию самостоятельно. Чтобы настроить блок try catch в виртуальной машине .NET, есть нулевые инструкции накладных расходов, и это, вероятно, то же самое в Java. Если есть инструкции, используемые в ключевом слове try, то они будут минимальный, и основная часть инструкций будет использоваться в части улова, и это происходит только в редких случаях, когда число не является допустимым.

таким образом, хотя "кажется", что вы можете написать более быструю функцию самостоятельно, вам придется оптимизировать ее лучше, чем компилятор Java, чтобы победить механизм try/catch, который вы уже используете, и преимущество более оптимизированной функции будет очень минимальным, поскольку разбор чисел является довольно общим.

Если вы запускаете тесты синхронизации с вашим компилятором и механизмом Java catch, который вы уже описали, вы, вероятно, не заметите никакого выше предельного замедления, и под предельным я имею в виду, что это должно быть почти ничего.

получите спецификацию языка java, чтобы понять исключения больше, и вы увидите, что использование такой техники в этом случае вполне приемлемо, поскольку она обертывает довольно большую и сложную функцию. Добавление этих нескольких дополнительных инструкций в CPU для части try не будет таким большим сделка.

Я думаю, что это единственный способ проверить, является ли строка допустимым длинным значением. но вы можете реализовать себе метод для этого, имея в виду самую большую долгосрочную ценность.

есть гораздо более быстрые способы parse долго не долго.parseLong. Если вы хотите увидеть пример метода, который составляет не оптимизирован, то вы должны смотреть на parseLong :)

вам действительно нужно учитывать "цифры", которые не являются ASCII?

вам действительно нужно сделать несколько методов называет проходя вокруг радикса даже жестко вы, вероятно, разбираете базу 10?

:)

использование регулярного выражения-это не путь: труднее определить, если вы номер слишком большой для долго: как вы используете регулярное выражение, чтобы определить, что 9223372036854775807 может быть проанализирован на долго, но что 9223372036854775907 не может?

тем не менее, ответ на очень быстрый метод длинного разбора и состояние машины и что независимо от того, если вы хотите проверить, если он разбирается или разобрать его. Просто это не общий конечный автомат принимая сложное регулярное выражение, но жестко закодированное.

Я могу написать вам метод, который анализирует длинный и другой, который определяет, можно ли анализировать длинный, который полностью превосходит долго.parseLong().

теперь чего ты хочешь? Метод государственного тестирования? В этом случае метод тестирования состояния может быть нежелателен, если вы хотите избежать вычисления в два раза дольше.

просто оберните свой вызов в try / catch.

и если вы действительно хочу что-то быстрее, чем по умолчанию долго.парселонг, напиши тот, что и С учетом вашей проблемы: база 10 если вы база 10, не проверяя цифры за пределами ASCII (потому что вы, вероятно, не заинтересованы в Японском itchi-ni-yon-go и т. д.).

надеюсь, что это поможет с положительными значениями. Я использовал этот метод один раз для проверки первичных ключей базы данных.

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

попробуйте использовать это регулярное выражение:

^(-9223372036854775808|0)$|^((-?)((?!0)\d{1,18}|[1-8]\d{18}|9[0-1]\d{17}|92[0-1]\d{16}|922[0-2]\d{15}|9223[0-2]\d{14}|92233[0-6]\d{13}|922337[0-1]\d{12}|92233720[0-2]\d{10}|922337203[0-5]\d{9}|9223372036[0-7]\d{8}|92233720368[0-4]\d{7}|922337203685[0-3]\d{6}|9223372036854[0-6]\d{5}|92233720368547[0-6]\d{4}|922337203685477[0-4]\d{3}|9223372036854775[0-7]\d{2}|922337203685477580[0-7]))$

Он долго проверяет все возможные номера. Но как вы знаете в Java Long может содержать дополнительные символы, такие как +,L,_ и т. д. И это регулярное выражение не проверяет эти значения. Но если этого регулярного выражения вам недостаточно, вы можете добавить дополнительные ограничения для него.

вы можете попробовать использовать регулярное выражение, чтобы проверить форму строки, прежде чем пытаться разобрать его?

гуавы тоскует.tryParse ("string") возвращает null вместо того, чтобы выдавать исключение, если разбор не удается. Но этот метод помечен как бета прямо сейчас.