Проверете дали низът е цифров в Java

1. Въведение

Често докато работим над String s, трябва да разберем дали String е валидно число или не.

В този урок ще изследваме множество начини да открием дали даден низ е цифров , първо използвайки обикновена Java, след това регулярни изрази и накрая с помощта на външни библиотеки.

След като приключим с обсъждането на различни имплементации, ще използваме бенчмаркове, за да добием представа кои методи са оптимални.

2. Предпоставки

Нека започнем с някои предпоставки, преди да преминем към основното съдържание.

Във втората част на тази статия ще използваме външната библиотека на Apache Commons, за която ще добавим нейната зависимост в нашия pom.xml :

 org.apache.commons commons-lang3 3.9 

Последната версия на тази библиотека може да бъде намерена на Maven Central.

3. Използване на обикновена Java

Може би най-лесният и най-надеждният начин да проверите дали даден низ е цифров или не е като го анализирате с помощта на вградените методи на Java:

  1. Integer.parseInt (низ)
  2. Float.parseFloat (String)
  3. Double.parseDouble (String)
  4. Long.parseLong (String)
  5. нов BigInteger (низ)

Ако тези методи не хвърлят всички NumberFormatException , това означава, че разбор е била успешна и String са числови:

public static boolean isNumeric(String strNum) { if (strNum == null) { return false; } try { double d = Double.parseDouble(strNum); } catch (NumberFormatException nfe) { return false; } return true; }

Нека видим този метод в действие:

assertThat(isNumeric("22")).isTrue(); assertThat(isNumeric("5.05")).isTrue(); assertThat(isNumeric("-200")).isTrue(); assertThat(isNumeric("10.0d")).isTrue(); assertThat(isNumeric(" 22 ")).isTrue(); assertThat(isNumeric(null)).isFalse(); assertThat(isNumeric("")).isFalse(); assertThat(isNumeric("abc")).isFalse();

В нашия метод isNumeric () ние просто проверяваме за стойности, които са от тип Double , но този метод може също да бъде модифициран, за да провери за Integer , Float , Long и големи числа, като използваме някой от методите за синтактичен анализ, които сме включили по-рано .

Тези методи се обсъждат и в статията за преобразуване на низове в Java.

4. Използване на регулярни изрази

Сега нека използваме регулярно изражение -? \ D + (\. \ D +)? да съвпада с числови низове, състоящи се от положително или отрицателно цяло число и плувки.

Но това се подразбира, че определено можем да модифицираме този регулярен израз, за ​​да идентифицираме и да се справим с широк спектър от правила. Тук ще го направим просто.

Нека разбием този регулярен израз и да видим как работи:

  • -? - тази част идентифицира дали даденото число е отрицателно, тирето „ - “ търси тире буквално и въпросителен знак „ ? ”Маркира присъствието си като незадължително
  • \ d + - това търси една или повече цифри
  • (\. \ d +)? - тази част от регулярния израз е да идентифицира числа с плаващо число. Тук търсим една или повече цифри, последвани от точка. Въпросният знак в крайна сметка означава, че тази пълна група не е задължителна

Регулярните изрази са много широка тема. За да получите кратък преглед, проверете нашия урок за API за регулярни изрази на Java.

Засега нека създадем метод, използващ горния регулярен израз:

private Pattern pattern = Pattern.compile("-?\\d+(\\.\\d+)?"); public boolean isNumeric(String strNum) { if (strNum == null) { return false; } return pattern.matcher(strNum).matches(); }

Нека сега разгледаме някои твърдения за горния метод:

assertThat(isNumeric("22")).isTrue(); assertThat(isNumeric("5.05")).isTrue(); assertThat(isNumeric("-200")).isTrue(); assertThat(isNumeric(null)).isFalse(); assertThat(isNumeric("abc")).isFalse();

5. Използване на Apache Commons

В този раздел ще обсъдим различни методи, налични в библиотеката Apache Commons.

5.1. NumberUtils.isCreatable (String)

NumberUtils от Apache Commons предоставя статичен метод NumberUtils.isCreatable (String), който проверява дали String е валиден номер на Java или не.

Този метод приема:

  1. Шестнадесетични числа, започващи с 0x или 0X
  2. Осмични числа, започващи с водеща 0
  3. Научна нотация (например 1.05e-10)
  4. Номера, маркирани с квалификатор на типа (например 1L или 2.2d)

