Regex или обработка исключений?

Какой из следующих способов лучше проверить, является ли строка плавающей?

try{
 Double.parseDouble(strVal);
}catch(NumberFormatException e){
 //My Logic
}

или же

if(!strVal.matches("[-+]?\\d*\\.?\\d+")){
 //My Logic
}

С точки зрения производительности, сопровождения и читабельности?

И да, я хотел бы знать, какая из них является хорошей практикой кодирования?

7 ответов

  1. Производительность: исключения являются медленными, как и логика на основе исключений, поэтому секунда будет быстрее.
  2. Техническое обслуживание / Надежность: первый кристально чистый и будет оставаться в курсе обновлений Java Framework.

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

Личное мнение - о коде, который я видел, я бы ожидал, что большинство разработчиков будут стремиться к блокам try - catch. Метод try является в некотором смысле более читабельным и предполагает, что в большинстве случаев строка будет содержать действительное число. Но есть несколько вещей, которые вы можете рассмотреть с примерами, которые могут повлиять на ваш выбор.

  1. Как часто вы ожидаете, что строка не будет содержать правильное число?
  2. Обратите внимание, что для массовой обработки вы должны создать объект Pattern вне цикла. Это остановит код от необходимости каждый раз перекомпилировать шаблон.
  3. Как правило, вы никогда не должны использовать ожидания в качестве логического потока. Ваш try - catch указывает логику, если это не строка, где, как ваше регулярное выражение указывает логику, если это число. Так что не было очевидно, каков контекст кода.
  4. Если вы выберете технику регулярных выражений, вам все равно, вероятно, придется конвертировать в какой-то момент, так что в действительности это может быть пустой тратой усилий.
  5. И, наконец, достаточно ли важны требования к производительности приложения для проведения анализа на этом уровне. Опять же, в общем, я бы рекомендовал сделать вещи максимально простыми, чтобы они работали, а затем, если возникнут проблемы с производительностью, используйте некоторые инструменты анализа кода, чтобы найти узкие места и настроить их.

Первый из них будет работать лучше, чем регулярное выражение, когда строка соответствует двойному. Например, он очень быстро анализируется, когда распознаватель жестко запрограммирован, как это было бы с Double.parse. Также нет ничего, что поддерживало бы то, что Java определяет как Double как строка. Не говоря уже о Double.parseDouble() легче читать.

Другое решение не собирается компилироваться, поэтому первое, что нужно сделать регулярному выражению, это скомпилировать и проанализировать выражение регулярного выражения, затем запустить это выражение, затем вам нужно будет выполнить Double.parseDouble(), чтобы получить это в два раза. И это будет сделано для каждого числа, переданного ему. Вы можете оптимизировать его с помощью Pattern.compile (), но выполнение выражения будет выполняться медленнее. Особенно, когда вам нужно запустить Double.doubleParse, чтобы получить значение в double.

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

Ниже приведен тест производительности, чтобы увидеть разницу в производительности между регулярным выражением VS try catch для проверки строки является числовым.

Ниже в таблице приведены статистические данные со списком (100 КБ) с тремя точками (90%, 70%, 50%) хороших данных (значение с плавающей запятой) и оставшимися плохими данными (строки).

                      **90% - 10%   70% - 30%   50% - 50%**
**Try Catch**           87234580    122297750   143470144
**Regular Expression**  202700266   192596610   162166308

Производительность try catch лучше (если количество неверных данных не превышает 50%), хотя try/catch может оказать некоторое влияние на производительность. Влияние на производительность try catch связано с тем, что try/catch не позволяет JVM выполнять некоторые оптимизации. Джошуа Блох в "Эффективной Яве" сказал следующее: Джошуа Блох в "Эффективной Яве" сказал следующее:

• Помещение кода в блок try-catch препятствует определенной оптимизации, которую иначе могли бы выполнить современные реализации JVM.

public class PerformanceStats {
static final String regularExpr = "([0-9]*[.])?[0-9]+";

public static void main(String[] args) {

    PerformanceStats ps = new PerformanceStats();
    ps.statsFinder();
    //System.out.println("123".matches(regularExpr));

}


private void statsFinder() {
    int count =  200000;
    int ncount = 200000;
    ArrayList<String> ar = getList(count, ncount);

    System.out.println("count = " + count + " ncount = " + ncount);

    long t1 = System.nanoTime();
    validateWithCatch(ar);
    long t2 = System.nanoTime();
    validateWithRegularExpression(ar);
    long t3 = System.nanoTime();

    System.out.println("time taken with Exception          " + (t2 - t1) );
    System.out.println("time taken with Regular Expression " + (t3 - t2) );
}


private ArrayList<String> getList(int count, int noiseCount) {
    Random rand = new Random();

    ArrayList<String> list = new ArrayList<String>();
    for (int i = 0; i < count; i++) {
        list.add((String) ("" + Math.abs(rand.nextFloat())));
    }
    // adding noise
    for (int i = 0; i < (noiseCount); i++) {
        list.add((String) ("sdss" + rand.nextInt() ));
    }
    return list;
}



private void validateWithRegularExpression(ArrayList<String> list) {
    ArrayList<Float> ar = new ArrayList<>();
    for (String s : list) {
        if (s.matches(regularExpr)) {
            ar.add(Float.parseFloat(s));
        }
    }
    System.out.println("the size is in regular expression " + ar.size());
}

private void validateWithCatch(ArrayList<String> list) {
    ArrayList<Float> ar = new ArrayList<>();
    for (String s : list) {
        try {
            float e = Float.parseFloat(s);
            ar.add(e);
        } catch (Exception e) {
        }
    }
    System.out.println("the size is in catch block " + ar.size());
}

}

И да, я хотел бы знать, какая из них является хорошей практикой кодирования?

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

  • Если неверные числа маловероятны (то есть это "исключительная" ситуация), то решение на основе исключений подходит. (Действительно, если вероятность ошибочных чисел достаточно мала, исключения могут быть в среднем даже быстрее. Это зависит от относительной скорости Double.parseDouble() и скомпилированное регулярное выражение для типичных входных строк. Это нужно было бы измерить...)

  • Если плохие числа достаточно (или очень) вероятны (то есть это НЕ "исключительная" ситуация), то решение на основе регулярных выражений, вероятно, лучше.

  • Если путь к коду, который выполняет тест, выполняется редко, то на самом деле не имеет значения, какой подход вы используете.

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

Так как исключения являются дорогостоящими только тогда, когда они выбрасываются, нужно искать другую стратегию, только если вы

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

В конце вы также вызовете parseDouble, и поэтому считается правильным использовать его таким образом.

Обратите внимание, что ваш шаблон отвергает 7. как Double, в то время как Java и C/C++ нет, а также научные нотации, такие как 4.2e8,

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

public static boolean isNumeric(String str) 
{ 
    str = "2.3452342323423424E8";
 //   str = "21414124.12412412412412";
 //   str = "123123";
    NumberFormat formatter = NumberFormat.getInstance(); 
    ParsePosition pos = new ParsePosition(0); 
    formatter.parse(str, pos); 
    return  str.length() == pos.getIndex();  
}
Другие вопросы по тегам