كنت مشغولاً بالتنفيذ المنطقي للمشروع خلال أيام الأسبوع، وكان لدي بعض الوقت يوم السبت، لذلك أخرجت النسخة الإنجليزية السميكة من Thinking In Java من خزانة الكتب وقرأت عن ربط كائنات السلسلة. قم بإجراء ترجمة للإشارة إلى هذا الكتاب، وأضف أفكارك الخاصة، واكتب هذه المقالة لتسجيلها.
كائن سلسلة غير قابل للتغيير
في Java، كائنات السلسلة غير قابلة للتغيير. في التعليمات البرمجية، يمكنك إنشاء أسماء مستعارة متعددة لكائن سلسلة. ولكن هذه الأسماء المستعارة كلها تشير إلى نفس الشيء.
على سبيل المثال، s1 وs2 كلاهما أسماء مستعارة لكائن "droidyue.com"، وتخزن الأسماء المستعارة إشارات إلى الكائنات الحقيقية. إذن s1 = s2
انسخ رمز الكود كما يلي:
سلسلة s1 = "droidyue.com";
سلسلة s2 = s1؛
System.out.println("s1 وs2 لهما نفس المرجع =" + (s1 == s2));
المشغل الوحيد المثقل في Java
في Java، يرتبط عامل التشغيل الوحيد المثقل بتسلسل السلسلة. +،+=. بالإضافة إلى ذلك، لا يسمح مصممو Java بالتحميل الزائد للمشغلين الآخرين.
تحليل الربط
هل هناك حقا تكلفة الأداء؟
بعد فهم النقطتين المذكورتين أعلاه، قد تكون لديك هذه الفكرة نظرًا لأن كائنات Sting غير قابلة للتغيير، فإن ربط سلاسل متعددة (ثلاثة أو أكثر) سيؤدي حتمًا إلى إنتاج كائنات سلسلة متوسطة زائدة عن الحاجة.
انسخ رمز الكود كما يلي:
String userName = "أندي"؛
عمر السلسلة = "24"؛
وظيفة السلسلة = "المطور"؛
معلومات السلسلة = اسم المستخدم + العمر + الوظيفة؛
للحصول على المعلومات المذكورة أعلاه، سيتم ربط اسم المستخدم والعمر لإنشاء كائن سلسلة مؤقت t1، والمحتوى هو Andy24، ثم سيتم ربط t1 والوظيفة لإنشاء كائن المعلومات النهائي الذي نحتاجه، وهو يتم إنشاء وسيط t1، ويتم إنشاء t1 بعد ذلك، إذا لم يكن هناك إعادة تدوير نشطة، فسوف يشغل حتمًا قدرًا معينًا من المساحة. إذا كان الأمر يتعلق بربط العديد من السلاسل (بافتراض المئات منها، معظمها في استدعاءات toString للكائنات)، فستكون التكلفة أكبر، وسيتم تقليل الأداء كثيرًا.
معالجة الأمثل للمترجم
هل هناك بالفعل تكلفة أداء مذكورة أعلاه؟ ألا يوجد تحسين خاص للمعالجة لتسلسل السلسلة الشائع استخدامه؟
إذا أراد برنامج Java أن يعمل، فإنه يحتاج إلى المرور بفترتين، وقت الترجمة ووقت التشغيل. أثناء الترجمة، يقوم مترجم Java (المترجم) بتحويل ملف Java إلى رمز بايت. في وقت التشغيل، يقوم Java Virtual Machine (JVM) بتشغيل الكود الثانوي الذي تم إنشاؤه في وقت الترجمة. خلال هاتين الفترتين، حققت Java ما يسمى بالتجميع في مكان واحد وتشغيله في كل مكان.
دعونا نجرب التحسينات التي تم إجراؤها أثناء التجميع، ويمكننا إنشاء جزء من التعليمات البرمجية التي قد يكون لها عقوبة الأداء.
انسخ رمز الكود كما يلي:
تسلسل الطبقة العامة {
public static void main(String[] args) {
String userName = "أندي";
عمر السلسلة = "24"؛
وظيفة السلسلة = "المطور"؛
معلومات السلسلة = اسم المستخدم + العمر + الوظيفة؛
System.out.println(info);
}
}
ترجمة Concatenation.java. getConcatenation.class
انسخ رمز الكود كما يلي:
javacConcatenation.java
ثم نستخدم javap لفك ترجمة ملف Concatenation.class المترجم. javap -c التسلسل. إذا لم يتم العثور على أمر javap، ففكر في إضافة الدليل الذي يوجد به javap إلى متغير البيئة أو استخدام المسار الكامل لـ javap.
انسخ رمز الكود كما يلي:
17:22:04-androidyue~/workspace_adt/strings/src$ javap -c التسلسل
تم تجميعها من "Concatenation.java"
تسلسل الطبقة العامة {
التسلسل العام ()؛
شفرة:
0: التحميل_0
1: استدعاء خاص #1 // طريقة java/lang/Object."<init>":()V
4: العودة
public static void main(java.lang.String[]);
شفرة:
0: LDC #2 // سلسلة آندي
2:المتجر_1
3: LDC #3 // السلسلة 24
5: أستور_2
6: LDC #4 // مطور السلسلة
8: أستور_3
9: الجديد رقم 5 // class java/lang/StringBuilder
12 : دوب
13: استدعاء خاص #6 // طريقة java/lang/StringBuilder."<init>":()V
16: التحميل_1
17: استدعاء افتراضي #7 // الطريقة java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: التحميل_2
21: استدعاء افتراضي #7 // الطريقة java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: التحميل_3
25: استدعاء افتراضي #7 // الطريقة java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: استدعاء افتراضي #8 // الطريقة java/lang/StringBuilder.toString:()Ljava/lang/String;
31:المتجر 4
33: getstatic #9 // الحقل java/lang/System.out:Ljava/io/PrintStream;
36: تحميل 4
38: استدعاء افتراضي #10 // طريقة java/io/PrintStream.println:(Ljava/lang/String;)V
41 : العودة
}
من بينها، ldc، astore، وما إلى ذلك، هي تعليمات Java bytecode، المشابهة لتعليمات التجميع. تستخدم التعليقات التالية محتوى متعلقًا بـ Java للتوضيح. يمكننا أن نرى أن هناك العديد من StringBuilders أعلاه، لكننا لا نسميها بشكل صريح في كود Java. هذا هو التحسين الذي يقوم به مترجم Java. عندما يواجه مترجم Java ربط السلسلة، فإنه سينشئ كائن StringBuilder، وما يلي يقوم الربط في الواقع باستدعاء طريقة الإلحاق لكائن StringBuilder. بهذه الطريقة لن تكون هناك مشاكل كنا قلقين بشأنها أعلاه.
تحسين المترجم وحده؟
نظرًا لأن المترجم قد قام بالتحسين نيابةً عنا، فهل يكفي الاعتماد فقط على التحسين الذي قام به المترجم؟
أدناه نلقي نظرة على جزء من التعليمات البرمجية غير المحسنة ذات الأداء المنخفض
انسخ رمز الكود كما يلي:
الفراغ العام ضمنياستخدامStringBuilder(String[]values) {
نتيجة السلسلة = ""؛
لـ (int i = 0; i <values.length; i ++) {
النتيجة += القيم[i]؛
}
System.out.println(result);
}
استخدم javac للتجميع وjavap للعرض
انسخ رمز الكود كما يلي:
public void implicitUseStringBuilder(java.lang.String[]);
شفرة:
0: إل دي سي #11 // سلسلة
2: مخزن_2
3: أيقونة_0
4:istore_3
5: التحميل_3
6: تحميل_1
7: طول المصفوفة
8: if_icmpge 38
11: الجديد رقم 5 // class java/lang/StringBuilder
14 : دوب
15: استدعاء خاص #6 // طريقة java/lang/StringBuilder."<init>":()V
18: التحميل_2
19: استدعاء افتراضي #7 // الطريقة java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: التحميل_1
23: إيلود_3
24: الحمولة
25: استدعاء افتراضي #7 // الطريقة java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: استدعاء افتراضي #8 // الطريقة java/lang/StringBuilder.toString:()Ljava/lang/String;
31: أستور_2
32: إينك 3، 1
35: اذهب إلى 5
38: getstatic #9 // الحقل java/lang/System.out:Ljava/io/PrintStream;
41: تحميل_2
42: استدعاء افتراضي #10 // طريقة java/io/PrintStream.println:(Ljava/lang/String;)V
45 : العودة
من بينها 8: if_icmpge 38 و35: goto 5 تشكل حلقة. 8: if_icmpge 38 يعني أنه إذا كانت مقارنة الأعداد الصحيحة لمكدس معاملات JVM أكبر من أو تساوي (النتيجة المعاكسة لـ i <values.length)، فانتقل إلى السطر 38 (System.out). 35: الانتقال إلى 5 يعني القفز مباشرة إلى السطر 5.
ولكن هناك شيء واحد مهم جدًا هنا وهو أن إنشاء كائنات StringBuilder يحدث بين الحلقات، مما يعني عدد كائنات StringBuilder التي سيتم إنشاؤها في نفس العدد من الحلقات، وهو أمر غير جيد بوضوح. عارية رمز المستوى المنخفض.
القليل من التحسين يمكن أن يحسن أدائك على الفور.
انسخ رمز الكود كما يلي:
الفراغ العام صريحUseStringBuider (قيم السلسلة []) {
نتيجة StringBuilder = new StringBuilder();
لـ (int i = 0; i <values.length; i ++) {
result.append(values[i]);
}
}
المعلومات المجمعة المقابلة
انسخ رمز الكود كما يلي:
الفراغ العام صريحUseStringBuider(java.lang.String[]);
شفرة:
0: الجديد رقم 5 // class java/lang/StringBuilder
3: مزدوج
4: استدعاء خاص #6 // طريقة java/lang/StringBuilder."<init>":()V
7: أستور_2
8: أيقونة_0
9:istore_3
10: التحميل_3
11: تحميل_1
12: طول المصفوفة
13: if_icmpge 30
16: التحميل_2
17: التحميل_1
18: إيلود_3
19: الحمولة
20: استدعاء افتراضي #7 // الطريقة java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: البوب
24: إينك 3، 1
27: اذهب إلى 10
30: العودة
كما يتبين مما سبق، 13: if_icmpge 30 و27: goto 10 يشكلان حلقة، و0: new #5 خارج الحلقة، لذلك لن يتم إنشاء StringBuilder عدة مرات.
بشكل عام، نحتاج إلى محاولة تجنب إنشاء StringBuilder بشكل ضمني أو صريح في نص الحلقة، لذلك يمكن لأولئك الذين يفهمون كيفية تجميع التعليمات البرمجية وكيفية تنفيذها داخليًا كتابة تعليمات برمجية ذات مستوى أعلى.
إذا كان هناك أي أخطاء في المقالة أعلاه، يرجى انتقادها وتصحيحها.