JavaRush /مدونة جافا /Random-AR /استبدال السلسلة في Java

استبدال السلسلة في Java

نشرت في المجموعة
في عمل المبرمج، في كثير من الأحيان قد تتكرر بعض المهام أو مكوناتها. لذلك، أود اليوم أن أتطرق إلى موضوع غالبا ما يتم مواجهته في العمل اليومي لأي مطور جافا. استبدال السلسلة في Java - 1لنفترض أنك تتلقى سلسلة معينة من طريقة معينة. ويبدو أن كل شيء فيه جيد، ولكن هناك بعض الأشياء الصغيرة التي لا تناسبك. على سبيل المثال، الفاصل غير مناسب، وتحتاج إلى بعض الآخر (أو لا على الإطلاق). ما الذي يمكن عمله في مثل هذه الحالة؟ وبطبيعة الحال، استخدم أساليب replaceالطبقة String.

استبدال سلسلة جافا

يحتوي كائن الكتابة 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]- قالب يصف الأحرف اللاتينية من الألف إلى الياء، غير حساس لحالة الأحرف؛ النظر في التطبيق في طريقة 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، فإن إمكانيات ملف 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. هذا الفصل الذي يحتوي على العديد من الأساليب المثيرة للاهتمام، ولكننا الآن على وجه التحديد مهتمون بـ 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
كما نرى، في المتوسط، طريقتنا أكثر إنتاجية بخمس مرات من replaceAllالطبقة الكلاسيكية! Stringحسنًا، أخيرًا، لنجري نفس الفحص، ولكن، إذا جاز التعبير، دون جدوى. بمعنى آخر، في حالة عدم العثور على تطابق. لنستبدل سلسلة البحث من "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