ในงานของโปรแกรมเมอร์ บ่อยครั้งที่งานหรือส่วนประกอบบางอย่างอาจถูกทำซ้ำ ดังนั้นวันนี้ฉันอยากจะพูดถึงหัวข้อที่มักพบในการทำงานประจำวันของ 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 God2.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 God3.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. แต่ตอนนี้เราสนใจเป็นพิเศษ — วิธีการนี้จะแทนที่อักขระในสตริงย่อยของลำดับนี้ด้วยอักขระในสตริงที่ระบุ สตริงย่อยเริ่มต้นที่จุดเริ่มต้นที่ระบุและดำเนินต่อไปจนกระทั่งอักขระอยู่ที่ส่วนท้ายของดัชนีหรือจนถึงจุดสิ้นสุดของลำดับหากไม่มีอักขระดังกล่าว ลองดูตัวอย่าง: replaceStringBuilder 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