检查字符串是否可以解析为Long而不使用try-catch?

时间:2010-04-01 20:53:30

标签: java string parsing try-catch

如果string无法解析为long,则

Long.parseLong("string")会抛出错误。 有没有办法比使用try-catch更快地验证字符串? 感谢

14 个答案:

答案 0 :(得分:47)

你可以创建相当复杂的正则表达式,但这不值得。在这里使用例外是绝对正常的。

这是很自然的特殊情况:你假设字符串中有一个整数,但确实还有其他东西。应该抛出异常并妥善处理。

如果查看parseLong代码,您会看到有许多不同的验证和操作。如果你想在解析它之前做所有这些事情会降低性能(如果我们谈论解析数百万个数字,因为否则无关紧要)。所以,你唯一可以做的事情如果你真的需要通过避免异常来提高性能是:将parseLong实现复制到你自己的函数并返回NaN而不是在所有相应的情况下抛出异常。

答案 1 :(得分:27)

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

答案 2 :(得分:10)

您可以执行类似

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

使用正则表达式 - 检查String s是否已满数字。 但是你有什么收获的呢?另一个条件?

答案 3 :(得分:5)

您可以使用java.util.Scanner

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

这也是范围检查等。当然会说"99 bottles of beer" hasNextLong(),所以如果你想确保只有有一个long,你就必须做额外的检查。

答案 4 :(得分:5)

这是一个有效的问题,因为有时您需要推断字符串中表示的数据类型。例如,您可能需要将大型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 :(得分:5)

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,".")){ ...

答案 6 :(得分:3)

这种情况对于您拥有输入字段且不确定该字符串是否为有效数字的表单和程序是常见的。因此,如果您了解try / catch的工作方式与尝试自己编写函数相比,那么将try / catch与java函数一起使用是最好的办法。为了在.NET虚拟机中设置try catch块,没有任何开销指令,它在Java中可能是相同的。如果在try关键字上使用了指令,则这些指令将是最小的,并且大部分指令将在catch部分使用,并且只有在数量无效的情况下才会发生。

因此虽然“似乎”似乎你可以自己编写一个更快的函数,但你必须比Java编译器更好地优化它,以便击败你已经使用的try / catch机制,以及更优化函数的好处因为数字解析非常通用,所以它将是非常小的。

如果你使用你的编译器和你已经描述的java catch机制运行时序测试,你可能不会注意到任何上面的边际减速,而且边际我的意思是它应该几乎没有。

获取java语言规范以更多地理解异常,你会发现在这种情况下使用这种技术是完全可以接受的,因为它包含了一个相当大的复杂函数。在CPU中为try部分添加那些额外的指令并不会是一件大事。

答案 7 :(得分:2)

我认为这是检查String是否为有效long值的唯一方法。但是你可以自己实现一种方法来实现这一点,并考虑到最大的长期价值。

答案 8 :(得分:2)

解析 Long.parseLong 更快的方法。如果您想查看优化的方法示例,那么您应该查看parseLong:)

您真的需要考虑非ASCII的“数字”吗?

你真的需要让几个方法调用传递一个基数甚至很难你可能解析基数10?

:)

使用正则表达式不是要走的路:很难确定你的数字是否太长了很长时间:你如何使用正则表达式来确定9223372036854775807可以被解析为长但是9223372036854775907不能?

那就是说,一个非常快速的长解析方法的答案是一个状态机,无论你是想测试它是否可解析或解析它。简单来说,它不是一个接受复杂正则表达式的通用状态机,而是一个硬编码的。 - / p>

我可以写一个解析long的方法,另一个方法确定是否可以解析一个完全优于 Long.parseLong()的long。

现在你想要什么?状态测试方法?在这种情况下,如果您想避免计算两倍的长度,则可能不需要状态测试方法。

只需将您的电话包裹在try / catch中即可。

如果你真的想要比默认的Long.parseLong更快的东西,写一个 为你的问题量身定制的那个:如果你的基数为10,则为10,不是检查ASCII之外的数字(因为你可能对日语的itchi-ni-yon-go等不感兴趣。)

答案 9 :(得分:2)

希望这有助于积极的价值观。我使用此方法一次验证数据库主键。

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

答案 10 :(得分:2)

尝试使用此正则表达式:

^(-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_等。此正则表达式不会验证这些值。但是如果这个正则表达式对你来说还不够,你可以为它添加额外的限制。

答案 11 :(得分:0)

您可以尝试使用正则表达式来检查字符串的形式,然后再尝试解析它吗?

答案 12 :(得分:0)

Guava Longs.tryParse("string")返回null,而不是在解析失败时引发异常。但是此方法现在为marked as Beta

答案 13 :(得分:0)

一个简单的验证整数的方法可以实现:

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

它不能处理八进制或0x前缀,但这很少是必需的。

为了提高速度,“。match”表达式易于循环编写。