Trong công việc của một lập trình viên, một số nhiệm vụ hoặc thành phần của chúng thường được lặp lại. Vì vậy, hôm nay tôi muốn đề cập đến một chủ đề thường gặp trong công việc hàng ngày của bất kỳ nhà phát triển Java nào. Giả sử bạn nhận được một chuỗi nhất định từ một phương thức nhất định. Và mọi thứ về nó có vẻ tốt, nhưng có một số điều nhỏ không phù hợp với bạn. Ví dụ: dấu phân cách không phù hợp và bạn cần một số dấu phân cách khác (hoặc hoàn toàn không). Có thể làm gì trong tình huống như vậy? Đương nhiên, hãy sử dụng các phương thức
replace
của lớp String
.
Thay thế chuỗi Java
Đối tượng loạiString
có bốn biến thể của phương thức thay thế replace
:
replace(char, char);
replace(CharSequence, CharSequence);
replaceFirst(String, String);
replaceAll(String, String).
replace(char, char)
String replace(char oldChar, char newChar)
- thay thế tất cả các lần xuất hiện của ký tự của đối số thứ nhất oldChar
bằng đối số thứ hai - newChar
. Trong ví dụ này, chúng tôi sẽ thay thế dấu phẩy bằng dấu chấm phẩy:
String value = "In JavaRush, Diego the best, Diego is Java God".replace(',', ';');
System.out.println(value);
Đầu ra của bảng điều khiển:
In JavaRush; Diego the best; Diego is Java God
2.replace(CharSequence, CharSequence)
Thay thế từng chuỗi con của một chuỗi khớp với một chuỗi ký tự đã chỉ định bằng một chuỗi ký tự thay thế.
String value = "In JavaRush, Diego the best, Diego is Java God".replace("Java", "Rush");
System.out.println(value);
Phần kết luận:
In RushRush, Diego the best, Diego is Rush God
3.replaceFirst(String, String)
String replaceFirst(String regex, String replacement)
- Thay thế chuỗi con đầu tiên khớp với biểu thức chính quy đã chỉ định bằng chuỗi thay thế. Khi sử dụng biểu thức chính quy không hợp lệ, bạn có thể bắt gặp ngoại lệ PatternSyntaxException (đây không phải là điều tốt). Trong ví dụ này, hãy thay thế tên robot vô địch:
String value = "In JavaRush, Diego the best, Diego is Java God".replaceFirst("Diego", "Amigo");
System.out.println(value);
Đầu ra của bảng điều khiển:
In JavaRush, Amigo the best, Diego is Java God
Như chúng ta có thể thấy, chỉ có lần xuất hiện đầu tiên của “Diego” là thay đổi, nhưng những lần tiếp theo vẫn bị bỏ qua - nghĩa là không bị ảnh hưởng. 4. replaceAll()
trong Java String replaceAll(String regex, String replacement)
- phương thức này thay thế tất cả các lần xuất hiện của chuỗi con trong chuỗi regex
bằng replacement
. Một biểu thức chính quy có thể được sử dụng làm đối số đầu tiên regex
. Ví dụ: hãy thử thực hiện thay thế trước đó bằng tên, nhưng bằng một phương thức mới:
String value = "In JavaRush, Diego the best, Diego is Java God".replaceAll("Diego", "Amigo");
System.out.println(value);
Đầu ra của bảng điều khiển:
In JavaRush, Amigo the best, Amigo is Java God
Như chúng ta có thể thấy, tất cả các biểu tượng đã được thay thế hoàn toàn bằng những biểu tượng cần thiết. Tôi nghĩ Amigo sẽ hài lòng =)
Biểu thức chính quy
Ở trên đã nói rằng có thể thay thế bằng cách sử dụng biểu thức chính quy. Trước tiên, chúng ta hãy tự làm rõ biểu thức chính quy là gì? Biểu thức chính quy là ngôn ngữ chính thức để tìm kiếm và thao tác các chuỗi con trong văn bản, dựa trên việc sử dụng siêu ký tự (ký tự đại diện). Nói một cách đơn giản, đó là một mẫu ký tự và siêu ký tự xác định quy tắc tìm kiếm. Ví dụ:\D
- một mẫu mô tả bất kỳ ký tự phi số nào; \d
- xác định bất kỳ ký tự số nào, cũng có thể được mô tả là [0-9]
; [a-zA-Z]
- một mẫu mô tả các ký tự Latinh từ a đến z, không phân biệt chữ hoa chữ thường; Hãy xem xét ứng dụng trong một phương thức replaceAll
lớp String
:
String value = "In JavaRush, Diego the best, Diego is Java God".replaceAll("\\s[a-zA-Z]{5}\\s", " Amigo ");
System.out.println(value);
Đầu ra của bảng điều khiển:
In JavaRush, Amigo the best, Amigo is Java God
\\s[a-zA-Z]{5}\\s
— mô tả một từ gồm 5 ký tự Latin được bao quanh bởi khoảng trắng. Theo đó, mẫu này được thay thế bằng chuỗi chúng ta đã truyền vào.
Thay thế biểu thức chính quy Java
Về cơ bản, để sử dụng các biểu thức chính quy trong Java, các khả năng củajava.util.regex
. Các lớp chính là:
Pattern
- một lớp cung cấp phiên bản đã biên dịch của một biểu thức chính quy.Matcher
— lớp này diễn giải mẫu và xác định kết quả khớp trong chuỗi mà nó nhận được.
Matcher
and 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);
Và kết luận của chúng tôi sẽ giống nhau:
In JavaRush, Amigo the best, Amigo is Java God
Bạn có thể đọc thêm về biểu thức chính quy trong bài viết này .
Thay thế để thay thếTất cả
Không còn nghi ngờ gì nữa, các phương pháp nàyreplace
rất String
ấn tượng, nhưng người ta không thể bỏ qua sự thật rằng String
nó là immutable
một đối tượng, tức là nó không thể thay đổi sau khi được tạo ra. Do đó, khi chúng ta thay thế một số phần của chuỗi bằng các phương thức replace
, chúng ta không thay đổi đối tượng String
mà mỗi lần tạo một đối tượng mới với nội dung cần thiết. Nhưng mỗi lần tạo một đối tượng mới sẽ mất nhiều thời gian phải không? Đặc biệt khi câu hỏi không phải là một vài đồ vật mà là vài trăm, thậm chí hàng nghìn. Dù muốn hay không, bạn cũng bắt đầu nghĩ về những lựa chọn thay thế. Và chúng ta có những lựa chọn thay thế nào? Hmm... Khi nói đến String
thuộc tính của nó immutable
, bạn nghĩ ngay đến các lựa chọn thay thế, nhưng không phải vậy immutable
, cụ thể là StringBuilder/StringBuffer . Như chúng ta nhớ, các lớp này không thực sự khác nhau, ngoại trừ việc StringBuffer
chúng được tối ưu hóa để sử dụng trong môi trường đa luồng, do đó StringBuilder
chúng hoạt động nhanh hơn một chút khi sử dụng đơn luồng. Dựa trên điều này, hôm nay chúng ta sẽ sử dụng StringBuilder.
Lớp này có nhiều phương thức thú vị, nhưng cụ thể là bây giờ chúng ta quan tâm đến replace
. StringBuilder replace(int start, int end, String str)
— phương thức này thay thế các ký tự trong chuỗi con của chuỗi này bằng các ký tự trong chuỗi đã chỉ định. Chuỗi con bắt đầu ở phần đầu được chỉ định và tiếp tục cho đến ký tự ở cuối chỉ mục hoặc -1
cho đến cuối chuỗi nếu không có ký tự đó tồn tại. Hãy xem một ví dụ:
StringBuilder strBuilder = new StringBuilder("Java Rush");
strBuilder.replace(5, 9, "God");
System.out.println(strBuilder);
Phần kết luận:
Java God
Như bạn có thể thấy, chúng tôi chỉ ra khoảng mà chúng tôi muốn viết chuỗi và viết một chuỗi con lên trên chuỗi trong khoảng đó. Vì vậy, bằng cách sử dụng trợ giúp, StringBuilder
chúng tôi sẽ tạo lại một phương thức tương tự replaceall java
. Nó sẽ trông như thế nào:
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();
}
Thoạt nhìn thì có vẻ đáng sợ nhưng chỉ cần tìm hiểu một chút bạn có thể hiểu rằng mọi thứ không quá phức tạp và khá logic. Chúng ta có ba lập luận:
str
— một dòng mà chúng ta muốn thay thế một số chuỗi con;oldStr
— biểu diễn các chuỗi con mà chúng ta sẽ thay thế;newStr
- chúng ta sẽ thay thế nó bằng cái gì.
if
Chúng ta cần cái đầu tiên để kiểm tra dữ liệu đến và nếu chuỗi str
trống oldStr
hoặc chuỗi con mới newStr
bằng chuỗi cũ oldStr
thì việc thực thi phương thức sẽ vô nghĩa. Do đó, chúng tôi trả về chuỗi gốc - str
. Tiếp theo, chúng tôi kiểm tra newStr
, null
và nếu đúng như vậy thì chúng tôi sẽ chuyển đổi nó thành định dạng chuỗi trống thuận tiện hơn cho chúng tôi - ""
. Sau đó chúng ta khai báo các biến chúng ta cần:
- tổng chiều dài chuỗi
str
; - độ dài chuỗi con
oldStr
; - đối tượng
StringBuilder
từ một chuỗi được chia sẻ.
StringBuilder
- indexOf
- chúng ta tìm ra chỉ mục xuất hiện đầu tiên của chuỗi con mà chúng ta quan tâm. Thật không may, tôi muốn lưu ý rằng nó indexOf
không hoạt động với các biểu thức chính quy, vì vậy phương thức cuối cùng của chúng tôi sẽ chỉ hoạt động với các lần xuất hiện của chuỗi (( Nếu chỉ số này bằng -1
, thì sẽ không còn lần xuất hiện nào của các lần xuất hiện này trong đối tượng hiện tại StringBuilder
, vì vậy chúng ta thoát khỏi phương thức với kết quả quan tâm: nó được chứa trong StringBuilder
, mà chúng ta chuyển đổi thành String
, bằng cách sử dụng toString
. Nếu chỉ số của chúng ta bằng -1
trong lần lặp đầu tiên của vòng lặp, thì chuỗi con cần được thay thế không nằm trong chuỗi chung chuỗi ban đầu. Do đó, trong tình huống như vậy, chúng ta chỉ cần trả về chuỗi chung. Tiếp theo, chúng ta có và phương thức được mô tả ở trên được sử dụng replace
để StringBuilder
sử dụng chỉ mục tìm thấy của lần xuất hiện để chỉ ra tọa độ của chuỗi con được thay thế. Vòng lặp này sẽ chạy tìm được bao nhiêu lần chuỗi con cần thay thế, nếu chuỗi chỉ gồm ký tự cần thay thế thì chỉ trong trường hợp này chúng ta có vòng lặp chạy hoàn toàn và chúng ta sẽ nhận được kết quả được StringBuilder
chuyển đổi thành chuỗi. Chúng ta cần kiểm tra tính đúng đắn của phương pháp này phải không? Hãy viết một bài kiểm tra để kiểm tra hoạt động của phương thức trong các tình huống khác nhau:
@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, "");
}
Có thể chia thành 7 test riêng biệt, mỗi test sẽ chịu trách nhiệm cho test case riêng của mình. Sau khi khởi động nó, chúng ta sẽ thấy nó có màu xanh, tức là đã thành công. Chà, có vẻ như vậy là hết rồi. Mặc dù chờ đợi nhưng chúng tôi đã nói ở trên rằng phương pháp này sẽ nhanh hơn nhiều replaceAll
so với String
. Vâng, chúng ta hãy xem:
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);
Tiếp theo, mã này được chạy ba lần và chúng tôi nhận được kết quả như sau: Đầu ra của bảng điều khiển:
Performance ratio - 5.012148941181627
Performance ratio - 5.320637176017641
Performance ratio - 4.719192686500394
Như chúng ta có thể thấy, trung bình phương pháp của chúng tôi có năng suất cao gấp 5 lần so với replaceAll
lớp cổ điển ! String
Chà, cuối cùng, chúng ta hãy thực hiện cùng một cuộc kiểm tra, nhưng có thể nói là vô ích. Nói cách khác, trong trường hợp không tìm thấy kết quả phù hợp. Hãy thay thế chuỗi tìm kiếm từ "tyu"
thành "--"
. Ba lần chạy mang lại kết quả như sau: Đầu ra bảng điều khiển:
Performance ratio - 8.789647093542246
Performance ratio - 9.177105482660881
Performance ratio - 8.520964375227406
Trung bình, hiệu suất đối với các trường hợp không tìm thấy kết quả phù hợp tăng 8,8 lần!
GO TO FULL VERSION