Ако предоставеният низ е null или празен / празен , тогава той не се счита за число и методът ще върне false .

Нека да стартираме някои тестове, използвайки този метод:

assertThat(NumberUtils.isCreatable("22")).isTrue(); assertThat(NumberUtils.isCreatable("5.05")).isTrue(); assertThat(NumberUtils.isCreatable("-200")).isTrue(); assertThat(NumberUtils.isCreatable("10.0d")).isTrue(); assertThat(NumberUtils.isCreatable("1000L")).isTrue(); assertThat(NumberUtils.isCreatable("0xFF")).isTrue(); assertThat(NumberUtils.isCreatable("07")).isTrue(); assertThat(NumberUtils.isCreatable("2.99e+8")).isTrue(); assertThat(NumberUtils.isCreatable(null)).isFalse(); assertThat(NumberUtils.isCreatable("")).isFalse(); assertThat(NumberUtils.isCreatable("abc")).isFalse(); assertThat(NumberUtils.isCreatable(" 22 ")).isFalse(); assertThat(NumberUtils.isCreatable("09")).isFalse();

Обърнете внимание как получаваме верни твърдения за шестнадесетични числа, осмични числа и научни обозначения в редове 6, 7 и 8 съответно.

Също така, на ред 14, низът „09“ връща false, тъй като предходното „0“ показва, че това е осмично число и „09“ не е валидно осмично число.

За всеки вход, който връща true с този метод, можем да използваме NumberUtils.createNumber (String), който ще ни даде валидното число.

5.2. NumberUtils.isParsable (String)

Методът NumberUtils.isParsable (String) проверява дали даденият String е синтактичен или не.

Синтаксируеми числа са тези, които се анализират успешно по всеки метод на синтактичен анализ, като Integer.parseInt (String) , Long.parseLong (String) , Float.parseFloat (String) или Double.parseDouble (String) .

За разлика от NumberUtils.isCreatable () , този метод не приема шестнадесетични числа, научни обозначения или низове, завършващи с какъвто и да е квалификатор на типа, т.е. „f“, „F“, „d“, „D“, „l“ или „ L ' .

Нека да разгледаме някои твърдения:

assertThat(NumberUtils.isParsable("22")).isTrue(); assertThat(NumberUtils.isParsable("-23")).isTrue(); assertThat(NumberUtils.isParsable("2.2")).isTrue(); assertThat(NumberUtils.isParsable("09")).isTrue(); assertThat(NumberUtils.isParsable(null)).isFalse(); assertThat(NumberUtils.isParsable("")).isFalse(); assertThat(NumberUtils.isParsable("6.2f")).isFalse(); assertThat(NumberUtils.isParsable("9.8d")).isFalse(); assertThat(NumberUtils.isParsable("22L")).isFalse(); assertThat(NumberUtils.isParsable("0xFF")).isFalse(); assertThat(NumberUtils.isParsable("2.99e+8")).isFalse();

На ред 4, за разлика от NumberUtils.isCreatable () , числото, започващо със низ „0“, не се счита за осмично число, а нормално десетично число и следователно връща true.

Можем да използваме този метод като заместител на това, което направихме в раздел 3, където се опитваме да анализираме число и да проверяваме за грешка.

5.3. StringUtils.isNumeric (CharSequence )

Методът StringUtils.isNumeric (CharSequence) проверява стриктно за Unicode цифри. Това означава:

  1. Всички цифри от всеки език, който е Unicode цифра, са приемливи
  2. Тъй като десетичната точка не се счита за Unicode цифра, тя не е валидна
  3. Водещите знаци (положителни или отрицателни) също не са приемливи

Нека сега видим този метод в действие:

assertThat(StringUtils.isNumeric("123")).isTrue(); assertThat(StringUtils.isNumeric("١٢٣")).isTrue(); assertThat(StringUtils.isNumeric("१२३")).isTrue(); assertThat(StringUtils.isNumeric(null)).isFalse(); assertThat(StringUtils.isNumeric("")).isFalse(); assertThat(StringUtils.isNumeric(" ")).isFalse(); assertThat(StringUtils.isNumeric("12 3")).isFalse(); assertThat(StringUtils.isNumeric("ab2c")).isFalse(); assertThat(StringUtils.isNumeric("12.3")).isFalse(); assertThat(StringUtils.isNumeric("-123")).isFalse();

