คำนำ
ฉันแก้ไขในเดือนมีนาคม 2559 และกลับมาอีกครั้งว่าทำไมฉันต้องปรับรหัสให้เหมาะสมตามงานของฉันเองและประสบการณ์การเรียนรู้ประจำวันของฉัน ก่อนที่ฉันจะแก้ไขคำแถลงของฉันเป็นเช่นนี้:
เช่นเดียวกับกุ้งกินปลาวาฬบางทีการกินกุ้งหนึ่งหรือสองตัวนั้นไม่ได้มีประสิทธิภาพมากสำหรับปลาวาฬ แต่ถ้าคุณกินกุ้งมากเกินไปปลาวาฬจะเต็มตามธรรมชาติ การเพิ่มประสิทธิภาพรหัสเหมือนกัน บางทีการปรับให้เหมาะสมอย่างหนึ่งหรือสองครั้งนั้นมีความสำคัญเพียงเล็กน้อยในการปรับปรุงประสิทธิภาพการทำงานของรหัส แต่ตราบใดที่คุณสามารถให้ความสนใจกับการเพิ่มประสิทธิภาพรหัสได้ทุกที่โดยทั่วไปแล้วจะมีประโยชน์มากสำหรับการปรับปรุงประสิทธิภาพการทำงานของรหัส
มุมมองนี้ในมุมมองปัจจุบันเป็นเหตุผลในการเพิ่มประสิทธิภาพรหัส แต่ไม่ถูกต้องอย่างสมบูรณ์ วันนี้ด้วยการพัฒนาเทคโนโลยีเชิงกลเซิร์ฟเวอร์มักจะมี 8 คอร์, 16 คอร์, และซีพียู 64 บิตและประสิทธิภาพการดำเนินการรหัสสูงมาก StringBuilder แทนที่ StringBuffer และ ArrayList แทนที่เวกเตอร์ซึ่งปรับปรุงประสิทธิภาพการทำงานของรหัสโดยจิ๋ว แม้ว่าคุณจะสังเกตเห็นทุกจุดในโครงการคุณไม่สามารถเห็นการเปลี่ยนแปลงที่ชัดเจนในการดำเนินการรหัส
ฉันคิดว่าบทบาทที่สำคัญที่สุดของการเพิ่มประสิทธิภาพรหัสควร: เพื่อหลีกเลี่ยงข้อผิดพลาดที่ไม่รู้จัก ในระหว่างการใช้รหัสออนไลน์ข้อผิดพลาดที่ไม่คาดคิดจำนวนมากมักเกิดขึ้นเนื่องจากสภาพแวดล้อมออนไลน์และสภาพแวดล้อมการพัฒนานั้นแตกต่างกันมากและการวางตำแหน่งข้อผิดพลาดมักจะเป็นเหตุผลที่น้อยมาก อย่างไรก็ตามเพื่อแก้ไขข้อผิดพลาดนี้เราต้องตรวจสอบตนเองก่อนจากนั้นจัดทำไฟล์คลาสที่จะถูกแทนที่ระงับธุรกิจและรีสตาร์ท สำหรับโครงการที่เป็นผู้ใหญ่โครงการสุดท้ายมีผลกระทบใหญ่มากซึ่งหมายความว่าผู้ใช้ไม่สามารถเข้าถึงแอปพลิเคชันในช่วงเวลานี้ ดังนั้นเมื่อเขียนโค้ดการให้ความสนใจกับรายละเอียดต่าง ๆ จากแหล่งที่มาการชั่งน้ำหนักและการใช้ตัวเลือกที่ดีที่สุดจะหลีกเลี่ยงข้อผิดพลาดที่ไม่รู้จักอย่างมากและลดภาระงานในระยะยาวอย่างมาก
เป้าหมายของการเพิ่มประสิทธิภาพรหัสคือ:
1. ลดปริมาณของรหัส
2. ปรับปรุงประสิทธิภาพของการใช้งานรหัส
เนื้อหาบางส่วนของบทความนี้มาจากอินเทอร์เน็ตและบางส่วนมาจากการทำงานประจำวันและการศึกษา แน่นอนว่านี่ไม่สำคัญ สิ่งสำคัญคือรายละเอียดของการเพิ่มประสิทธิภาพรหัสเหล่านี้มีประโยชน์อย่างแท้จริงหรือไม่ จากนั้นบทความนี้จะได้รับการปรับปรุงเป็นเวลานาน ตราบใดที่คุณพบรายละเอียดการเพิ่มประสิทธิภาพรหัสที่ควรค่าแก่การแบ่งปันบทความนี้จะได้รับการอัปเดตเป็นครั้งคราว
รายละเอียดการเพิ่มประสิทธิภาพรหัส
(1) พยายามระบุตัวดัดแปลงสุดท้ายของคลาสและวิธีการให้มากที่สุด
ชั้นเรียนที่มีตัวดัดแปลงสุดท้ายไม่สามารถหาได้ ใน Java Core API มีตัวอย่างมากมายของการใช้ขั้นสุดท้ายเช่น java.lang.string และทั้งชั้นเรียนถือเป็นที่สิ้นสุด การระบุตัวดัดแปลงสุดท้ายสำหรับคลาสสามารถป้องกันไม่ให้คลาสถูกสืบทอดและการระบุตัวปรับเปลี่ยนสุดท้ายสำหรับวิธีการสามารถป้องกันไม่ให้วิธีการถูกแทนที่ หากมีการระบุคลาสเป็นขั้นสุดท้ายวิธีการทั้งหมดของคลาสนั้นถือเป็นที่สิ้นสุด คอมไพเลอร์ Java จะมองหาโอกาสในการอินไลน์วิธีสุดท้ายทั้งหมด อินไลน์มีความสำคัญอย่างยิ่งในการปรับปรุงประสิทธิภาพการทำงานของ Java สำหรับรายละเอียดดูการเพิ่มประสิทธิภาพ Java Runtime การเคลื่อนไหวนี้สามารถปรับปรุงประสิทธิภาพได้โดยเฉลี่ย 50%
(2) พยายามนำวัตถุกลับมาใช้ซ้ำ
โดยเฉพาะอย่างยิ่งสำหรับการใช้วัตถุสตริงควรใช้ StringBuilder/StringBuffer แทนเมื่อเกิดการต่อการเชื่อมต่อสตริง เนื่องจากเครื่องเสมือน Java ไม่เพียง แต่ต้องใช้เวลาในการสร้างวัตถุพวกเขาอาจต้องใช้เวลาในการรวบรวมและประมวลผลวัตถุเหล่านี้ในอนาคตการสร้างวัตถุจำนวนมากเกินไปจะมีผลกระทบอย่างมากต่อประสิทธิภาพของโปรแกรม
(3) ใช้ตัวแปรท้องถิ่นให้มากที่สุด
พารามิเตอร์ที่ส่งผ่านเมื่อเรียกวิธีการและตัวแปรชั่วคราวที่สร้างขึ้นในการโทรจะถูกเก็บไว้ในสแต็กซึ่งเร็วกว่า ตัวแปรอื่น ๆ เช่นตัวแปรคงที่ตัวแปรอินสแตนซ์ ฯลฯ ถูกสร้างขึ้นในกองซึ่งช้ากว่า นอกจากนี้เมื่อตัวแปรที่สร้างขึ้นในสแต็กเสร็จสิ้นเนื้อหาเหล่านี้จะหายไปและไม่จำเป็นต้องมีการรวบรวมขยะเพิ่มเติม
(4) ปิดการไหลในเวลา
ในระหว่างการเขียนโปรแกรม Java ให้ระวังเมื่อทำการเชื่อมต่อฐานข้อมูลและการดำเนินการสตรีมมิ่ง I/O หลังการใช้งานให้ปิดทันเวลาเพื่อปล่อยทรัพยากร เนื่องจากการทำงานของวัตถุขนาดใหญ่เหล่านี้จะทำให้เกิดค่าใช้จ่ายของระบบขนาดใหญ่และหากคุณไม่ระวังมันจะนำไปสู่ผลกระทบร้ายแรง
(5) ลดการคำนวณตัวแปรซ้ำ ๆ ซ้ำ ๆ
เพื่อชี้แจงแนวคิดแม้ว่าจะมีเพียงหนึ่งประโยคในวิธีการ แต่ก็ยังคงใช้งานอยู่รวมถึงการสร้างเฟรมสแต็กปกป้องไซต์เมื่อเรียกวิธีการและกู้คืนไซต์เมื่อเรียกวิธีการ ตัวอย่างเช่นการดำเนินการต่อไปนี้:
สำหรับ (int i = 0; i <list.size (); i ++) {... }ขอแนะนำให้แทนที่ด้วย:
สำหรับ (int i = 0, length = list.size (); i <length; i ++) {... }ด้วยวิธีนี้เมื่อ list.size () มีขนาดใหญ่มากมันจะลดการบริโภคจำนวนมาก
(6) พยายามใช้กลยุทธ์การโหลดขี้เกียจนั่นคือสร้างขึ้นเมื่อจำเป็น
ตัวอย่างเช่น:
string str = "aaa"; ถ้า (i == 1) {list.add (str);}ขอแนะนำให้แทนที่ด้วย:
if (i == 1) {string str = "aaa"; list.add (str);}(7) ใช้ความผิดปกติด้วยความระมัดระวัง
ความผิดปกติไม่ดีต่อประสิทธิภาพ ในการโยนข้อยกเว้นคุณต้องสร้างวัตถุใหม่ก่อน ตัวสร้างของอินเทอร์เฟซที่สามารถหมุนได้เรียกใช้วิธีการซิงโครไนซ์ในท้องถิ่นที่ชื่อว่า FillinstackTrace () วิธีการ FillinstackTrace () ตรวจสอบสแต็กและรวบรวมข้อมูลการติดตามการโทร ตราบใดที่มีการโยนข้อยกเว้นเครื่องเสมือน Java จะต้องปรับสแต็กการโทรเนื่องจากวัตถุใหม่ถูกสร้างขึ้นระหว่างการประมวลผล ข้อยกเว้นสามารถใช้สำหรับการจัดการข้อผิดพลาดเท่านั้นและไม่ควรใช้เพื่อควบคุมการไหลของโปรแกรม
(8) อย่าใช้ลอง ... จับ ... ในลูปควรวางไว้ที่ชั้นนอกสุดสุด
ตามความคิดเห็นที่นำเสนอโดยชาวเน็ตฉันคิดว่านี่เป็นสิ่งที่ควรค่าแก่การพูดคุย
(9) หากความยาวของเนื้อหาที่จะเพิ่มสามารถประมาณได้ให้ระบุความยาวเริ่มต้นสำหรับการรวบรวมและคลาสเครื่องมือที่ใช้ในอาร์เรย์บนเลเยอร์พื้นฐาน
ตัวอย่างเช่น arraylist, linkedllist, stringbuilder, stringbuffer, hashmap, hashset ฯลฯ ใช้ Stringbuilder เป็นตัวอย่าง:
StringBuilder () // เริ่มต้นจัดสรร 16 อักขระของ Space StringBuilder (ขนาด int) // เริ่มต้นจัดสรร 16 อักขระของ Space StringBuilder (String Str) // การจัดสรรเริ่มต้น 16 อักขระ + str.length () Space
ความสามารถในการเริ่มต้นของมันสามารถตั้งค่าผ่านตัวสร้างของคลาส (ที่นี่เราไม่เพียง แต่อ้างถึง StringBuilder ด้านบน) ซึ่งสามารถปรับปรุงประสิทธิภาพได้อย่างมีนัยสำคัญ ตัวอย่างเช่น StringBuilder ความยาวแสดงจำนวนอักขระที่สตริงปัจจุบันสามารถรักษาได้ เนื่องจากเมื่อ StringBuilder มีความจุสูงสุดมันจะเพิ่มความสามารถของตัวเองเป็น 2 ครั้งและเพิ่ม 2 เมื่อใดก็ตามที่ StringBuilder ถึงความจุสูงสุดมันจะต้องสร้างอาร์เรย์อักขระใหม่และคัดลอกเนื้อหาอาร์เรย์ตัวละครเก่าไปยังอาร์เรย์ตัวละครใหม่ ลองจินตนาการว่าถ้าคุณสามารถประเมินได้ว่า 5,000 อักขระจะถูกเก็บไว้ในอาร์เรย์อักขระโดยไม่ระบุความยาวพลังของ 2 ที่ใกล้เคียงที่สุดถึง 5,000 คือ 4096 และ 2 ที่เพิ่มเข้ามาในการขยายแต่ละครั้งโดยไม่คำนึงถึง 2
ขึ้นอยู่กับ 4096 ใช้สำหรับอาร์เรย์อักขระขนาด 8194 ซึ่งเพิ่มอาร์เรย์อักขระขนาดใหญ่ถึง 12290 ในครั้งเดียว หากคุณสามารถระบุอาร์เรย์อักขระขนาด 5,000 ที่เริ่มต้นคุณจะประหยัดพื้นที่มากกว่าสองเท่าในการคัดลอกอักขระ 4096 ต้นฉบับลงในอาร์เรย์อักขระใหม่
สิ่งนี้ไม่เพียงเสียพื้นที่หน่วยความจำ แต่ยังลดประสิทธิภาพการทำงานของรหัส ดังนั้นจึงไม่ผิดที่จะตั้งค่าความสามารถในการเริ่มต้นที่สมเหตุสมผลสำหรับการรวบรวมและคลาสเครื่องมือที่ใช้ในอาร์เรย์พื้นฐานซึ่งจะให้ผลลัพธ์ทันที อย่างไรก็ตามโปรดทราบว่าคอลเลกชันเช่น HashMap ที่นำไปใช้ในอาร์เรย์ + รายการที่เชื่อมโยงไม่ควรตั้งขนาดเริ่มต้นเท่ากันกับขนาดโดยประมาณของคุณเนื่องจากความเป็นไปได้ของวัตถุเดียวที่เชื่อมต่อกับตารางเกือบ 0 แนะนำให้ตั้งค่าเริ่มต้นเป็น N 2
(10) เมื่อคัดลอกข้อมูลจำนวนมากให้ใช้คำสั่ง system.arraycopy ()
(11) การคูณและการแบ่งใช้การดำเนินการกะ
ตัวอย่างเช่น:
สำหรับ (val = 0; val <100000; val += 5) {a = val * 8; b = val / 2;}การใช้การทำงานแบบกะสามารถปรับปรุงประสิทธิภาพได้อย่างมากเนื่องจากที่ด้านล่างของคอมพิวเตอร์การดำเนินการวางตำแหน่งนั้นสะดวกและเร็วที่สุดดังนั้นจึงขอแนะนำให้ปรับเปลี่ยนให้เป็น:
สำหรับ (val = 0; val <100000; val += 5) {a = val << 3; b = val >> 1;}แม้ว่าการดำเนินการกะนั้นรวดเร็ว แต่อาจทำให้รหัสเข้าใจยากดังนั้นจึงเป็นการดีที่สุดที่จะเพิ่มความคิดเห็นที่สอดคล้องกัน
(12) อย่าสร้างการอ้างอิงวัตถุอย่างต่อเนื่องในลูป
ตัวอย่างเช่น:
สำหรับ (int i = 1; i <= count; i ++) {object obj = วัตถุใหม่ (); -วิธีการนี้จะทำให้การอ้างอิงวัตถุวัตถุนับมีอยู่ในหน่วยความจำ หากการนับมีขนาดใหญ่มันจะใช้หน่วยความจำ ขอแนะนำให้เปลี่ยนเป็น:
Object obj = null; สำหรับ (int i = 0; i <= count; i ++) {obj = วัตถุใหม่ ();}ด้วยวิธีนี้มีการอ้างอิงวัตถุวัตถุเดียวในหน่วยความจำ ทุกครั้งที่มีการใช้วัตถุใหม่ () วัตถุอ้างอิงวัตถุจะชี้ไปที่วัตถุที่แตกต่างกัน แต่มีเพียงวัตถุเดียวในหน่วยความจำซึ่งช่วยประหยัดพื้นที่หน่วยความจำได้อย่างมาก
(13) ขึ้นอยู่กับการพิจารณาประสิทธิภาพและการตรวจสอบประเภทควรใช้อาร์เรย์ให้มากที่สุด ควรใช้ ArrayList เฉพาะในกรณีที่ไม่สามารถกำหนดขนาดอาร์เรย์ได้
(14) ลองใช้ HashMap, ArrayList และ StringBuilder หากไม่จำเป็นสำหรับความปลอดภัยของเธรดก็ไม่แนะนำให้ใช้แฮชแต้มเวกเตอร์และสตริงบัฟเฟอร์ สามหลังมีค่าใช้จ่ายด้านประสิทธิภาพเนื่องจากการใช้กลไกการซิงโครไนซ์
(15) อย่าประกาศว่าอาร์เรย์เป็นแบบคงที่สาธารณะสุดท้าย
เนื่องจากสิ่งนี้ไม่มีความหมายจึงกำหนดเฉพาะการอ้างอิงว่าเป็นแบบคงที่และเนื้อหาของอาร์เรย์ยังสามารถเปลี่ยนแปลงได้ตามต้องการ การประกาศอาร์เรย์เป็นสาธารณะเป็นช่องโหว่ด้านความปลอดภัยซึ่งหมายความว่าอาเรย์สามารถเปลี่ยนแปลงได้โดยชั้นเรียนภายนอก
(16) พยายามใช้กรณีเดียวในโอกาสที่เหมาะสม
การใช้ Singletons สามารถลดภาระการโหลดลดเวลาในการโหลดและปรับปรุงประสิทธิภาพการโหลด แต่ไม่ใช่ทุกที่ที่เหมาะสำหรับ Singletons พูดง่ายๆคือ Singletons ส่วนใหญ่ใช้กับสามด้านต่อไปนี้:
ควบคุมการใช้ทรัพยากรควบคุมการสร้างอินสแตนซ์การควบคุมการเข้าถึงที่เกิดขึ้นพร้อมกันของทรัพยากรผ่านการซิงโครไนซ์เธรดเพื่อให้บรรลุวัตถุประสงค์ในการประหยัดทรัพยากรเพื่อควบคุมการแบ่งปันข้อมูลและเปิดใช้งานการสื่อสารระหว่างกระบวนการหรือเธรดที่ไม่เกี่ยวข้องหลายอย่างโดยไม่ต้องสร้างการเชื่อมโยงโดยตรง
(17) พยายามหลีกเลี่ยงการใช้ตัวแปรคงที่ตามต้องการ
คุณควรรู้ว่าเมื่อวัตถุถูกอ้างอิงโดยตัวแปรที่กำหนดเป็นแบบคงที่ GC มักจะไม่รีไซเคิลหน่วยความจำฮีปที่ครอบครองโดยวัตถุเช่น::
คลาสสาธารณะ A {ส่วนตัวคงที่ b B = ใหม่ b (); -ในเวลานี้วัฏจักรชีวิตของตัวแปรคงที่ B นั้นเหมือนกับคลาส A หากคลาส A ไม่ได้ถูกถอนการติดตั้งวัตถุ B ที่ชี้ไปที่การอ้างอิง B จะอยู่ในหน่วยความจำจนกว่าโปรแกรมจะสิ้นสุดลง
(18) ล้างช่วงเวลาที่ไม่จำเป็นอีกต่อไป
เพื่อล้างเซสชันที่ไม่ได้ใช้งานอีกต่อไปเซิร์ฟเวอร์แอปพลิเคชันจำนวนมากมีการหมดเวลาเซสชันเริ่มต้นโดยปกติ 30 นาที เมื่อแอปพลิเคชันเซิร์ฟเวอร์ต้องการบันทึกเซสชันมากขึ้นหากมีหน่วยความจำไม่เพียงพอระบบปฏิบัติการจะถ่ายโอนส่วนหนึ่งของข้อมูลไปยังดิสก์ เซิร์ฟเวอร์แอปพลิเคชันอาจทิ้งเซสชันที่ไม่ได้ใช้งานไปยังดิสก์ตามอัลกอริทึม MRU (บ่อยที่สุดที่ใช้บ่อยที่สุด) และอาจส่งข้อยกเว้นหน่วยความจำไม่เพียงพอ หากเซสชั่นจะถูกทิ้งลงในดิสก์จะต้องมีการจัดลำดับก่อน ในกลุ่มขนาดใหญ่วัตถุที่เป็นอนุกรมมีราคาแพง ดังนั้นเมื่อไม่จำเป็นต้องใช้เซสชั่นอีกต่อไปวิธีการ httpsession ของ httpsession จะถูกเรียกในเวลาเพื่อล้างเซสชัน
(19) คอลเลกชันที่ใช้อินเทอร์เฟซแบบสุ่มเช่น arrayList ควรใช้สิ่งที่พบได้บ่อยที่สุดสำหรับลูปแทน foreach loop เพื่อสำรวจ
แนะนำโดย JDK ให้กับผู้ใช้ คำอธิบายของ JDK API เกี่ยวกับอินเทอร์เฟซแบบสุ่มคือ: การใช้อินเทอร์เฟซแบบสุ่มใช้เพื่อระบุว่ารองรับการเข้าถึงแบบสุ่มอย่างรวดเร็ว วัตถุประสงค์หลักของอินเทอร์เฟซนี้คือการอนุญาตให้อัลกอริทึมทั่วไปเปลี่ยนพฤติกรรมของพวกเขาเพื่อให้สามารถให้ประสิทธิภาพที่ดีเมื่อนำไปใช้กับรายการการเข้าถึงแบบสุ่มหรือต่อเนื่อง ประสบการณ์ในทางปฏิบัติแสดงให้เห็นว่าหากอินสแตนซ์ของคลาสที่ใช้อินเทอร์เฟซแบบสุ่มจะเข้าถึงแบบสุ่มประสิทธิภาพของการใช้งานธรรมดาสำหรับลูปจะสูงกว่าการใช้ลูป foreach; ในทางกลับกันหากเข้าถึงตามลำดับมันจะมีประสิทธิภาพมากขึ้นในการใช้ตัววนซ้ำ คุณสามารถใช้รหัสที่คล้ายกับต่อไปนี้เพื่อทำการตัดสิน:
if (รายการอินสแตนซ์ของแบบสุ่ม) {สำหรับ (int i = 0; i <list.size (); i ++) {}} else {iterator <?> iterator = list.iterable (); ในขณะที่ (iterator.hasnext ()) {iterator.next ()}}หลักการดำเนินการพื้นฐานของ Foreach Loop คือตัววนซ้ำดู Java Syntax Sugar 1: พารามิเตอร์ความยาวตัวแปรและหลักการวนรอบ foreach ดังนั้นในช่วงครึ่งหลังของประโยค "ในทางกลับกันหากเข้าถึงได้ตามลำดับการใช้ตัววนซ้ำจะมีประสิทธิภาพมากขึ้น" หมายความว่าอินสแตนซ์ของคลาสที่เข้าถึงตามลำดับจะถูกสำรวจโดยใช้ foreach loop
(20) ใช้บล็อกรหัสแบบซิงโครนัสแทนวิธีการซิงโครไนซ์
สิ่งนี้มีการระบุไว้อย่างชัดเจนในบล็อกวิธีการล็อคแบบซิงโครไนซ์ในบทความในโมดูลมัลติเธรด หากไม่สามารถพิจารณาได้ว่าวิธีการทั้งหมดจะต้องซิงโครไนซ์ให้ลองใช้บล็อกรหัสที่ซิงโครไนซ์เพื่อหลีกเลี่ยงการซิงโครไนซ์รหัสเหล่านั้นที่ไม่จำเป็นต้องซิงโครไนซ์ซึ่งส่งผลต่อประสิทธิภาพการดำเนินการของรหัส
(21) ประกาศค่าคงที่เป็นสุดท้ายและตั้งชื่อในเมืองหลวง
ด้วยวิธีนี้เนื้อหาเหล่านี้สามารถใส่ลงในพูลคงที่ในระหว่างการรวบรวมหลีกเลี่ยงการคำนวณค่าคงที่ที่สร้างขึ้นในระหว่างการรันไทม์ นอกจากนี้การตั้งชื่อชื่อของค่าคงที่ในทุนยังสามารถอำนวยความสะดวกความแตกต่างระหว่างค่าคงที่และตัวแปร
(22) อย่าสร้างวัตถุที่ไม่ได้ใช้อย่านำเข้าชั้นเรียนที่ไม่ได้ใช้
สิ่งนี้ไม่สมเหตุสมผล หาก "ค่าของตัวแปรท้องถิ่นที่ฉันไม่ได้ใช้" และ "การนำเข้า java.util ไม่เคยใช้" ปรากฏในรหัสแล้วโปรดลบเนื้อหาที่ไร้ประโยชน์เหล่านี้
(23) หลีกเลี่ยงการใช้การสะท้อนระหว่างการทำงานของโปรแกรม
สำหรับข้อมูลเพิ่มเติมดูการสะท้อน การสะท้อนกลับเป็นฟังก์ชั่นที่ทรงพลังมากโดย Java ให้กับผู้ใช้ ฟังก์ชั่นที่ทรงพลังมักหมายถึงประสิทธิภาพต่ำ ไม่แนะนำให้ใช้กลไกการสะท้อนบ่อยครั้งในระหว่างการทำงานของโปรแกรมโดยเฉพาะอย่างยิ่งวิธีการเรียกใช้วิธีการ หากมีความจำเป็นอย่างแท้จริงวิธีการชี้นำคือการสร้างอินสแตนซ์วัตถุผ่านการสะท้อนและนำไปใช้เป็นหน่วยความจำเมื่อโครงการเริ่มต้นขึ้น ผู้ใช้สนใจเฉพาะความเร็วในการตอบสนองที่เร็วที่สุดเมื่อมีปฏิสัมพันธ์กับเพียร์และไม่สนใจว่าจะใช้เวลานานแค่ไหนในการเริ่มต้นโครงการเพียร์
(24) ใช้พูลการเชื่อมต่อฐานข้อมูลและพูลเธรด
สระทั้งสองใช้เพื่อนำวัตถุกลับมาใช้ใหม่ก่อนหน้านี้หลีกเลี่ยงการเปิดและปิดการเชื่อมต่อบ่อยครั้งและหลังหลีกเลี่ยงการสร้างและทำลายเธรดบ่อยครั้งบ่อยครั้ง
(25) ใช้กระแสอินพุตและเอาต์พุตบัฟเฟอร์สำหรับการดำเนินการ IO
กระแสอินพุตและเอาต์พุตบัฟเฟอร์ ได้แก่ bufferedreader, bufferedWriter, bufferedInputStream, bufferedOutputStream ซึ่งสามารถปรับปรุงประสิทธิภาพของ IO ได้อย่างมาก
(26) ใช้ ArrayList สำหรับฉากที่มีการแทรกแบบต่อเนื่องและการเข้าถึงแบบสุ่มมากขึ้นและใช้ LinkedList สำหรับฉากที่มีการลบองค์ประกอบมากขึ้นและการแทรกระดับกลาง
สิ่งนี้เป็นที่รู้จักกันโดยการทำความเข้าใจหลักการของ ArrayList และ LinkedList
(27) อย่าปล่อยให้พารามิเตอร์อย่างเป็นทางการมากเกินไปในวิธีการสาธารณะ
วิธีการสาธารณะเป็นวิธีการที่มอบให้กับโลกภายนอก หากคุณให้วิธีการเหล่านี้มากเกินไปพารามิเตอร์อย่างเป็นทางการมีสองข้อเสียหลัก:
การละเมิดแนวคิดการเขียนโปรแกรมเชิงวัตถุ Java เน้นว่าทุกอย่างเป็นวัตถุ พารามิเตอร์ที่เป็นทางการมากเกินไปและไม่ตรงกับแนวคิดการเขียนโปรแกรมเชิงวัตถุ พารามิเตอร์จำนวนมากเกินไปจะนำไปสู่การเพิ่มขึ้นของความน่าจะเป็นของข้อผิดพลาดในการเรียกวิธีการ
สำหรับจำนวน "มากเกินไป" หมายถึง 3 หรือ 4 ตัวอย่างเช่นเราใช้ JDBC ในการเขียนวิธีการ insertstudentInfo มีข้อมูลนักเรียน 10 สาขาที่จะแทรกลงในตารางนักเรียน พารามิเตอร์ทั้ง 10 นี้สามารถห่อหุ้มในคลาสเอนทิตีเป็นพารามิเตอร์อย่างเป็นทางการของวิธีการแทรก
(28) เมื่อตัวแปรสตริงและค่าคงที่สตริงเท่ากับจะถูกเขียนไว้ด้านหน้าของค่าคงที่สตริง
นี่เป็นเคล็ดลับที่ค่อนข้างธรรมดาหากมีรหัสต่อไปนี้:
string str = "123"; ถ้า (str.equals ("123")) {... }ขอแนะนำให้แก้ไขเป็น:
string str = "123"; ถ้า ("123" .equals (str)) {... }สิ่งนี้สามารถหลีกเลี่ยงข้อยกเว้นตัวชี้ว่างเปล่าได้
(29) โปรดทราบว่าใน Java ไม่มีความแตกต่างระหว่าง IF (i == 1) และถ้า (1 == i) แต่จากมุมมองของนิสัยการอ่านขอแนะนำให้ใช้อดีต
บางครั้งมีคนถามว่ามีความแตกต่างระหว่าง "ถ้า (i == 1)" และ "ถ้า (1 == i)" หรือไม่? สิ่งนี้เริ่มต้นด้วย C/C ++
ใน C/C ++ เงื่อนไขการตัดสิน "if (i == 1)" นั้นถูกต้องและขึ้นอยู่กับ 0 และไม่ใช่ 0 0 หมายถึงเท็จและไม่ใช่ 0 หมายถึงความจริง หากมีรหัสชิ้นหนึ่ง:
int i = 2; if (i == 1) {... } else {... }C/C ++ ตัดสินว่า "i == 1" ไม่ถูกต้องดังนั้นจึงเป็นตัวแทนของ 0 นั่นคือเท็จ แต่ถ้า:
int i = 2; ถ้า (i = 1) {... } else {... }หากโปรแกรมเมอร์เขียนโดยไม่ตั้งใจ "ถ้า (i == 1)" เป็น "ถ้า (i = 1)" จะมีปัญหา กำหนด I ถึง 1 ภายใน if หากคุณพิจารณาว่าเนื้อหาในนั้นไม่ใช่ 0, The Returned True แต่ฉันเห็นได้ชัดว่า 2 และค่าเปรียบเทียบคือ 1 เท็จที่ควรส่งคืน สถานการณ์นี้มีแนวโน้มที่จะเกิดขึ้นในการพัฒนา C/C ++ และจะทำให้เกิดข้อผิดพลาดที่ยากต่อการเข้าใจ ดังนั้นเพื่อหลีกเลี่ยงการดำเนินการที่ได้รับมอบหมายที่ไม่ถูกต้องของนักพัฒนาซอฟต์แวร์ในคำสั่ง IF ขอแนะนำให้เขียนคำสั่ง IF เป็น:
int i = 2; if (1 == i) {... } else {... }ด้วยวิธีนี้แม้ว่าผู้พัฒนาจะเขียนโดยบังเอิญว่า "1 = i" คอมไพเลอร์ C/C ++ สามารถตรวจสอบได้โดยเร็วที่สุดเพราะเราสามารถกำหนดค่าตัวแปร I ถึง 1 แต่เราไม่สามารถกำหนดค่าคงที่ 1 ให้กับ i
อย่างไรก็ตามใน Java, "if (i = 1)" ไวยากรณ์ของ C/C ++ นั้นเป็นไปไม่ได้ที่จะปรากฏขึ้นเพราะเมื่อเขียนไวยากรณ์นี้ Java จะรวบรวมและรายงานข้อผิดพลาด อย่างไรก็ตามแม้ว่าจะไม่มีความแตกต่างทางความหมายระหว่าง "ถ้า (i == 1)" และ "ถ้า (1 == i)" ใน Java มันจะดีกว่าที่จะแนะนำโดยใช้อดีตในแง่ของนิสัยการอ่าน
(30) อย่าใช้วิธี ToString () บนอาร์เรย์
มาดูสิ่งที่พิมพ์โดยใช้ toString () สำหรับอาร์เรย์:
int i = 2; if (1 == i) {... } else {... }กลับกลายเป็นว่า:
ฉัน@18a992f
ความตั้งใจดั้งเดิมคือการพิมพ์เนื้อหาของอาร์เรย์ แต่อาจทำให้เกิดข้อยกเว้นตัวชี้โมฆะเนื่องจากการอ้างอิงอาร์เรย์เป็นโมฆะ อย่างไรก็ตามแม้ว่ามันจะไม่สมเหตุสมผลกับอาร์เรย์ toString () เนื้อหาในคอลเลกชันสามารถพิมพ์ออกมาสำหรับคอลเลกชัน toString () เนื่องจากคลาสแม่ของคอลเลกชันวิธีการ toString () ของวัตถุถูกเขียนใหม่
(31) อย่าทำการแปลงแรงลงบนประเภทข้อมูลพื้นฐานที่อยู่นอกช่วง
สิ่งนี้จะไม่ได้รับผลลัพธ์ที่ต้องการ:
โมฆะคงที่สาธารณะหลัก (สตริง [] args) {long l = 12345678901234L; int i = (int) l; System.out.println (i);}เราอาจคาดหวังว่าจะได้รับบางส่วน แต่ผลลัพธ์คือ:
1942892530
อธิบาย ยาวในชวาคือ 8 ไบต์ที่มี 64 บิตดังนั้นการเป็นตัวแทนของ 12345678901234 ในคอมพิวเตอร์ควรเป็น:
0000 0000 0000 0000 0000 1011 0011 1010 0111 0011 1100 1110 0010 1111 1111 1111 0010
ข้อมูลประเภท int คือ 4 ไบต์และ 32 บิต 32 บิตแรกของสตริงข้อมูลไบนารีข้างต้นมาจากบิตต่ำ:
0111 0011 1100 1110 0010 1111 1111 0010
สตริงไบนารีนี้แสดงเป็นทศนิยม 1942892530 ดังนั้นจึงเป็นเอาต์พุตเนื้อหาบนคอนโซลด้านบน จากตัวอย่างนี้เราสามารถวาดสองข้อสรุป:
1. ประเภทข้อมูลเริ่มต้นของประเภทจำนวนเต็มคือ int, ยาว l = 12345678901234L หมายเลขนี้เกินช่วงของ int ดังนั้นจึงมี L ในตอนท้ายซึ่งบ่งชี้ว่านี่เป็นหมายเลขประเภทที่ยาว โดยวิธีการประเภทจุดเริ่มต้นของจุดลอยตัวเป็นสองเท่าดังนั้นเมื่อกำหนดลอยควรเขียนเป็น "" float f = 3.5f "
2. ถัดไปเขียนประโยคอื่น "int ii = l + i;" และจะรายงานข้อผิดพลาดเนื่องจาก Long + Int ยาวและไม่สามารถกำหนดให้กับ Int ได้
(32) ข้อมูลที่ไม่ได้ใช้ในคลาสคอลเลกชันสาธารณะจะต้องถูกลบออกในเวลา
หากคลาสคอลเลกชันเป็นสาธารณะ (นั่นคือมันไม่ใช่คุณสมบัติในวิธีการ) องค์ประกอบในคอลเลกชันนี้จะไม่ถูกปล่อยออกมาโดยอัตโนมัติเนื่องจากมีการอ้างอิงที่ชี้ไปที่พวกเขาเสมอ ดังนั้นหากข้อมูลบางอย่างในการรวบรวมสาธารณะไม่ได้ใช้และไม่ได้ถูกลบออกก็จะทำให้การสะสมสาธารณะเติบโตต่อไปทำให้ระบบมีศักยภาพในการรั่วไหลของหน่วยความจำ
(33) แปลงชนิดข้อมูลพื้นฐานเป็นสตริง ประเภทข้อมูลพื้นฐาน. toString () เป็นวิธีที่เร็วที่สุดตามด้วย string.valueof (data) และ data +"" ช้าที่สุด
โดยทั่วไปมีสามวิธีในการแปลงชนิดข้อมูลพื้นฐานเป็น ฉันมีข้อมูลประเภทจำนวนเต็มเช่น toString (), string.valueof (i) และ i+"" สามวิธี สามวิธีมีประสิทธิภาพแค่ไหน? ดูการทดสอบ:
โมฆะคงที่สาธารณะหลัก (สตริง [] args) {int looptime = 50000; จำนวนเต็ม i = 0; Long StartTime = System.currentTimeMillis (); สำหรับ (int j = 0; j <looptime; j ++) {string str = string.valueof (i); } system.out.println ("string.valueof ():" + (System.currentTimeMillis () - เริ่มต้น) + "MS"); startTime = system.currentTimeMillis (); สำหรับ (int j = 0; j <looptime; j ++) {string str = i.toString (); } system.out.println ("integer.toString ():" + (system.currentTimeMillis () - เริ่มต้น) + "MS"); startTime = system.currentTimeMillis (); สำหรับ (int j = 0; j <looptime; j ++) {string str = i+""; } system.out.println ("i + /" /":" + (system.currentTimeMillis () - เริ่มต้น) + "MS");}ผลการทำงานคือ:
string.valueof (): 11msinteger.toString (): 5ms + "": 25ms
ดังนั้นเมื่อคุณแปลงชนิดข้อมูลพื้นฐานเป็นสตริงในอนาคตคุณควรให้ความสำคัญกับการใช้วิธี ToString () เพราะเหตุใดจึงง่ายมาก:
วิธี String.valueOf() เรียกว่าเมธอด Integer.toString() แต่มันจะสั้น ๆ ที่จะตัดสินเมธอด Integer.toString() ก่อนโทร ฉันจะเรียก I + "" เลเยอร์ด้านล่างใช้การใช้งาน StringBuilder ก่อนอื่นให้ใช้วิธีการต่อท้ายเพื่อแยกมันจากนั้นใช้วิธี TOSTRING () เพื่อรับสตริง
เมื่อเทียบกับทั้งสามมันเห็นได้ชัดว่าเร็วที่สุดเร็วที่สุดและช้าที่สุด
(34) ใช้วิธีที่มีประสิทธิภาพที่สุดในการสำรวจแผนที่
มีหลายวิธีในการสำรวจแผนที่ โดยปกติแล้วสิ่งที่เราต้องการในการสำรวจคีย์และค่าในแผนที่ วิธีที่แนะนำและมีประสิทธิภาพมากที่สุดคือ:
โมฆะคงที่สาธารณะหลัก (String [] args) {hashmap <string, string> hm = new hashmap <string, string> (); HM.PUT ("111", "222"); ตั้งค่า <map.entry <string, string >> entryset = hm.entryset (); Iterator <map.entry <string, string >> iter = entryset.iterator (); ในขณะที่ (iter.hasnext ()) {map.entry <string, string> entry = iter.next (); System.out.println (entry.getKey () + "/t" + entry.getValue ()); - หากคุณต้องการทำซ้ำค่าคีย์ของแผนที่นี้มันจะเหมาะสมกว่าที่จะใช้ " Set<String> keySet = hm.keySet();"
(35) ขอแนะนำให้ดำเนินการแยกต่างหากเพื่อปิด () ของทรัพยากร
ตัวอย่างเช่นฉันมีรหัสชิ้นส่วนเช่นนี้:
ลอง {xxx.close (); yyy.close ();} catch (Exception e) {... }ขอแนะนำให้แก้ไขเป็น:
ลอง {xxx.close ();} catch (Exception e) {... } ลอง {yyy.close ();} catch (Exception e) {... }แม้ว่ามันจะค่อนข้างลำบาก แต่ก็สามารถหลีกเลี่ยงการรั่วไหลของทรัพยากรได้ เราคิดว่าหากไม่มีรหัสที่แก้ไขแล้วถ้า xxx.close () โยนข้อยกเว้นมันจะเข้าสู่บล็อก catch yyy.close () จะไม่ถูกดำเนินการและทรัพยากร YYY จะไม่ถูกนำกลับมาใช้ใหม่และจะถูกครอบครองตลอดเวลา ด้วยรหัสมากขึ้นเช่นนี้อาจทำให้การจัดการทรัพยากรรั่วไหลออกมา หลังจากเปลี่ยนเป็นวิธีการเขียนต่อไปนี้รับประกันได้ว่า XXX และ YYY จะถูกปิดไม่ว่าจะเกิดอะไรขึ้น
(36) สำหรับ ThreadLocal คุณต้องลบก่อนหรือหลังการใช้งาน
ปัจจุบันโครงการทั้งหมดใช้เทคโนโลยีการรวมเธรดซึ่งดีมาก คุณสามารถกำหนดค่าจำนวนเธรดและนำเธรดมาใช้ซ้ำได้แบบไดนามิก
อย่างไรก็ตามหากคุณใช้ ThreadLocal ในโครงการของคุณอย่าลืมลบออกก่อนหรือหลังการใช้งาน นี่เป็นเพราะเทคโนโลยีพูลเธรดที่กล่าวถึงข้างต้นคือการนำเธรดกลับมาใช้ใหม่ซึ่งหมายความว่าในระหว่างการทำงานของรหัสเธรดจะไม่ถูกทำลายและจะรอการใช้งานครั้งต่อไป ลองดูที่การอ้างอิงในคลาสเธรดที่เก็บ threadlocal.threadlocalmap:
/* ค่าเธรดที่เกี่ยวข้องกับเธรดนี้ แผนที่นี้ได้รับการบำรุงรักษา * โดยคลาส Threadlocal */threadlocal.threadLocalMap threadLocals = null;
เธรดไม่ได้ถูกทำลายหมายความว่าข้อมูลใน ThreadLocal.threadLocalMap ของชุดเธรดก่อนหน้ายังคงมีอยู่ เมื่อเธรดถัดไปนำมาใช้ซ้ำเธรดนี้เป็นไปได้ว่าสิ่งที่คุณได้รับคือข้อมูลของชุดเธรดก่อนหน้าแทนที่จะเป็นเนื้อหาที่คุณต้องการ
ปัญหานี้คลุมเครือมาก เมื่อเกิดข้อผิดพลาดที่เกิดจากสาเหตุนี้มันเป็นเรื่องยากมากที่จะพบปัญหานี้โดยไม่มีประสบการณ์ที่เกี่ยวข้องหรือรากฐานที่มั่นคง ดังนั้นคุณควรให้ความสนใจกับสิ่งนี้เมื่อเขียนโค้ดซึ่งจะลดภาระงานที่ตามมาของคุณ
(37) อย่าลืมแทนที่หมายเลขปีศาจด้วยคำจำกัดความคงที่ การมีอยู่ของจำนวนปีศาจจะลดความสามารถในการอ่านรหัสได้อย่างมาก ไม่ว่าจะเป็นค่าคงที่สตริงที่ใช้คำจำกัดความคงที่สามารถขึ้นอยู่กับสถานการณ์ได้หรือไม่
(38) เมื่อการกำหนดครั้งแรกของความยาวหรือยาวใช้ตัวพิมพ์ใหญ่ l แทนตัวพิมพ์เล็ก l เนื่องจากตัวอักษร L นั้นง่ายมากที่จะสับสนกับหมายเลข 1 จุดนี้มีรายละเอียดมากและน่าสังเกต
(39) วิธีการเขียนใหม่ทั้งหมดจะต้องรักษาคำอธิบายประกอบ @Override
มีสามเหตุผลในการทำสิ่งนี้:
(1) เป็นที่ชัดเจนว่าวิธีนี้ได้รับการสืบทอดมาจากคลาสหลัก
(2) วิธีการ getObject () และ get0bject () จดหมายฉบับที่สี่ของอดีตคือ "O" และผู้ปกครองและลูกคนที่สี่ของหลังคือ "0" การเพิ่มคำอธิบายประกอบ @Override สามารถตรวจสอบได้ทันทีว่าการเขียนใหม่นั้นสำเร็จหรือไม่
(3) แก้ไขลายเซ็นวิธีการในคลาสนามธรรมและคลาสการใช้งานจะรายงานข้อผิดพลาดในการรวบรวมทันที
(40) ขอแนะนำให้ใช้คลาสเครื่องมือวัตถุที่เพิ่งเปิดตัวใน JDK7 เพื่อเปรียบเทียบวัตถุเท่ากับ A.Equals โดยตรง (B) และมีความเสี่ยงของข้อยกเว้นตัวชี้โมฆะ
(41) อย่าใช้ "+" เพื่อประกบสตริงในตัววนลูป แต่ใช้ StringBuilder เพื่อผนวกอย่างต่อเนื่อง
ให้ฉันพูดถึงเหตุผลที่ฉันไม่ใช้ "+" สำหรับการประกบสตริง ถ้าฉันมีวิธี:
สตริงสาธารณะ appendStr (string oristr, string ... appendStrs) {ถ้า (appendStrs == null || appendStr.length == 0) {return oristr; } สำหรับ (String appendStr: appendStrs) {oristr += appendStr; } return oristr;}หลังจากรวบรวมรหัสนี้ให้ถอดรหัสไฟล์. class โดยใช้ javap -c เพื่อสกัดกั้นส่วนสำคัญ:
หมายความว่าทุกครั้งที่เครื่องเสมือนจะพบตัวดำเนินการ "+" เพื่อแยกสตริงมันจะใหม่สตริง builder จากนั้นเรียกใช้วิธีการผนวกและในที่สุดเรียกใช้เมธอด toString () เพื่อแปลงสตริงเป็นวัตถุ Orister นั่นคือกี่ครั้งที่ loop จะใหม่ stringbuilder () ซึ่งเป็นการสูญเสียความทรงจำ
(42) อย่าจับภาพคลาสข้อยกเว้นรันไทม์ที่กำหนดไว้ในไลบรารีคลาส Java ที่สืบทอดมาจาก RuntimeException
ประสิทธิภาพการจัดการข้อยกเว้นอยู่ในระดับต่ำคลาสข้อยกเว้นของรันไทม์ส่วนใหญ่ของ RuntimeException สามารถหลีกเลี่ยงได้อย่างสมบูรณ์โดยโปรแกรมเมอร์เช่น:
(43) หลีกเลี่ยงการใช้อินสแตนซ์แบบสุ่มโดยหลายเธรด แม้ว่าการแบ่งปันอินสแตนซ์จะปลอดภัยในเธรด แต่ก็จะทำให้ประสิทธิภาพลดลงเนื่องจากการแข่งขันสำหรับเมล็ดพันธุ์เดียวกัน หลังจาก JDK7 คุณสามารถใช้ ThreadLocalRandom เพื่อรับหมายเลขสุ่ม
อธิบายเหตุผลว่าทำไมการแข่งขันสำหรับเมล็ดพันธุ์เดียวกันทำให้เกิดการลดลงของประสิทธิภาพ ตัวอย่างเช่นลองดูการใช้วิธี Nextint () ของคลาสสุ่ม:
สาธารณะ int nextint () {return next (32); -วิธีการถัดไป (int bits) เรียกว่าซึ่งเป็นวิธีการป้องกัน:
ได้รับการป้องกัน int ถัดไป (int bits) {long oldseed, nextseed; Seed Atomiclong = this.eed; ทำ {oldseed = seed.get (); nextseed = (oldseed * ตัวคูณ + เพิ่มเติม) & mask; } ในขณะที่ (! seed.compareandset (oldseed, nextseed)); return (int) (nextseed >>> (48 - บิต));}และเมล็ดพันธุ์ที่นี่เป็นตัวแปรระดับโลก:
/*** สถานะภายในที่เกี่ยวข้องกับเครื่องกำเนิดหมายเลข pseudorandom นี้ * (รายละเอียดสำหรับวิธีการในคลาสนี้อธิบายถึงการคำนวณ * การคำนวณของค่านี้อย่างต่อเนื่อง) */ เมล็ดพันธุ์อะตอม
เมื่อหลายเธรดได้รับตัวเลขสุ่มในเวลาเดียวกันพวกเขาจะแข่งขันกันเพื่อเมล็ดเดียวกันทำให้ประสิทธิภาพลดลง
(44) ชั้นเรียนแบบคง
นี่เป็นเพราะเราไม่จำเป็นต้องใหม่ข้างนอก หลังจากตั้งค่าตัวสร้างเป็นส่วนตัวเรามั่นใจว่าคลาสเหล่านี้จะไม่สร้างวัตถุอินสแตนซ์
postscript
รหัสที่ยอดเยี่ยมมาจากการเพิ่มประสิทธิภาพเล็กน้อย การใส่ใจในทุกรายละเอียดไม่เพียง แต่สามารถปรับปรุงประสิทธิภาพการทำงานของโปรแกรม แต่ยังหลีกเลี่ยงปัญหาที่ไม่รู้จักมากมาย
ข้างต้นคือ 44 คำแนะนำการเพิ่มประสิทธิภาพรหัส Java ที่แนะนำโดยตัวแก้ไข ฉันหวังว่ามันจะเป็นประโยชน์กับคุณ หากคุณมีคำถามใด ๆ โปรดฝากข้อความถึงฉันและบรรณาธิการจะตอบกลับคุณทันเวลา ขอบคุณมากสำหรับการสนับสนุนเว็บไซต์ Wulin.com!