JavaRush /จาวาบล็อก /Random-TH /แทนที่สตริงใน Java

แทนที่สตริงใน Java

เผยแพร่ในกลุ่ม
ในงานของโปรแกรมเมอร์ บ่อยครั้งที่งานหรือส่วนประกอบบางอย่างอาจถูกทำซ้ำ ดังนั้นวันนี้ฉันอยากจะพูดถึงหัวข้อที่มักพบในการทำงานประจำวันของ Java Developer แทนที่สตริงใน Java - 1สมมติว่าคุณได้รับสตริงจากวิธีการบางอย่าง และทุกอย่างดูเหมือนจะดี แต่มีบางอย่างที่ไม่เหมาะกับคุณ ตัวอย่างเช่นตัวคั่นไม่เหมาะสมและคุณต้องการตัวคั่นอื่น (หรือไม่มีเลย) สิ่งที่สามารถทำได้ในสถานการณ์เช่นนี้? โดยธรรมชาติแล้วให้ใช้วิธีการreplaceของคลาสString

แทนที่สตริง Java

วัตถุประเภทStringมีวิธีการเปลี่ยนสี่รูปแบบreplace:
  • replace(char, char);
  • replace(CharSequence, CharSequence);
  • replaceFirst(String, String);
  • replaceAll(String, String).
วัตถุประสงค์ของวิธีการทั้งหมดนี้เหมือนกัน - แทนที่ส่วนหนึ่งของสตริงด้วยสตริงอื่น มาดูพวกเขากันดีกว่า 1.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. ชั้นเรียนที่สำคัญคือ:
  1. Pattern- คลาสที่จัดเตรียมนิพจน์ทั่วไปในเวอร์ชันที่คอมไพล์แล้ว
  2. 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 ใหม่แต่ละครั้งใช้เวลานานใช่ไหม? โดยเฉพาะอย่างยิ่งเมื่อคำถามไม่ใช่วัตถุสองสามชิ้น แต่เป็นสองสามร้อยหรือหลายพันด้วยซ้ำ วิลลี่-นิลลี่ คุณเริ่มคิดถึงทางเลือกอื่น และเรามีทางเลือกอื่นอะไรบ้าง? แทนที่สตริงใน Java - 2อืม... เมื่อพูดถึง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 เท่า! แทนที่สตริงใน Java - 4
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION