JavaRush /Java блог /Random UA /Replace string в Java

Replace string в Java

Стаття з групи Random UA
У роботі програміста часто-густо деякі завдання або їх складові можуть повторюватися. Тому сьогодні хотілося б торкнутися теми, яка часто зустрічається у повсякденній роботі будь-якого Java-розробника. Replace string в Java - 1Припустимо, що вам із деякого методу приходить деякий рядок. І все в ній начебто добре, але є якась дрібниця, яка вас не влаштовує. Наприклад, не підходить роздільник і вам потрібен якийсь інший (або зовсім не потрібен). Що можна зробити у такій ситуації? Звичайно, скористатися методами replaceкласу String.

Java string replace

Об'єкт типу Stringмає чотири варіації методу заміни replace.
  • replace(char, char);
  • replace(CharSequence, CharSequence);
  • replaceFirst(String, String);
  • replaceAll(String, String).
Призначення всіх цих методів одне – заміна частини рядка на інший рядок. Давайте розглянемо їх докладніше. 1.replace(char, char) String replace(char oldChar, char newChar) - Замінює всі входження символу першого аргументу oldCharдругим - newChar. У цьому прикладі ми замінимо кому на крапку з комою:
String value = "In JavaRush, Diego the best, Diego is Java God".replace(',', ';');
System.out.println(value);
Виведення в консоль:
In JavaRush; Diego the best; Diego is Java God
2.replace(CharSequence, CharSequence) Замінює кожен підрядок рядка, який відповідає зазначеній послідовності символів, на послідовності символів заміни.
String value = "In JavaRush, Diego the best, Diego is Java God".replace("Java", "Rush");
System.out.println(value);
Висновок:
In RushRush, Diego the best, Diego is Rush God
3.replaceFirst(String, String) String replaceFirst(String regex, String replacement) — замінює перший підрядок, який відповідає зазначеному регулярному виразу, що заміщає рядком. При використанні неприпустимого регулярного виразу можна вловити PatternSyntaxException (що не є гуд). У цьому прикладі давайте замінимо ім'я робота-чемпіона:
String value = "In JavaRush, Diego the best, Diego is Java God".replaceFirst("Diego", "Amigo");
System.out.println(value);
Виведення в консоль:
In JavaRush, Amigo the best, Diego is Java God
Як бачимо, змінилося лише перше входження " Diego " , ну а подальші залишабося за бортом — тобто, недоторканими. 4. replaceAll()Java String replaceAll(String regex, String replacement) - даний метод замінює в рядку всі входження підрядки regexна replacement. Як перший аргумент regexможливе використання регулярного виразу. Як приклад спробуємо виконати попередню заміну з іменами, але вже новим методом:
String value = "In JavaRush, Diego the best, Diego is Java God".replaceAll("Diego", "Amigo");
System.out.println(value);
Виведення в консоль:
In JavaRush, Amigo the best, Amigo is Java God
Як бачимо, відбулася повна заміна всіх символів на необхідні. Думаю, Аміго буде задоволений =)

Регулярні вирази

Вище було сказано, що є можливість заміни за регулярним виразом. Для початку прояснимо для себе, що ж таке регулярне вираження? Регулярні вирази - це формальна мова для пошуку та здійснення маніпуляцій з підрядками в тексті, заснована на використанні метасимволів (символів-джокерів). Простіше кажучи, це шаблон, що складається із символів та метасимволів, що задає правило пошуку. Наприклад: \Dшаблон, що описує будь-який нецифровий символ; \d— визначає будь-який цифровий символ, який можна описати як [0-9]; [a-zA-Z]- шаблон, що описує латинські символи від a до z, без урахування регістру; Розглянемо застосування в методі replaceAllкласу String:
String value = "In JavaRush, Diego the best, Diego is Java God".replaceAll("\\s[a-zA-Z]{5}\\s", " Amigo ");
System.out.println(value);
Виведення в консоль:
In JavaRush, Amigo the best, Amigo is Java God
\\s[a-zA-Z]{5}\\s- Описує слово з 5 латинських символів, оточене пробілами. Відповідно, цей шаблон і замінюється на переданий нами рядок.

Java regex replace

В основному для використання регулярних виразів у Java застосовують можливості пакета java.util.regex. Ключовими класами є:
  1. Pattern- Клас, що надає скомпільований варіант регулярного вираження.
  2. Matcher— даний клас інтерпретує шаблон і визначає збіги в рядку, що прийшов йому рядку.
