Проверете дали низът съдържа подниз

1. Общ преглед

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

2. String.indexOf

Нека първо опитаме да използваме метода String.indexOf . indexOf ни дава първата позиция, където е намерен поднизът, или -1, ако изобщо не е намерен.

Когато търсим „Rhap“, ще се върне 9:

Assert.assertEquals(9, "Bohemian Rhapsodyan".indexOf("Rhap"));

Когато търсим „rhap“, той ще върне -1, защото е чувствителен към малки и големи букви.

Assert.assertEquals(-1, "Bohemian Rhapsodyan".indexOf("rhap")); Assert.assertEquals(9, "Bohemian Rhapsodyan".toLowerCase().indexOf("rhap"));

Също така е важно да се отбележи, че ако търсим подниза „an“, той ще върне 6, защото връща първото появяване:

Assert.assertEquals(6, "Bohemian Rhapsodyan".indexOf("an"));

3. String.съдържа

След това нека опитаме String.contains . contains ще търси подниз в целия String и ще върне true, ако е намерен и false в противен случай.

В този пример съдържа връща true, защото е намерено „Hey“.

Assert.assertTrue("Hey Ho, let's go".contains("Hey"));

Ако низът не е намерен, съдържа връща false :

Assert.assertFalse("Hey Ho, let's go".contains("jey"));

В последния пример „хей“ не е намерен, защото String.contains чувствителен към малки и големи букви.

Assert.assertFalse("Hey Ho, let's go".contains("hey")); Assert.assertTrue("Hey Ho, let's go".toLowerCase().contains("hey"));

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

4. StringUtils.containsIgnoreCase

Третият ни подход ще бъде използването на StringUtils # containsIgnoreCase от библиотеката Apache Commons Lang :

Assert.assertTrue(StringUtils.containsIgnoreCase("Runaway train", "train")); Assert.assertTrue(StringUtils.containsIgnoreCase("Runaway train", "Train"));

Можем да видим, че ще провери дали даден подниз се съдържа в String , като игнорира случая . Ето защо containsIgnoreCase връща true, когато търсим „Trai“, а също и „trai“ вътре в „Runaway Train“.

Този подход няма да бъде толкова ефективен, колкото предишните подходи, тъй като отнема допълнително време, за да се игнорира случаят. containsIgnoreCase вътрешно преобразува всяка буква в главни букви и сравнява преобразуваните букви вместо оригиналните.

5. Използване на Pattern

Последният ни подход ще бъде използване на шаблон с регулярен израз :

Pattern pattern = Pattern.compile("(?
    

We can observe that we need to build the Pattern first, then we need to create the Matcher, and finally, we can check with the find method if there's an occurrence of the substring or not:

Matcher matcher = pattern.matcher("Hit the road Jack"); Assert.assertTrue(matcher.find());

For example, the first time that find is executed, it returns true because the word “road” is contained inside of the string “Hit the road Jack”, but when we try to find the same word in the string “and don't you come back no more” it returns false:

Matcher matcher = pattern.matcher("and don't you come back no more"); Assert.assertFalse(matcher.find());

6. Performance Comparison

We'll use an open-source micro-benchmark framework called Java Microbenchmark Harness (JMH) in order to decide which method is the most efficient in terms of execution time.

6.1. Benchmark Setup

As in every JMH benchmark, we have the ability to write a setup method, in order to have certain things in place before our benchmarks are run:

@Setup public void setup() { message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, " + "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " + "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris " + "nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in " + "reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. " + "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt " + "mollit anim id est laborum"; pattern = Pattern.compile("(?
     

In the setup method, we're initializing the message field. We'll use this as the source text for our various searching implementations.

We also are initializing pattern in order to use it later in one of our benchmarks.

6.2. The String.indexOf Benchmark

Our first benchmark will use indexOf:

@Benchmark public int indexOf() { return message.indexOf("eiusmod"); }

We'll search in which position “eiusmod” is present in the message variable.

6.3. The String.contains Benchmark

Our second benchmark will use contains:

@Benchmark public boolean contains() { return message.contains("eiusmod"); }

We'll try to find if the message value contains “eiusmod”, the same substring used in the previous benchmark.

6.4. The StringUtils.containsIgnoreCase Benchmark

Our third benchmark will use StringUtils#containsIgnoreCase:

@Benchmark public boolean containsStringUtilsIgnoreCase() { return StringUtils.containsIgnoreCase(message, "eiusmod"); }

As with the previous benchmarks, we'll search the substring in the message value.

6.5. The Pattern Benchmark

And our last benchmark will use Pattern:

@Benchmark public boolean searchWithPattern() { return pattern.matcher(message).find(); }

We'll use the pattern initialized in the setup method to create a Matcher and be able to call the find method, using the same substring as before.

6.6. Analysis of Benchmarks Results

It's important to note that we're evaluating the benchmark results in nanoseconds.

After running our JMH test, we can see the average time each took:

  • contains: 14.736 ns
  • indexOf: 14.200 ns
  • containsStringUtilsIgnoreCase: 385.632 ns
  • searchWithPattern: 1014.633 ns

indexOf method is the most efficient one, closely followed by contains. It makes sense that contains took longer because is using indexOf internally.

containsStringUtilsIgnoreCase took extra time compared with the previous ones because it's case insensitive.

searchWithPattern, took an even higher average time the last one, proving that using Patterns is the worst alternative for this task.

7. Conclusion

In this article, we've explored various ways to search for a substring in a String. We've also benchmarked the performance of the different solutions.

As always, the code is available over on GitHub.