ในงานของโปรแกรมเมอร์ บ่อยครั้งที่งานหรือส่วนประกอบบางอย่างอาจถูกทำซ้ำ ดังนั้นวันนี้ฉันอยากจะพูดถึงหัวข้อที่มักพบในการทำงานประจำวันของ Java Developer
สมมติว่าคุณได้รับสตริงจากวิธีการบางอย่าง และทุกอย่างดูเหมือนจะดี แต่มีบางอย่างที่ไม่เหมาะกับคุณ ตัวอย่างเช่นตัวคั่นไม่เหมาะสมและคุณต้องการตัวคั่นอื่น (หรือไม่มีเลย) สิ่งที่สามารถทำได้ในสถานการณ์เช่นนี้? โดยธรรมชาติแล้วให้ใช้วิธีการ
อืม... เมื่อพูดถึง
replace
ของคลาสString
แทนที่สตริง Java
วัตถุประเภท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
ดังที่เราเห็น มีเพียงการเกิดขึ้นครั้งแรกของ "ดิเอโก" เท่านั้นที่เปลี่ยนไป แต่สิ่งต่อมายังคงถูกละทิ้ง นั่นคือ ไม่ถูกแตะต้อง 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
ดังที่เราเห็นสัญลักษณ์ทั้งหมดถูกแทนที่ด้วยสัญลักษณ์ที่จำเป็นทั้งหมด ฉันคิดว่า Amigo จะต้องพอใจ =)
นิพจน์ทั่วไป
กล่าวไว้ข้างต้นว่าสามารถแทนที่ด้วยนิพจน์ทั่วไปได้ ก่อนอื่น เรามาทำความเข้าใจกันก่อนว่านิพจน์ทั่วไปคืออะไร? นิพจน์ทั่วไปเป็นภาษาที่เป็นทางการสำหรับการค้นหาและจัดการสตริงย่อยในข้อความ โดยอิงตามการใช้อักขระเมตา (ไวด์การ์ด) พูดง่ายๆ ก็คือรูปแบบของอักขระและอักขระเมตาที่กำหนดกฎการค้นหา ตัวอย่างเช่น:\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 แทนที่
โดยพื้นฐานแล้ว หากต้องการใช้นิพจน์ทั่วไปใน 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
คุณสามารถอ่านเพิ่มเติมเกี่ยวกับนิพจน์ทั่วไปได้ในบทความนี้
ทางเลือกอื่นในการแทนที่ทั้งหมด
ไม่ต้องสงสัยเลยว่าวิธีการต่างๆ นั้นreplace
น่าString
ประทับใจมาก แต่ไม่มีใครสามารถเพิกเฉยต่อความจริงที่ว่าString
มันเป็นimmutable
วัตถุได้ นั่นคือ มันไม่สามารถเปลี่ยนแปลงได้หลังจากการสร้างมันขึ้นมา ดังนั้น เมื่อเราแทนที่บางส่วนของสตริงโดยใช้ method replace
เราจะไม่เปลี่ยนอ็อบเจ็กต์String
แต่สร้างใหม่ทุกครั้งด้วยเนื้อหาที่จำเป็น แต่การสร้าง Object ใหม่แต่ละครั้งใช้เวลานานใช่ไหม? โดยเฉพาะอย่างยิ่งเมื่อคำถามไม่ใช่วัตถุสองสามชิ้น แต่เป็นสองสามร้อยหรือหลายพันด้วยซ้ำ วิลลี่-นิลลี่ คุณเริ่มคิดถึงทางเลือกอื่น และเรามีทางเลือกอื่นอะไรบ้าง? 
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
ในการวนซ้ำครั้งแรกของลูปแสดงว่าสตริงย่อยที่ต้องถูกแทนที่นั้นไม่ได้อยู่ในแบบทั่วไป string ในตอนแรก ดังนั้น ในสถานการณ์เช่นนี้ เราเพียงแต่คืนค่า string ทั่วไป ถัดไปเรามี และวิธีที่อธิบายไว้ข้างต้นใช้replace
สำหรับStringBuilder
การใช้ดัชนีที่พบของเหตุการณ์เพื่อระบุพิกัดของสตริงย่อยที่จะแทนที่ ลูปนี้จะรัน หลายครั้งที่พบสตริงย่อยที่ต้องถูกแทนที่ หาก string ประกอบด้วยอักขระที่ต้องการแทนที่เท่านั้นในกรณีนี้เท่านั้นที่เรามี The loop จะทำงานโดยสมบูรณ์และเราจะได้รับผลลัพธ์ที่แปลงเป็น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 เท่า! 
GO TO FULL VERSION