Имайте предвид, че входните параметри в редове 2 и 3 представляват числа 123 съответно на арабски и деванагари. Тъй като те са валидни Unicode цифри, този метод връща true за тях.

5.4. StringUtils.isNumericSpace(CharSequence)

The StringUtils.isNumericSpace(CharSequence) checks strictly for Unicode digits and/or space. This is same as StringUtils.isNumeric() with the only difference being that it accepts spaces as well, not only leading and trailing spaces but also if they're in between numbers:

assertThat(StringUtils.isNumericSpace("123")).isTrue(); assertThat(StringUtils.isNumericSpace("١٢٣")).isTrue(); assertThat(StringUtils.isNumericSpace("")).isTrue(); assertThat(StringUtils.isNumericSpace(" ")).isTrue(); assertThat(StringUtils.isNumericSpace("12 3")).isTrue(); assertThat(StringUtils.isNumericSpace(null)).isFalse(); assertThat(StringUtils.isNumericSpace("ab2c")).isFalse(); assertThat(StringUtils.isNumericSpace("12.3")).isFalse(); assertThat(StringUtils.isNumericSpace("-123")).isFalse();

6. Benchmarks

Before we conclude this article, let's go through some benchmark results to help us to analyze which of the above-mentioned methods are best for our use-case.

6.1. Simple Benchmark

First, we take a simple approach. We pick one string value – for our test we use Integer.MAX_VALUE. Then, that value will be tested against all our implementations:

Benchmark Mode Cnt Score Error Units Benchmarking.usingCoreJava avgt 20 57.241 ± 0.792 ns/op Benchmarking.usingNumberUtils_isCreatable avgt 20 26.711 ± 1.110 ns/op Benchmarking.usingNumberUtils_isParsable avgt 20 46.577 ± 1.973 ns/op Benchmarking.usingRegularExpressions avgt 20 101.580 ± 4.244 ns/op Benchmarking.usingStringUtils_isNumeric avgt 20 35.885 ± 1.691 ns/op Benchmarking.usingStringUtils_isNumericSpace avgt 20 31.979 ± 1.393 ns/op

As we see, the most costly operations are regular expressions. After that is our core Java-based solution.

Moreover, note that the operations using the Apache Commons library are by-and-large the same.

6.2. Enhanced Benchmark

Let's use a more diverse set of tests, for a more representative benchmark:

  • 95 values are numeric (0-94 and Integer.MAX_VALUE)
  • 3 contain numbers but are still malformatted — ‘x0‘, ‘0..005′, and ‘–11
  • 1 contains only text
  • 1 is a null

Upon executing the same tests, we'll see the results:

Benchmark Mode Cnt Score Error Units Benchmarking.usingCoreJava avgt 20 10162.872 ± 798.387 ns/op Benchmarking.usingNumberUtils_isCreatable avgt 20 1703.243 ± 108.244 ns/op Benchmarking.usingNumberUtils_isParsable avgt 20 1589.915 ± 203.052 ns/op Benchmarking.usingRegularExpressions avgt 20 7168.761 ± 344.597 ns/op Benchmarking.usingStringUtils_isNumeric avgt 20 1071.753 ± 8.657 ns/op Benchmarking.usingStringUtils_isNumericSpace avgt 20 1157.722 ± 24.139 ns/op

The most important difference is that two of our tests – the regular expressions solution and the core Java-based solution – have traded places.

От този резултат научаваме, че хвърлянето и обработката на NumberFormatException , което се случва само в 5% от случаите, има относително голямо влияние върху цялостната производителност. И така, заключаваме, че оптималното решение зависи от очаквания ни принос.

Също така можем спокойно да заключим, че трябва да използваме методите от библиотеката на Commons или метод, реализиран по подобен начин за оптимална производителност.

7. Заключение

В тази статия разгледахме различни начини да открием дали даден низ е цифров или не. Разгледахме и двете решения - вградени методи, а също и външни библиотеки.

Както винаги, изпълнението на всички примери и кодови фрагменти, дадени по-горе, включително кода, използван за изпълнение на бенчмаркове, може да бъде намерено в GitHub.