ลองพิจารณาคำถามเกี่ยวกับสตริงใน Java: ความแตกต่างระหว่าง "ABC" + '/' และ "ABC" + "/" ผ่านตัวอย่างนี้เราสามารถฝึกการใช้ Javap ในเครื่องมือ JDK คำถามเดิมมีดังนี้:
ความแตกต่างระหว่างการรักษาสแลช/เป็นตัวละครหรือสตริงคืออะไร?
หนึ่งถูกใช้เป็นถ่านชนิดข้อมูลพื้นฐานและอื่น ๆ คือสตริงวัตถุ ความแตกต่างเฉพาะคืออะไร?
มันจะมีประสิทธิภาพมากขึ้นหรือไม่ที่จะปฏิบัติต่อมันเป็นตัวละคร?
string str = "abc" + '/';
และ
string str = "abc" + "/";
1. การเพิ่มประสิทธิภาพคอมไพเลอร์
ก่อนอื่นคุณควรรู้ว่าสองประโยคข้างต้นมีเอฟเฟกต์เดียวกันเนื่องจากคอมไพเลอร์จะเพิ่มประสิทธิภาพสองประโยคข้างต้นลงในข้อความต่อไปนี้:
string str = "abc/";
เราสามารถพิสูจน์สิ่งนี้ผ่าน Javap สำหรับ Javap เราสามารถอ้างถึง "5 เครื่องมือ JDK ที่นักพัฒนา Java ทุกคนควรรู้" ก่อนอื่นเราสร้างคลาส: StringOne และกรอกรหัสต่อไปนี้ในวิธีหลัก:
stringone.javastring str1 = "abc" + '/'; string str2 = "abc" + "/";
รวบรวมและเรียกใช้ผลลัพธ์ผลลัพธ์เป็นจริง ถัดไป javap ของเราเปิดตัวแล้วป้อนคำสั่งต่อไปนี้ในบรรทัดคำสั่ง:
javap -v -l stringone.class> stringone.s
จากนั้นตรวจสอบไฟล์ StringOne.s ที่สร้างขึ้น คุณจะพบว่ามีหลายบรรทัดในนั้น
StringOne.s #2 = String #20 // ABC /...# 20 = UTF8 ABC/... 0: LDC #2 // String ABC/2: Store_13: LDC #2 // String ABC/5: Store_2
คำอธิบาย: STR1 และ STR2 ทั้งคู่อ้างถึงสตริง "ABC/"
2. ใช้ Javap เพื่อวิเคราะห์ความแตกต่าง
ตอนนี้เรามาเปลี่ยนคำถามกันเถอะ ความแตกต่างระหว่างวิธี StringAddSstring และ StringAddchar ในรหัสต่อไปนี้คืออะไร?
StringTwopublic String String StringDdString (String str1, String str2) {return str1 + str2;} String String String Public Stringddar (String Str, Char CH) {return Str + Ch;}เวลานี้ Javap ใช้สำหรับการสลายตัวและเนื้อหาบางส่วนของไฟล์ที่สร้างขึ้นมีดังนี้
Stringtwo.spublic java.lang.string StringaddSstring (java.lang.string, java.lang.string); descriptor: (ljava/lang/string; ljava/lang/string;) ljava/lang/string; Flags: ACC_PBUBLIC CODE: stack = 2, locals = 3, args_size = 3 0: ใหม่ #2 // คลาส java/lang/stringbuilder 3: dup 4: invokespecial #3 // เมธอด java/lang/stringbuilder. ผนวก: (ljava/lang/string;) ljava/lang/stringbuilder; 11: aload_2 12: invokevirtual #4 // เมธอด java/lang/stringbuilder ผนวก: (ljava/lang/string;) ljava/lang/stringbuilder; 15: InvokeVirtual #5 // วิธี Java/Lang/StringBuilder ToString :() ljava/lang/string; 18: Areturnpublic java.lang.string Stringaddchar (java.lang.string, Char); descriptor: (ljava/lang/string; c) ljava/lang/string; Flags: ACC_Public Code: stack = 2, locals = 3, args_size = 3 0: ใหม่ #2 // คลาส java/lang/stringbuilder 3: dup 4: invokespecial #3 // เมธอด java/lang/stringbuilder. java/lang/stringbuilder.append: (ljava/lang/string;) ljava/lang/stringbuilder; 11: iload_2 12: invokevirtual #6 // วิธี java/lang/stringbuilder.append: (c) ljava/lang/stringbuilder; 15: invokevirtual #5 // เมธอด java/lang/stringbuilder.toString :() ljava/lang/string; 18: Areturn
ตอนนี้เราสามารถเห็นกระบวนการของการดำเนินการทั้งสองวิธีนี้อย่างชัดเจน:
Stringaddstring
ขั้นตอนของ StringAdDCHAR เหมือนกับ StringAddSstring ยกเว้นว่าเมื่อวิธีการผนวกเรียกว่าครั้งที่สองพารามิเตอร์ของ StringAddString เป็นประเภทสตริงในขณะที่พารามิเตอร์ของ StringAddchar เป็นประเภทถ่าน
3. เมธอดภาคผนวก (ถ่าน) และผนวก (สตริง) เมธอดของคลาส StringBuilder
ที่นี่เราเพียงแค่ต้องตรวจสอบซอร์สโค้ดโดยตรง (ของฉันคือซอร์สโค้ดที่มาพร้อมกับ JDK1.8.0_60) โปรดทราบว่าแม้ว่าเอกสารแสดงให้เห็นว่า StringBuilder สืบทอดมาจากวัตถุจากซอร์สโค้ด แต่ก็สืบทอดมาจากคลาสนามธรรม AbstractStringBuilder และวิธีการภาคผนวกถูกนำมาใช้โดย AbstractStringBuilder
AbstractStringBuilder.java
Public AbstractStringBuilder ผนวก (Char C) {ensureCapacityInternal (นับ + 1); // ตรวจสอบให้แน่ใจว่าอาร์เรย์สามารถรองรับค่า Count+1 อักขระ [count ++] = c; ส่งคืนสิ่งนี้;} Public AbstractStringBuilder ผนวก (String str) {ถ้า (str == null) ส่งคืนภาคผนวก (); int len = str.length (); ensurecapacityInternal (นับ + len); str.getchars (0, len, ค่า, นับ); // คัดลอกอาร์เรย์อักขระในสตริงลงในอาร์เรย์อักขระของจำนวนวัตถุนี้ += len; ส่งคืนสิ่งนี้;}ส่วนที่เหลือจะไม่ถูกโพสต์อีกต่อไป String.getChars (int, int, char [], int) ในที่สุดขึ้นอยู่กับ void arraycopy native native public (วัตถุ, int, วัตถุ, int, int) กล่าวอีกนัยหนึ่งอาจเขียนด้วยภาษา C และประสิทธิภาพควรจะดีกว่าเมื่อคัดลอกอาร์เรย์ขนาดใหญ่กว่าโปรแกรมที่เขียนใน Java ตอนนี้ให้ฉันบอกสิ่งที่ฉันเข้าใจ:
ในแง่ของหน่วยความจำโดยตรงเนื่องจากสตริงมีอาร์เรย์ถ่านอาร์เรย์ควรมีฟิลด์ความยาว ในเวลาเดียวกันคลาสสตริงมีคุณสมบัติแฮช int และวัตถุเองจะครอบครองหน่วยความจำเพิ่มเติมเพื่อจัดเก็บข้อมูลอื่น ๆ ดังนั้นสตริงจะครอบครองหน่วยความจำมากขึ้น อย่างไรก็ตามหากสตริงยาวมากค่าใช้จ่ายหน่วยความจำเหล่านี้เกือบจะถูกเพิกเฉย และถ้าสตริงสั้นมาก (มาก) ก็เป็นไปได้มากที่มีการอ้างอิงที่ใช้ร่วมกันจำนวนมากเพื่อแบ่งปันค่าใช้จ่ายหน่วยความจำเหล่านี้ดังนั้นค่าใช้จ่ายหน่วยความจำส่วนเกินยังสามารถเพิกเฉยได้
จากสแต็กการโทรเนื่องจากสตริงมีการเรียกใช้ฟังก์ชันมากกว่าหนึ่งหรือสองรายการมากกว่าถ่านที่นี่หากไม่ได้รับการพิจารณาว่าการเรียกใช้ฟังก์ชัน (รวมถึงเวลาและพื้นที่) จึงควรจะคล้ายกัน หากการเรียกใช้ฟังก์ชันค่าใช้จ่ายค่าใช้จ่าย "ABC" + '/' ควรจะดีกว่า แต่เมื่อต้องเชื่อมต่อตัวละครหลายตัว (ฉันคิดว่าสถานการณ์นี้ควรจะเป็นเรื่องธรรมดามากขึ้นใช่ไหม) เนื่องจากการใช้ถ่านต้องใช้ลูปจำนวนมากเพื่อให้การเชื่อมต่อเสร็จสมบูรณ์จำนวนฟังก์ชั่นที่เรียกว่าจะเรียกมากกว่าการใช้สตริงเท่านั้น ในเวลาเดียวกันการคัดลอกจะไม่เร็วกว่าสตริงการคัดลอกอาร์เรย์โดยตรง ดังนั้นในเวลานี้มันจะกลายเป็น "ABC" + "/" ด้วยปริมาณงานที่ใหญ่กว่า
ตอนนี้ฉันรู้สึกว่าคำถามนี้กำลังถาม: มีประสิทธิภาพมากกว่าที่จะใช้การโทรระบบเมื่ออ่านและเขียนไฟล์หรือมีประสิทธิภาพมากขึ้นในการใช้ไลบรารี IO ในไลบรารีฟังก์ชั่นมาตรฐาน โดยส่วนตัวแล้วฉันรู้สึกว่าแม้ว่าไลบรารี IO มาตรฐานจะต้องโทรติดต่อระบบในที่สุดและตัวแปรชั่วคราวและสแต็คการโทรที่ลึกกว่าจะถูกสร้างขึ้นเนื่องจากกลไกการบัฟเฟอร์ของไลบรารี IO การรับส่งข้อมูลของไลบรารี IO จะมีขนาดใหญ่ขึ้น ในทำนองเดียวกันแม้ว่าคลาสสตริงจะมีฟิลด์มากขึ้นและสแต็กฟังก์ชั่นที่ลึกกว่า แต่ปริมาณงานควรดีขึ้นเนื่องจากแคชและการคัดลอกโดยตรงมากขึ้น
คำถามใหม่
การตัดสินจากรหัสการสลายตัวของ Javap ข้างต้นเมื่อมีการเพิ่มสตริงทั้งสองเข้าด้วยกันพวกเขาจะกลายเป็นสตริงของต่อท้าย ดังนั้นในทางทฤษฎีรหัสใดต่อไปนี้มีประสิทธิภาพมากกว่า
string str1 = "abc" + "123"; // 1stringBuilder StringBuilder = new StringBuilder (); // 2stringBuilder.Append ("ABC"); StringBuilder.Append ("123"); String str2 = StringBuilder.toString ();ปล่อยให้คำถามนี้ถูกทิ้งไว้เพื่อให้ทุกคนคิด!
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ซึ่งช่วยให้คุณแยกแยะความแตกต่างของสตริง+สตริงและสตริง+ถ่านใน Java ได้ดีขึ้น ฉันหวังว่ามันจะเป็นประโยชน์กับการเรียนรู้ของทุกคน