Як правило ці два класи працюють у зв'язці. Отже, як буде виглядати наш попередній об'єкт, але вже за допомогою Matcherі Pattern:
Pattern pattern = Pattern.compile("\\s[a-zA-Z]{5}\\s");
Matcher matcher = pattern.matcher("In JavaRush, Diego the best, Diego is Java God");
String value = matcher.replaceAll(" Amigo ");
System.out.println(value);
І висновок у нас буде тим самим:
In JavaRush, Amigo the best, Amigo is Java God
Більш детально з регулярними виразами можна ознайомитись у цій статті .

Альтернатива replaceAll

Безперечно, методи replaceдуже Stringвражаючі, але не можна не враховувати той факт, що Stringоб'єкт immutable, тобто він не може бути змінений після свого створення. Тому коли ми замінюємо деякі частини рядка за допомогою методів replace, ми не змінюємо об'єкт String, а щоразу створюємо новий, з необхідним вмістом. Але щоразу створювати новий об'єкт досить довго, чи не так? Особливо, коли питання не в парі об'єктів, а в парі сотень, а то й тисяч. Мимоволі починаєш замислюватися про альтернативи. І які ми маємо альтернативи? Replace string в Java - 2Хм... Коли мова заходить про Stringїї властивості immutable, відразу згадуєш про альтернативи, але не immutable, а саме про StringBuilder/StringBuffer. Як ми пам'ятаємо, ці класи практично не відрізняються за винятком того, що StringBufferоптимізовано під використання в багатопотоковому середовищі, тому в умовах однопоточного використання StringBuilderпрацює дещо швидше. Виходячи з цього, сьогодні ми і будемо використовувати StringBuilder. У даного класу є багато цікавих методів, але саме зараз нас цікавить replace. StringBuilder replace(int start, int end, String str)— цей метод замінює символи у підстроюванні цієї послідовності на символи у зазначеному рядку. Підрядок починається в зазначеному початку і продовжується до символу в кінці індексу -1або до кінця послідовності, якщо такого символу немає. Давайте розглянемо приклад:
StringBuilder strBuilder = new StringBuilder("Java Rush");
strBuilder.replace(5, 9, "God");
System.out.println(strBuilder);
Висновок:
Java God
Як ви бачите, ми вказуємо проміжок, в який хочемо записати рядок, і записуємо підрядок поверх те, що є в проміжку. Так ось, за допомогою StringBuilderвідтворимо аналог методу replaceall java. Як це буде виглядати:
public static String customReplaceAll(String str, String oldStr, String newStr) {

   if ("".equals(str) || "".equals(oldStr) || oldStr.equals(newStr)) {
       return str;
   }
   if (newStr == null) {
       newStr = "";
   }
   final int strLength = str.length();
   final int oldStrLength = oldStr.length();
   StringBuilder builder = new StringBuilder(str);

   for (int i = 0; i < strLength; i++) {
       int index = builder.indexOf(oldStr, i);

       if (index == -1) {
           if (i == 0) {
               return str;
           }
           return builder.toString();
       }
       builder = builder.replace(index, index + oldStrLength, newStr);

   }
       return builder.toString();
}
На перший погляд страшно, але трохи розібравшись, можна зрозуміти, що все не так вже й складно і цілком собі логічно. У нас є три аргументи:
  • str— рядок, у якому ми хочемо замінити деякі підрядки;
  • oldStr— подання підрядків, які замінюватимемо;
  • newStr— те, на що ми замінюватимемо.
Перший ifнам необхідний, щоб перевірити вхідні дані, і якщо рядок strабо oldStrпорожні, або ж новий підряд newStrдорівнює старому oldStr, то виконання методу буде безглуздим. Тому повертаємо початковий рядок - str. Далі перевіряємо newStrна null, і якщо це так, то перетворимо на більш зручний для нас формат порожнього рядка - "". Після цього у нас йде оголошення необхідних нам змінних:
  • довжини загального рядка str;
  • довжини підрядки oldStr;
  • об'єкт StringBuilderіз загального рядка.
Запускаємо цикл, який повинен відпрацювати кількість разів, що дорівнює довжині загального рядка (але, швидше за все, цього ніколи не станеться). За допомогою методу класу StringBuilderindexOfдізнаємося індекс першого входження цікавої для нас підрядки. З жалем, хотілося б відзначити, що indexOfне працює з регулярними висловлюваннями, тому наш підсумковий метод буде працювати тільки з входженнями рядків(( Якщо цей індекс у нас дорівнює -1, то даних входжень у поточному об'єкті StringBuilderбільше немає, тому виходимо з методу з результатом, що цікавить): він міститься в нашому StringBuilder, який ми перетворимо до String, за допомогою toString. Якщо у нас індекс дорівнює-1у першій ітерації циклу, отже підрядки, яку треба замінити, був у загальному рядку спочатку. Тож у такій ситуації просто повернемо загальний рядок. Далі у нас і йде використання вищеописаного методу replaceз StringBuilderвикористанням знайденого індексу входження для позначення координат замінної підрядки. Цей цикл відпрацює стільки разів, скільки буде знайдено підрядок, які потрібно замінити. Якщо рядок складається лише із символу, який потрібно замінити, то тільки в такому разі у нас цикл відпрацює повністю і ми отримаємо результат, StringBuilderперетворений на рядок. Потрібно перевірити коректність роботи цього методу, чи не так? Напишемо тест, який перевіряє роботу методу у різних ситуаціях:
@Test
public void customReplaceAllTest() {
   String str = "qwertyuiop__qwertyuiop__";

   String firstCase = Solution.customReplaceAll(str, "q", "a");
   String firstResult = "awertyuiop__awertyuiop__";
   assertEquals(firstCase, firstResult);

   String secondCase = Solution.customReplaceAll(str, "q", "ab");
   String secondResult = "abwertyuiop__abwertyuiop__";
   assertEquals(secondCase, secondResult);

   String thirdCase = Solution.customReplaceAll(str, "rtyu", "*");
   String thirdResult = "qwe*iop__qwe*iop__";
   assertEquals(thirdCase, thirdResult);

   String fourthCase = Solution.customReplaceAll(str, "q", "");
   String fourthResult = "wertyuiop__wertyuiop__";
   assertEquals(fourthCase, fourthResult);

   String fifthCase = Solution.customReplaceAll(str, "uio", "");
   String fifthResult = "qwertyp__qwertyp__";
   assertEquals(fifthCase, fifthResult);

   String sixthCase = Solution.customReplaceAll(str, "", "***");
   assertEquals(sixthCase, str);

   String seventhCase = Solution.customReplaceAll("", "q", "***");
   assertEquals(seventhCase, "");
}
Можна розбити на 7 окремих тестів, кожен із яких відповідатиме за свій тестовий випадок. Запустивши його, ми побачимо, що він зелений, тобто успішний. Ну от, начебто і все. Хоча зачекайте, вище ми говорабо, що цей метод буде значно швидше, ніж replaceAllу String. Що ж, давайте подивимося:
String str = "qwertyuiop__qwertyuiop__";
long firstStartTime = System.nanoTime();

for (long i = 0; i < 10000000L; i++) {
   str.replaceAll("tyu", "#");
}

double firstPerformance = System.nanoTime() - firstStartTime;

long secondStartTime = System.nanoTime();

for (long i = 0; i < 10000000L; i++) {
   customReplaceAll(str, "tyu", "#");
}

double secondPerformance = System.nanoTime() - secondStartTime;

System.out.println("Performance ratio  - " +  firstPerformance / secondPerformance);
Далі цей код був запущений тричі, і ми отримали наступні результати: Висновок в консоль:
Performance ratio  - 5.012148941181627
 
Performance ratio  - 5.320637176017641
 
Performance ratio  - 4.719192686500394
Як ми бачимо, в середньому наш метод продуктивніший, ніж класичний replaceAllклас Stringу 5 разів! Що ж і насамкінець давайте запустимо цю ж перевірку, але, так би мовити, вхолосту. Інакше кажучи, у разі коли збіги знайдено нічого очікувати. Замінимо рядок для пошуку "tyu"з "--". При трьох запусках були отримані наступні результати: Виведення в консоль:
Performance ratio  - 8.789647093542246
 
Performance ratio  - 9.177105482660881
 
Performance ratio  - 8.520964375227406
У середньому продуктивність випадків, коли збігів не було знайдено, зросла в 8.8 раз! Replace string в Java - 4
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