У роботі програміста часто-густо деякі завдання або їх складові можуть повторюватися. Тому сьогодні хотілося б торкнутися теми, яка часто зустрічається у повсякденній роботі будь-якого Java-розробника. Припустимо, що вам із деякого методу приходить деякий рядок. І все в ній начебто добре, але є якась дрібниця, яка вас не влаштовує. Наприклад, не підходить роздільник і вам потрібен якийсь інший (або зовсім не потрібен). Що можна зробити у такій ситуації? Звичайно, скористатися методами
replace
класу String
.
Java string replace
Об'єкт типуString
має чотири варіації методу заміни replace
.
replace(char, char);
replace(CharSequence, CharSequence);
replaceFirst(String, String);
replaceAll(String, String).
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
. Ключовими класами є:
Pattern
- Клас, що надає скомпільований варіант регулярного вираження.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
, а щоразу створюємо новий, з необхідним вмістом. Але щоразу створювати новий об'єкт досить довго, чи не так? Особливо, коли питання не в парі об'єктів, а в парі сотень, а то й тисяч. Мимоволі починаєш замислюватися про альтернативи. І які ми маємо альтернативи? Хм... Коли мова заходить про 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
із загального рядка.
StringBuilder
— indexOf
дізнаємося індекс першого входження цікавої для нас підрядки. З жалем, хотілося б відзначити, що 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 раз!
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