ฉันยุ่งอยู่กับการใช้ตรรกะของโปรเจ็กต์ในช่วงวันธรรมดา ฉันมีเวลาว่างในวันเสาร์ ดังนั้นฉันจึงหยิบ Thinking In Java เวอร์ชันภาษาอังกฤษฉบับหนาออกจากตู้หนังสือ และอ่านเกี่ยวกับการต่อออบเจ็กต์สตริง แปลโดยอ้างอิงหนังสือเล่มนี้ เพิ่มความคิดของคุณเอง และเขียนบทความนี้เพื่อบันทึกไว้
วัตถุสตริงที่ไม่เปลี่ยนรูป
ใน Java วัตถุ String จะไม่เปลี่ยนรูป ในโค้ด คุณสามารถสร้างนามแฝงได้หลายรายการสำหรับออบเจ็กต์ String แต่นามแฝงเหล่านี้ล้วนอ้างถึงสิ่งเดียวกัน
ตัวอย่างเช่น s1 และ s2 เป็นทั้งนามแฝงของวัตถุ "droidyue.com" และนามแฝงจะจัดเก็บการอ้างอิงถึงวัตถุจริง ดังนั้น s1 = s2
คัดลอกรหัสรหัสดังต่อไปนี้:
สตริง s1 = "droidyue.com";
สตริง s2 = s1;
System.out.println("s1 และ s2 มีการอ้างอิงเหมือนกัน =" + (s1 == s2));
ตัวดำเนินการโอเวอร์โหลดเดียวใน Java
ใน Java ตัวดำเนินการโอเวอร์โหลดเดียวเท่านั้นที่เกี่ยวข้องกับการต่อสตริง - นอกจากนี้ ผู้ออกแบบ Java ไม่อนุญาตให้มีโอเปอเรเตอร์อื่นโอเวอร์โหลด
การวิเคราะห์การต่อประกบ
มีค่าใช้จ่ายประสิทธิภาพจริงหรือ?
หลังจากทำความเข้าใจสองประเด็นข้างต้นแล้ว คุณอาจมีความคิดนี้ เนื่องจากวัตถุ Sting ไม่สามารถเปลี่ยนรูปได้ การเชื่อมสตริงหลาย ๆ ตัว (สามตัวขึ้นไป) จะสร้างวัตถุ String ระดับกลางที่ซ้ำซ้อนอย่างหลีกเลี่ยงไม่ได้
คัดลอกรหัสรหัสดังต่อไปนี้:
ชื่อผู้ใช้สตริง = "แอนดี้";
อายุสตริง = "24";
งานสตริง = "นักพัฒนา";
ข้อมูลสตริง = ชื่อผู้ใช้ + อายุ + งาน;
ในการรับข้อมูลข้างต้น ชื่อผู้ใช้และอายุจะถูกต่อกันเพื่อสร้างออบเจ็กต์ String ชั่วคราว t1 เนื้อหาคือ Andy24 จากนั้น t1 และงานจะถูกต่อกันเพื่อสร้างออบเจ็กต์ข้อมูลสุดท้ายที่เราต้องการ ระดับกลาง t1 ถูกสร้างขึ้น และ t1 ถูกสร้างขึ้นหลังจากนั้น หากไม่มีการนำกลับมาใช้ใหม่ มันจะครอบครองพื้นที่จำนวนหนึ่งอย่างหลีกเลี่ยงไม่ได้ หากเป็นการต่อสตริงจำนวนมาก (สมมติว่าหลายร้อยรายการ ซึ่งส่วนใหญ่เป็นการเรียก toString ของอ็อบเจ็กต์) ค่าใช้จ่ายก็จะยิ่งสูงขึ้นไปอีก และประสิทธิภาพจะลดลงอย่างมาก
การประมวลผลการเพิ่มประสิทธิภาพคอมไพเลอร์
มีค่าใช้จ่ายด้านประสิทธิภาพสูงกว่านี้จริงหรือ ไม่มีการเพิ่มประสิทธิภาพการประมวลผลพิเศษสำหรับการต่อสตริงที่ใช้กันทั่วไปหรือไม่ คำตอบคือใช่
หากโปรแกรม Java ต้องการรัน จะต้องผ่านสองช่วงเวลา คือ เวลาในการคอมไพล์และรันไทม์ ในระหว่างการคอมไพล์ คอมไพเลอร์ Java (คอมไพเลอร์) จะแปลงไฟล์ java ให้เป็น bytecode ณ รันไทม์ Java Virtual Machine (JVM) จะรันโค้ดไบต์ที่สร้างขึ้นในเวลาคอมไพล์ ในสองช่วงเวลานี้ Java ประสบความสำเร็จในการคอมไพล์ในที่เดียวและรันได้ทุกที่
มาทดลองดูว่ามีการปรับปรุงอะไรบ้างระหว่างการคอมไพล์ และเราสามารถสร้างโค้ดชิ้นหนึ่งที่อาจมีการปรับลดประสิทธิภาพได้
คัดลอกรหัสรหัสดังต่อไปนี้:
การต่อข้อมูลคลาสสาธารณะ {
โมฆะคงที่สาธารณะ main (String [] args) {
ชื่อผู้ใช้สตริง = "แอนดี้";
อายุสตริง = "24";
งานสตริง = "นักพัฒนา";
ข้อมูลสตริง = ชื่อผู้ใช้ + อายุ + งาน;
System.out.println (ข้อมูล);
-
-
คอมไพล์ Concatenation.java getConcatenation.คลาส
คัดลอกรหัสรหัสดังต่อไปนี้:
javacConcatenation.java
จากนั้นเราใช้ javap เพื่อถอดรหัสไฟล์ Concatenation.class ที่คอมไพล์แล้ว javap -c การต่อข้อมูล หากไม่พบคำสั่ง javap ให้ลองเพิ่มไดเร็กทอรีที่มี javap อยู่ในตัวแปรสภาพแวดล้อม หรือใช้พาธแบบเต็มไปยัง javap
คัดลอกรหัสรหัสดังต่อไปนี้:
17:22:04-androidyue~/workspace_adt/strings/src$ javap -c การต่อข้อมูล
เรียบเรียงจาก "Concatenation.java"
การต่อข้อมูลคลาสสาธารณะ {
การต่อสาธารณะ ();
รหัส:
0: aload_0
1: เรียกใช้พิเศษ #1 // วิธีการ java/lang/Object"<init>":()V
4: กลับ
โมฆะคงสาธารณะ main(java.lang.String[]);
รหัส:
0: ldc #2 // สตริงแอนดี้
2: astore_1
3: ldc #3 // สตริง 24
5: astore_2
6: ldc #4 // นักพัฒนาสตริง
8: astore_3
9: ใหม่ #5 // คลาส java/lang/StringBuilder
12: ซ้ำ
13: เรียกใช้พิเศษ #6 // วิธีการ java/lang/StringBuilder"<init>":()V
16: aload_1
17: เรียกใช้เสมือน #7 // วิธีการ java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: aload_2
21: เรียกใช้เสมือน #7 // วิธีการ java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: aload_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 และต่อไปนี้ Splicing เรียกวิธีการผนวกของวัตถุ StringBuilder จริง ๆ ด้วยวิธีนี้จะไม่มีปัญหาที่เรากังวลข้างต้น
การเพิ่มประสิทธิภาพคอมไพเลอร์เพียงอย่างเดียว?
เนื่องจากคอมไพเลอร์ได้ทำการปรับให้เหมาะสมที่สุดสำหรับเราแล้ว การพึ่งพาการปรับให้เหมาะสมของคอมไพเลอร์นั้นเพียงพอหรือไม่
ด้านล่างนี้เราจะดูโค้ดที่ยังไม่ได้เพิ่มประสิทธิภาพซึ่งมีประสิทธิภาพต่ำกว่า
คัดลอกรหัสรหัสดังต่อไปนี้:
โมฆะสาธารณะ implicitUseStringBuilder (ค่า String []) {
ผลลัพธ์สตริง = "";
สำหรับ (int i = 0; i <values.length; i ++) {
ผลลัพธ์ += ค่า [i];
-
System.out.println (ผลลัพธ์);
-
ใช้ javac เพื่อคอมไพล์และ javap เพื่อดู
คัดลอกรหัสรหัสดังต่อไปนี้:
โมฆะสาธารณะ implicitUseStringBuilder (java.lang.String []);
รหัส:
0: ldc #11 // สตริง
2: astore_2
3: ไอคอนst_0
4: istore_3
5: iload_3
6: aload_1
7: ความยาวอาร์เรย์
8: if_icmpge 38
11: ใหม่ #5 // คลาส java/lang/StringBuilder
14: ซ้ำ
15: เรียกใช้พิเศษ #6 // วิธีการ java/lang/StringBuilder"<init>":()V
18: aload_2
19: เรียกใช้เสมือน #7 // วิธีการ java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: aload_1
23: iload_3
24: โหลดเลย
25: เรียกใช้เสมือน #7 // วิธีการ java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: เรียกใช้เสมือน #8 // วิธีการ java/lang/StringBuilder.toString:()Ljava/lang/String;
31: astore_2
32: iinc 3, 1
35: ไปที่ 5
38: getstatic #9 // ฟิลด์ java/lang/System.out:Ljava/io/PrintStream;
41: aload_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 = StringBuilder ใหม่ ();
สำหรับ (int i = 0; i <values.length; i ++) {
result.append(ค่า[i]);
-
-
ข้อมูลที่เรียบเรียงสอดคล้องกัน
คัดลอกรหัสรหัสดังต่อไปนี้:
โมฆะสาธารณะชัด UseStringBuider (java.lang.String []);
รหัส:
0: ใหม่ #5 // คลาส java/lang/StringBuilder
3: ซ้ำ
4: เรียกใช้พิเศษ #6 // วิธีการ java/lang/StringBuilder"<init>":()V
7: astore_2
8: ไอคอนst_0
9: istore_3
10: iload_3
11: aload_1
12: ความยาวอาร์เรย์
13: if_icmpge 30
16: aload_2
17: aload_1
18: iload_3
19: โหลดเลย
20: เรียกใช้เสมือน #7 // วิธีการ java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: ป๊อป
24: iinc 3, 1
27: ไปที่ 10
30: กลับ
ดังที่เห็นจากด้านบน 13: if_icmpge 30 และ 27: goto 10 สร้างลูป และ 0: new #5 อยู่นอกลูป ดังนั้น StringBuilder จะไม่ถูกสร้างขึ้นหลายครั้ง
โดยทั่วไป เราต้องพยายามหลีกเลี่ยงการสร้าง StringBuilder ในเนื้อหาของลูปโดยปริยายหรือโดยชัดแจ้ง ดังนั้น ผู้ที่เข้าใจวิธีการคอมไพล์โค้ดและวิธีดำเนินการภายในสามารถเขียนโค้ดระดับสูงกว่าได้
หากบทความข้างต้นมีข้อผิดพลาดประการใด โปรดวิจารณ์และแก้ไขให้ถูกต้อง