JavaRush /בלוג Java /Random-HE /החלף מחרוזת ב-Java

החלף מחרוזת ב-Java

פורסם בקבוצה
בעבודתו של מתכנת, לעתים קרובות כמה משימות או מרכיביהן עשויות לחזור על עצמן. לכן, היום אני רוצה לגעת בנושא שנתקל בו לעיתים קרובות בעבודה היומיומית של כל מפתח ג'אווה. החלף מחרוזת ב-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
כפי שאנו יכולים לראות, כל הסמלים הוחלפו לחלוטין באלו הדרושים. אני חושב שאמיגו יהיה מרוצה =)

ביטויים רגולריים

נאמר לעיל שאפשר להחליף באמצעות ביטוי רגולרי. ראשית, בואו נבהיר לעצמנו מהו ביטוי רגולרי? ביטויים רגולריים הם שפה רשמית לחיפוש ולטפל במחרוזות משנה בטקסט, המבוססת על שימוש בתווים מטא (תווים כלליים). במילים פשוטות, זהו דפוס של תווים ומטא-תווים שמגדיר כלל חיפוש. לדוגמה: \D- תבנית המתארת ​​כל תו שאינו דיגיטלי; \d- מגדיר כל תו מספרי, שניתן לתאר גם כ- [0-9]; [a-zA-Z]- תבנית המתארת ​​תווים לטיניים מ-a עד ת, ללא רגישות לאותיות גדולות; שקול את היישום בשיטת 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, כלומר, לא ניתן לשנותו לאחר יצירתו. לכן, כאשר אנו מחליפים חלקים של מחרוזת באמצעות מתודות replace, אנו לא משנים את האובייקט String, אלא יוצרים אחד חדש בכל פעם, עם התוכן הדרוש. אבל ליצור אובייקט חדש בכל פעם לוקח הרבה זמן, לא? במיוחד כאשר השאלה היא לא כמה חפצים, אלא כמה מאות, או אפילו אלפים. ווילי-נילי, אתה מתחיל לחשוב על חלופות. ואיזה אלטרנטיבות יש לנו? החלף מחרוזת ב-Java - 2הממ... כשזה מגיע Stringלנכס שלו immutable, אתה מיד חושב על חלופות, אבל לא immutable, כלומר StringBuilder/StringBuffer . כזכור, המחלקות הללו למעשה אינן שונות, פרט לכך שהן StringBufferמותאמות לשימוש בסביבה מרובת הליכי הליכה, כך StringBuilderשהן פועלות מעט יותר מהר בשימוש עם חוטים בודדים. בהתבסס על זה, היום נשתמש StringBuilder. ב-This class יש הרבה שיטות מעניינות, אבל ספציפית עכשיו אנחנו מעוניינים ב 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באיטרציה הראשונה של הלולאה, אז המחרוזת המשנה שצריך להחליף לא הייתה בכלל מחרוזת בתחילה. לכן, במצב כזה, אנו פשוט מחזירים את המחרוזת הכללית. לאחר מכן יש לנו והשיטה המתוארת לעיל משמשת לשימוש באינדקס replaceהמצוי StringBuilderשל ההתרחשות כדי לציין את הקואורדינטות של המחרוזת המשנה להחלפה. לולאה זו תפעל כמה פעמים נמצאות תת מחרוזות שצריך להחליף אם המחרוזת מורכבת רק מהתו שצריך להחליף אז רק במקרה הזה יש לנו הלולאה תרוץ לגמרי ונקבל את התוצאה המרה 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
כפי שאנו יכולים לראות, השיטה שלנו פרודוקטיבית פי 5 replaceAllמהכיתה הקלאסית בממוצע ! Stringובכן, לבסוף, בואו נבצע את אותו בדיקה, אבל, כביכול, לשווא. במילים אחרות, במקרה בו לא נמצא התאמה. בואו נחליף את מחרוזת החיפוש מ- "tyu"to "--". שלוש ריצות הניבו את התוצאות הבאות: פלט מסוף:
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