Проверьте, можно ли перевести строку в Long без try-catch? - PullRequest
59 голосов
/ 02 апреля 2010

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

Ответы [ 14 ]

47 голосов
/ 02 апреля 2010

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

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

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

27 голосов
/ 02 апреля 2010

Из общего ресурса 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;
}
10 голосов
/ 02 апреля 2010

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

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

Использование регулярного выражения - чтобы проверить, полна ли строка s. Но что ты собираешься получить? другое условие if?

6 голосов
/ 19 июля 2014

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

Следующий код обрабатывает только десятичные 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 голосов
/ 10 мая 2017

org.apache.commons.lang3.math.NumberUtils.isParsable (yourString) определит, может ли строка быть проанализирована одним из следующих типов: Integer.parseInt (String), Long.parseLong (String), Float.parseFloat (String ) или Double.parseDouble (String)

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

if (NumberUtils.isParsable(yourString) && !StringUtils.contains(yourString,".")){ ...
5 голосов
/ 02 апреля 2010

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

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

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

3 голосов
/ 19 сентября 2011

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

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

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

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

2 голосов
/ 13 декабря 2016

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

^(-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]))$

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

2 голосов
/ 18 декабря 2014

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

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 голосов
/ 02 апреля 2010

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

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

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

:)

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

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

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

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

Просто оберните ваш звонок в попытку / поймать.

и , если вы действительно хотите что-то быстрее, чем Long.parseLong по умолчанию, напишите такое, что с учетом вашей проблемы: база 10, если вы база 10, не проверяя цифры за пределами ASCII (потому что вы, вероятно, не интересуетесь японским «итчи-ни-йон-го» и т.д.).

...