W pracy programisty dość często niektóre zadania lub ich elementy mogą się powtarzać. Dlatego dzisiaj chciałbym poruszyć temat, który często spotyka się w codziennej pracy każdego programisty Java.
Załóżmy, że otrzymujesz określony ciąg znaków z określonej metody. I wszystko wydaje się być w porządku, ale jest pewna drobnostka, która Ci nie pasuje. Np. separator się nie nadaje i trzeba jakiś inny (albo wcale). Co można zrobić w takiej sytuacji? Oczywiście skorzystaj z metod
replace
klasy
String
.
Zamień ciąg Java
Obiekt typu
String
ma cztery odmiany metody zamiany
replace
:
replace(char, char);
replace(CharSequence, CharSequence);
replaceFirst(String, String);
replaceAll(String, String).
Cel wszystkich tych metod jest ten sam - zastąpienie części ciągu innym ciągiem. Przyjrzyjmy się im bliżej.
1.replace(char, char)
String replace(char oldChar, char newChar)
- zastępuje wszystkie wystąpienia znaku pierwszego argumentu
oldChar
drugim -
newChar
. W tym przykładzie zastąpimy przecinek średnikiem:
String value = "In JavaRush, Diego the best, Diego is Java God".replace(',', ';');
System.out.println(value);
Wyjście konsoli:
In JavaRush; Diego the best; Diego is Java God
2.replace(CharSequence, CharSequence)
Zastępuje każdy podciąg ciągu znaków pasujący do określonej sekwencji znaków ciągiem znaków zastępczych.
String value = "In JavaRush, Diego the best, Diego is Java God".replace("Java", "Rush");
System.out.println(value);
Wniosek:
In RushRush, Diego the best, Diego is Rush God
3.replaceFirst(String, String)
String replaceFirst(String regex, String replacement)
- Zastępuje pierwszy podciąg pasujący do określonego wyrażenia regularnego ciągiem zastępczym. Używając nieprawidłowego wyrażenia regularnego, możesz przechwycić
wyjątek PatternSyntaxException (co nie jest dobrą rzeczą). W tym przykładzie zamieńmy nazwę robota-mistrza:
String value = "In JavaRush, Diego the best, Diego is Java God".replaceFirst("Diego", "Amigo");
System.out.println(value);
Wyjście konsoli:
In JavaRush, Amigo the best, Diego is Java God
Jak widać, zmianie uległo jedynie pierwsze wystąpienie „Diego”, natomiast kolejne pozostały pominięte, czyli nietknięte.
4. replaceAll()
w Javie String replaceAll(String regex, String replacement)
- ta metoda zastępuje wszystkie wystąpienia podciągu w ciągu
regex
znaków
replacement
. Jako pierwszego argumentu można użyć wyrażenia regularnego
regex
. Jako przykład spróbujmy wykonać poprzednią zamianę nazwami, ale nową metodą:
String value = "In JavaRush, Diego the best, Diego is Java God".replaceAll("Diego", "Amigo");
System.out.println(value);
Wyjście konsoli:
In JavaRush, Amigo the best, Amigo is Java God
Jak widzimy, wszystkie symbole zostały całkowicie zastąpione niezbędnymi. Myślę, że Amigo będzie zadowolony =)
Wyrażenia regularne
Powyżej powiedziano, że można zastąpić wyrażeniem regularnym. Najpierw wyjaśnijmy sobie, czym jest wyrażenie regularne?
Wyrażenia regularne to formalny język służący do wyszukiwania i manipulowania podciągami w tekście w oparciu o użycie metaznaków (znaków wieloznacznych). Mówiąc najprościej, jest to wzór znaków i metaznaków definiujący regułę wyszukiwania. Na przykład:
\D
- szablon opisujący dowolny znak niecyfrowy;
\d
— definiuje dowolny znak numeryczny, który można również opisać jako
[0-9]
;
[a-zA-Z]
— szablon opisujący znaki łacińskie od a do z, wielkość liter nie jest uwzględniana; Rozważ zastosowanie w metodzie
replaceAll
klasowej
String
:
String value = "In JavaRush, Diego the best, Diego is Java God".replaceAll("\\s[a-zA-Z]{5}\\s", " Amigo ");
System.out.println(value);
Wyjście konsoli:
In JavaRush, Amigo the best, Amigo is Java God
\\s[a-zA-Z]{5}\\s
— opisuje słowo składające się z 5 znaków łacińskich otoczonych spacjami. Odpowiednio szablon ten zostaje zastąpiony przekazanym przez nas ciągiem znaków.
Zamień wyrażenie regularne Java
Zasadniczo, aby używać wyrażeń regularnych w Javie, należy wykorzystać możliwości platformy
java.util.regex
. Kluczowe klasy to:
Pattern
- klasa udostępniająca skompilowaną wersję wyrażenia regularnego.
Matcher
— ta klasa interpretuje wzorzec i określa dopasowania w otrzymanym ciągu znaków.
Zazwyczaj te dwie klasy działają w połączeniu. Jak więc będzie wyglądał nasz poprzedni obiekt, ale za pomocą
Matcher
i
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);
A nasz wniosek będzie taki sam:
In JavaRush, Amigo the best, Amigo is Java God
Więcej o wyrażeniach regularnych możesz przeczytać
w tym artykule .
Alternatywa dla zastąpieniaWszystko
Nie ulega wątpliwości, że metody
replace
robią
String
wrażenie, jednak nie można ignorować faktu, że
String
jest to
immutable
obiekt, czyli nie można go zmienić po stworzeniu. Dlatego też, gdy za pomocą metod zastępujemy pewne części ciągu znaków
replace
, nie zmieniamy obiektu
String
, lecz za każdym razem tworzymy nowy, z niezbędną zawartością. Ale tworzenie nowego obiektu za każdym razem zajmuje dużo czasu, prawda? Zwłaszcza, gdy nie chodzi o kilka obiektów, ale o kilkaset, a nawet tysiące. Chcąc nie chcąc, zaczynasz myśleć o alternatywach. A jakie mamy alternatywy?
Hmm... Jeśli chodzi o
String
jego właściwość
immutable
, od razu przychodzą ci na myśl alternatywy, ale nie
immutable
, a mianowicie
StringBuilder/StringBuffer . Jak pamiętamy, klasy te właściwie niczym się nie różnią, poza tym, że
StringBuffer
są zoptymalizowane do użycia w środowisku wielowątkowym, więc
StringBuilder
w przypadku użycia jednowątkowego działają nieco szybciej. Na tej podstawie dzisiaj skorzystamy z
StringBuilder.
tej klasy, która ma wiele ciekawych metod, ale konkretnie teraz interesuje nas
replace
.
StringBuilder replace(int start, int end, String str)
— ta metoda zastępuje znaki w podciągu tej sekwencji znakami z określonego ciągu. Podciąg zaczyna się od określonego początku i trwa aż do znaku na końcu indeksu
-1
lub do końca sekwencji, jeśli taki znak nie istnieje. Spójrzmy na przykład:
StringBuilder strBuilder = new StringBuilder("Java Rush");
strBuilder.replace(5, 9, "God");
System.out.println(strBuilder);
Wniosek:
Java God
Jak widać, wskazujemy przedział, w którym chcemy zapisać ciąg, i dopisujemy podciąg na wierzchu tego, co znajduje się w przedziale. Korzystając z pomocy,
StringBuilder
odtworzymy analogię metody
replaceall java
. Jak to będzie wyglądać:
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();
}
Na pierwszy rzut oka to przerażające, ale przy odrobinie zrozumienia można zrozumieć, że wszystko nie jest takie skomplikowane i całkiem logiczne.Mamy trzy argumenty:
str
— linia, w której chcemy zastąpić niektóre podciągi;
oldStr
— reprezentacja podciągów, które zastąpimy;
newStr
- czym to zastąpimy.
Pierwszy z nich
if
jest nam potrzebny do sprawdzania przychodzących danych i jeśli ciąg znaków
str
będzie
oldStr
pusty lub nowy podciąg
newStr
będzie równy staremu
oldStr
, to wykonanie metody będzie pozbawione sensu. Dlatego zwracamy oryginalny ciąg -
str
. Następnie sprawdzamy ,
newStr
czy
null
tak jest i jeśli tak jest, to konwertujemy go na wygodniejszy dla nas format pustego ciągu znaków -
""
. Następnie mamy deklarację potrzebnych nam zmiennych:
- całkowita długość ciągu
str
;
- długość podciągu
oldStr
;
- obiekt
StringBuilder
ze wspólnego ciągu.
Rozpoczynamy pętlę, która powinna zostać wykonana tyle razy, ile wynosi długość całego łańcucha (ale najprawdopodobniej nigdy tak się nie stanie). Stosując metodę klasową
StringBuilder
-
indexOf
- znajdujemy indeks pierwszego wystąpienia interesującego nas podciągu. Niestety chciałbym zaznaczyć, że
indexOf
nie działa to z wyrażeniami regularnymi, więc nasza ostateczna metoda będzie działać tylko z wystąpieniami ciągów znaków ((Jeśli ten indeks jest równy
-1
, to nie ma już więcej wystąpień tych wystąpień w bieżącym obiekcie
StringBuilder
, więc wychodzimy z metody z wynikiem będącym przedmiotem zainteresowania: jest on zawarty w naszym
StringBuilder
, na które konwertujemy za
String
pomocą
toString
.Jeśli nasz indeks jest równy
-1
w pierwszej iteracji pętli, to podciąg, który należy zastąpić, nie znajdował się w ogólnym początkowo string. Dlatego w takiej sytuacji po prostu zwracamy ogólny string. Następnie mamy i opisaną powyżej metodę wykorzystujemy
replace
znaleziony
StringBuilder
indeks wystąpienia do wskazania współrzędnych podciągu, który ma zostać zastąpiony. Ta pętla zostanie uruchomiona tyle razy, ile zostanie znalezionych podciągów, które należy zastąpić.Jeśli ciąg składa się tylko ze znaku, który należy zastąpić, to tylko w tym przypadku mamy Pętla wykona się całkowicie i otrzymamy wynik
StringBuilder
przekonwertowany na ciąg. Trzeba sprawdzić poprawność tej metody, prawda? Napiszmy test sprawdzający działanie metody w różnych sytuacjach:
@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, "");
}
Można go podzielić na 7 oddzielnych testów, z których każdy będzie odpowiedzialny za własny przypadek testowy. Po uruchomieniu przekonamy się, że jest zielony, czyli udany. Cóż, to chyba wszystko. Chociaż poczekaj, powiedzieliśmy powyżej, że ta metoda będzie znacznie szybsza
replaceAll
niż
String
. No cóż, spójrzmy:
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);
Następnie ten kod został uruchomiony trzykrotnie i otrzymaliśmy następujące wyniki: Dane wyjściowe konsoli:
Performance ratio - 5.012148941181627
Performance ratio - 5.320637176017641
Performance ratio - 4.719192686500394
Jak widzimy, nasza metoda jest średnio 5 razy bardziej produktywna niż
replaceAll
zajęcia klasyczne!
String
No cóż, w końcu przeprowadźmy ten sam test, ale, że tak powiem, na próżno. Innymi słowy, w przypadku, gdy nie zostanie znalezione żadne dopasowanie. Zamieńmy wyszukiwany ciąg z
"tyu"
na
"--"
. Trzy serie dały następujące wyniki: Dane wyjściowe konsoli:
Performance ratio - 8.789647093542246
Performance ratio - 9.177105482660881
Performance ratio - 8.520964375227406
Średnio wydajność w przypadkach, w których nie znaleziono żadnych dopasowań, wzrosła o 8,8 razy!
Co jeszcze warto przeczytać: |
|
GO TO FULL VERSION