Страницы

Поиск по вопросам

пятница, 27 декабря 2019 г.

Наиболее эффективный код парсинга строки в целое число

#java #соревнование


Часто приходится пАрсить строку в целое число, типа:

public static int string2Int(String s, int defaultValue) {
    int i=defaultValue;
    try {
        i=Integer.parseInt(s);
    }
    catch(Exception ex) {
    }
    return i;
}


Код хорош всем, за исключением, того, что Exception, как известно, дорогой процесс.

Так вот вопрос: кто предложит наиболее эффективный (в смысле ресурсов и скорости)
способ парсинга строки в int? Строка содержит число в десятичном исчислении (никаких
hex/bin/octal) - возможно и отрицательное. Если строка содержит дробное число, то дробная
часть откидывается. 

Условия: метод принимает 2 параметра: строка и значение по умолчанию - никаких Exception
не выкидывается. При ошибке парсинга возвращается значение по умолчанию.

Update

В итоге есть 3 способа, условно классический (в самом вопросе), метод прямого парсинга
в инкарнации @АртемКоновалов и удаленный способ через Apache Commons предложенный @LiashenkoV.

Я составил небольшой тест, который генерирует 50 тыс. случайных строк, 1/3 строка
типа int, 1/3 строка double, 1/3 строка портится внесением случайных символов. Получились
следующие цифры:

ClassicParser - 300 мс.
ArtemParser - 100 мс.
ApacheParser - 200 мс.

    


Ответы

Ответ 1



Получилось такое решение: public static long getNumber(String value, long defaultValue) { if (value == null) return defaultValue; value = value.trim(); if (value.length() == 0) return defaultValue; final char[] chars = value.toCharArray(); final char delimiter = DecimalFormatSymbols.getInstance().getDecimalSeparator(); final boolean isNegative; if (chars[0] == '-' || chars[0] == '+') { if (chars.length == 1 || chars[1] == delimiter) return defaultValue; else isNegative = chars[0] == '-'; } else if (isDigit(chars[0])) isNegative = false; else return defaultValue; final int radix = 10; boolean isFloat = false; long result = 0; for (int i = isDigit(chars[0]) ? 0 : 1; i < chars.length; i++) { char currentSymbol = chars[i]; if (currentSymbol == delimiter) if (isFloat) return defaultValue; else isFloat = true; if (currentSymbol != delimiter && !isDigit(currentSymbol)) return defaultValue; if (!isFloat) { long intermediateResult = result * radix + parse(currentSymbol); if (isOverflow(result, intermediateResult)) return defaultValue; else result = intermediateResult; } } return isNegative ? -result : result; } private static boolean isOverflow(long prev, long current) { return prev != 0 && Math.signum(prev) != Math.signum(current); } private static boolean isDigit(char symbol) { return '0' <= symbol && symbol <= '9'; } private static int parse(char symbol) { return symbol - '0'; } Тест: @Test public void test() { final int defaultValue = 10; assertEquals(0, getNumber("0", 10)); assertEquals(1, getNumber("1", 10)); assertEquals(-1, getNumber("-1", 10)); assertEquals(420, getNumber("0420", defaultValue)); assertEquals(142, getNumber("142", defaultValue)); assertEquals(-72, getNumber("-72", defaultValue)); assertEquals(19, getNumber("+19", defaultValue)); assertEquals(-19, getNumber("-19.11", defaultValue)); assertEquals(19, getNumber("+19.16", defaultValue)); assertEquals(defaultValue, getNumber("+19+.16", defaultValue)); assertEquals(defaultValue, getNumber("+1.9+16", defaultValue)); assertEquals(defaultValue, getNumber("1.1.16", defaultValue)); assertEquals(defaultValue, getNumber("+.", defaultValue)); assertEquals(defaultValue, getNumber(".", defaultValue)); assertEquals(defaultValue, getNumber("+.0", defaultValue)); assertEquals(defaultValue, getNumber(Long.MAX_VALUE + "0", 10)); assertEquals(defaultValue, getNumber(Long.MIN_VALUE + "0", 10)); }

Комментариев нет:

Отправить комментарий