ความแตกต่างในสแต็ก Java
ผู้เขียน:Eve Cole
เวลาอัปเดต:2009-11-30 17:08:19
-
1. สแต็กและฮีปเป็นสถานที่ที่ Java ใช้เพื่อจัดเก็บข้อมูลใน Ram Java แตกต่างจาก C++ ตรงที่จะจัดการสแต็กและฮีปโดยอัตโนมัติ และโปรแกรมเมอร์ไม่สามารถตั้งค่าสแต็กหรือฮีปได้โดยตรง
2. ข้อดีของสแต็กคือความเร็วในการเข้าถึงเร็วกว่าฮีป รองจากรีจิสเตอร์ที่อยู่ใน CPU โดยตรงเท่านั้น แต่ข้อเสียคือต้องกำหนดขนาดและอายุการใช้งานของข้อมูลที่จัดเก็บไว้ในสแต็กและไม่มีความยืดหยุ่น นอกจากนี้ยังสามารถแชร์ข้อมูลสแต็กได้ โปรดดูรายละเอียดในจุดที่ 3 ข้อดีของฮีปคือสามารถจัดสรรขนาดหน่วยความจำได้แบบไดนามิก และไม่จำเป็นต้องบอกอายุการใช้งานให้คอมไพเลอร์ทราบล่วงหน้า ตัวรวบรวมขยะของ Java จะรวบรวมข้อมูลที่ไม่ได้ใช้งานอีกต่อไป แต่ข้อเสียคือเนื่องจากจำเป็นต้องจัดสรรหน่วยความจำแบบไดนามิกขณะรันไทม์ ความเร็วในการเข้าถึงจึงช้า
3. ประเภทข้อมูลใน Java มีสองประเภท
ประเภทแรกคือประเภทดั้งเดิม มีทั้งหมด 8 ประเภท ได้แก่ int, short, long, byte, float, double, boolean, char (โปรดทราบว่าไม่มีประเภทสตริงพื้นฐาน) คำจำกัดความประเภทนี้กำหนดไว้ในรูปแบบเช่น int a = 3; long b = 255L และเรียกว่าตัวแปรอัตโนมัติ เป็นที่น่าสังเกตว่าตัวแปรอัตโนมัติจะเก็บค่าตามตัวอักษร ไม่ใช่อินสแตนซ์ของคลาส กล่าวคือ ตัวแปรเหล่านั้นไม่ได้อ้างอิงถึงคลาสที่นี่ ตัวอย่างเช่น int a = 3; โดยที่ a คือการอ้างอิงที่ชี้ไปที่ประเภท int ซึ่งชี้ไปที่ค่าตัวอักษร 3 ข้อมูลของค่าตัวอักษรเหล่านี้เป็นที่รู้จักในขนาดและอายุการใช้งาน (ค่าตัวอักษรเหล่านี้ถูกกำหนดไว้คงที่ในบล็อกโปรแกรมบางตัวและค่าฟิลด์จะหายไปหลังจากออกจากบล็อกโปรแกรม) มีอยู่บนสแต็ก
นอกจากนี้สแต็กยังมีคุณสมบัติพิเศษที่สำคัญมาก นั่นคือ สามารถแชร์ข้อมูลที่เก็บไว้ในสแต็กได้ สมมติว่าเรายังกำหนด:
int = 3;
int ข = 3;
คอมไพเลอร์ประมวลผล int a = 3 ก่อน โดยสร้างการอ้างอิงไปยังตัวแปร a บนสแต็ก จากนั้นค้นหาที่อยู่ที่มีค่าตัวอักษรเป็น 3 หากไม่พบ คอมไพเลอร์จะเปิดที่อยู่เพื่อจัดเก็บตัวอักษร ค่า 3 แล้วชี้ไปยังที่อยู่ 3 ถัดไป int b = 3 จะถูกประมวลผล หลังจากสร้างตัวแปรอ้างอิงของ b เนื่องจากมีค่าตัวอักษรเป็น 3 อยู่แล้วบนสแต็ก b จึงชี้ไปยังที่อยู่ของ 3 โดยตรง ด้วยวิธีนี้ จึงเกิดสถานการณ์ที่ a และ b ชี้ไปที่ 3 พร้อมกัน
สิ่งสำคัญคือต้องสังเกตว่าการอ้างอิงของค่าตัวอักษรนี้แตกต่างจากการอ้างอิงของอ็อบเจ็กต์คลาส สมมติว่าการอ้างอิงของออบเจ็กต์คลาสสองตัวชี้ไปที่ออบเจ็กต์เดียวกันในเวลาเดียวกัน หากตัวแปรอ้างอิงออบเจ็กต์ตัวหนึ่งแก้ไขสถานะภายในของออบเจ็กต์ ตัวแปรอ้างอิงออบเจ็กต์อื่นจะสะท้อนถึงการเปลี่ยนแปลงทันที ในทางตรงกันข้าม การแก้ไขค่าของการอ้างอิงตามตัวอักษรไม่ทำให้ค่าของการอ้างอิงอื่นในการอ้างอิงตามตัวอักษรเปลี่ยนแปลงไปด้วย ดังตัวอย่างข้างต้น หลังจากที่เรากำหนดค่าของ a และ b แล้ว เราก็ตั้งค่า a=4 จากนั้น b จะไม่เท่ากับ 4 แต่ยังคงเท่ากับ 3 ภายในคอมไพเลอร์เมื่อพบ a = 4; มันจะค้นหาอีกครั้งว่ามีค่าตัวอักษรเป็น 4 บนสแต็กหรือไม่ ถ้าไม่ มันจะเปิดที่อยู่อีกครั้งเพื่อเก็บค่า 4 ถ้ามีอยู่แล้ว มันจะชี้ a ไปยังที่อยู่นี้โดยตรง ดังนั้นการเปลี่ยนแปลงค่า a จะไม่ส่งผลต่อค่า b
อีกประเภทหนึ่งคือข้อมูลคลาส wrapper เช่น Integer, String, Double และคลาสอื่น ๆ ที่ล้อมประเภทข้อมูลพื้นฐานที่เกี่ยวข้อง ข้อมูลประเภทนี้ทั้งหมดมีอยู่ในฮีป Java ใช้คำสั่ง () ใหม่เพื่อบอกคอมไพลเลอร์อย่างชัดเจนว่ามันจะถูกสร้างขึ้นแบบไดนามิกตามความจำเป็นในขณะรันไทม์ ดังนั้นจึงมีความยืดหยุ่นมากกว่า แต่ข้อเสียคือใช้เวลานานกว่า 4. String เป็นข้อมูลประเภท wrapper พิเศษ นั่นคือสามารถสร้างในรูปแบบของ String str = new String( "abc" ); หรือสามารถสร้างในรูปแบบของ String str = "abc" ; (สำหรับการเปรียบเทียบ ก่อน JDK 5.0 คุณไม่เคย เห็นจำนวนเต็ม i = 3; นิพจน์เนื่องจากคลาสและค่าตัวอักษรไม่สามารถใช้แทนกันได้ยกเว้นใน JDK 5.0 นิพจน์นี้เป็นไปได้! เนื่องจากคอมไพเลอร์ทำการแปลง Integer i = new Integer(3) พื้นหลัง) . แบบแรกเป็นกระบวนการสร้างคลาสมาตรฐาน นั่นคือใน Java ทุกอย่างเป็นอ็อบเจ็กต์ และอ็อบเจ็กต์เป็นอินสแตนซ์ของคลาส ซึ่งทั้งหมดสร้างขึ้นในรูปแบบของ new () คลาสบางคลาสใน Java เช่นคลาส DateFormat สามารถส่งคืนคลาสที่สร้างขึ้นใหม่ผ่านเมธอด getInstance() ของคลาส ซึ่งดูเหมือนว่าจะละเมิดหลักการนี้ ไม่เชิง. คลาสนี้ใช้รูปแบบซิงเกิลตันเพื่อส่งคืนอินสแตนซ์ของคลาส แต่อินสแตนซ์นี้ถูกสร้างขึ้นภายในคลาสผ่าน new () และ getInstance() จะซ่อนรายละเอียดนี้จากภายนอก แล้วทำไมใน String str = "abc" ;, อินสแตนซ์จึงไม่ถูกสร้างขึ้นผ่าน new () มันละเมิดหลักการข้างต้นหรือไม่? จริงๆแล้วไม่มี
5. เกี่ยวกับการทำงานภายในของ String str = "abc" Java แปลงคำสั่งนี้เป็นขั้นตอนต่อไปนี้ภายใน:
(1) ขั้นแรกให้กำหนดตัวแปรอ้างอิงวัตถุชื่อ str ให้กับคลาส String: String str;
(2) ค้นหาสแต็กเพื่อดูว่ามีที่อยู่ที่มีค่า "abc" หรือไม่ หากไม่มี ให้เปิดที่อยู่ที่มีค่าตัวอักษร "abc" จากนั้นสร้างอ็อบเจ็กต์ใหม่ o ของคลาส String แล้วเพิ่ม อักขระของ o ค่าสตริงชี้ไปยังที่อยู่นี้ และวัตถุอ้างอิง o จะถูกบันทึกถัดจากที่อยู่นี้บนสแต็ก หากมีที่อยู่ที่มีค่า "abc" อยู่แล้ว ระบบจะพบออบเจ็กต์ o และส่งคืนที่อยู่ของ o
(3) ชี้ str ไปยังที่อยู่ของวัตถุ o
เป็นที่น่าสังเกตว่าโดยทั่วไปค่าสตริงในคลาส String จะถูกจัดเก็บโดยตรง แต่เช่นเดียวกับ String str = "abc"; ในกรณีนี้ ค่าสตริงจะบันทึกการอ้างอิงไปยังข้อมูลที่เก็บไว้ในสแต็ก!
เพื่อที่จะอธิบายปัญหานี้ได้ดีขึ้น เราสามารถตรวจสอบได้โดยใช้รหัสต่อไปนี้
สตริง str1 = "abc" ;
สตริง str2 = "abc" ;
System.out.println(str1==str2); //true
โปรดทราบว่าเราไม่ได้ใช้ str1.equals(str2); ในที่นี้ เพราะจะเป็นการเปรียบเทียบว่าค่าของทั้งสองสตริงเท่ากันหรือไม่ เครื่องหมาย == ตามคำสั่งของ JDK จะส่งคืนค่าจริงเมื่อการอ้างอิงทั้งสองชี้ไปที่วัตถุเดียวกันเท่านั้น สิ่งที่เราต้องการดูที่นี่คือ str1 และ str2 ทั้งสองชี้ไปที่วัตถุเดียวกันหรือไม่
ผลลัพธ์แสดงให้เห็นว่า JVM สร้างการอ้างอิง str1 และ str2 สองรายการ แต่สร้างเพียงวัตถุเดียว และการอ้างอิงทั้งสองชี้ไปที่วัตถุนี้
ก้าวไปอีกขั้นหนึ่งแล้วเปลี่ยนโค้ดด้านบนเป็น:
สตริง str1 = "abc" ;
สตริง str2 = "abc" ;
str1 = "บีซีดี" ;
System.out.println(str1 + "," + str2); //bcd, abc
System.out.println(str1==str2); //false
ซึ่งหมายความว่าการเปลี่ยนแปลงในการมอบหมายส่งผลให้เกิดการเปลี่ยนแปลงในการอ้างอิงอ็อบเจ็กต์คลาส และ str1 ชี้ไปที่อ็อบเจ็กต์ใหม่อื่น! และ str2 ยังคงชี้ไปที่วัตถุดั้งเดิม ในตัวอย่างด้านบน เมื่อเราเปลี่ยนค่าของ str1 เป็น "bcd" JVM พบว่าไม่มีที่อยู่สำหรับจัดเก็บค่านี้บนสแต็ก จึงเปิดที่อยู่นี้และสร้างอ็อบเจ็กต์ใหม่ที่มีค่าสตริงชี้ไปยังที่อยู่นี้ .
ที่จริงแล้วคลาส String ได้รับการออกแบบมาให้ไม่เปลี่ยนรูป หากคุณต้องการเปลี่ยนค่า คุณสามารถทำได้ แต่ JVM จะสร้างอ็อบเจ็กต์ใหม่อย่างเงียบ ๆ โดยอิงตามค่าใหม่ขณะรันไทม์ จากนั้นส่งคืนที่อยู่ของอ็อบเจ็กต์นี้เพื่ออ้างอิงถึงคลาสดั้งเดิม แม้ว่ากระบวนการสร้างนี้จะเป็นแบบอัตโนมัติทั้งหมด แต่ก็ใช้เวลานานกว่านั้น ในสภาพแวดล้อมที่ไวต่อความต้องการด้านเวลา จะมีผลกระทบเชิงลบบางประการ
แก้ไขรหัสต้นฉบับอีกครั้ง:
สตริง str1 = "abc" ;
สตริง str2 = "abc" ;
str1 = "บีซีดี" ;
สตริง str3 = str1;
System.out.println(str3); //bcd
สตริง str4 = "bcd" ;
System.out.println(str1 == str4); //true
การอ้างอิงถึงวัตถุ str3 ชี้ไปที่วัตถุที่ชี้โดย str1 โดยตรง (โปรดทราบว่า str3 ไม่ได้สร้างวัตถุใหม่) หลังจากที่ str1 เปลี่ยนค่าแล้ว ให้สร้างการอ้างอิงสตริง str4 และชี้ไปที่ออบเจ็กต์ใหม่ที่สร้างขึ้นเนื่องจาก str1 แก้ไขค่า พบว่าครั้งนี้ str4 ไม่ได้สร้างอ็อบเจ็กต์ใหม่ จึงทำให้มีการแบ่งปันข้อมูลในสแต็กอีกครั้ง
ลองดูโค้ดต่อไปนี้อีกครั้ง
สตริง str1 = สตริงใหม่ ( "abc" );
สตริง str2 = "abc" ;
System.out.println(str1==str2); //false
มีการสร้างข้อมูลอ้างอิงสองรายการ มีการสร้างวัตถุสองชิ้น การอ้างอิงทั้งสองชี้ไปที่ออบเจ็กต์ที่แตกต่างกันสองรายการตามลำดับ
สตริง str1 = "abc" ;
สตริง str2 = สตริงใหม่ ( "abc" );
System.out.println(str1==str2); //false
มีการสร้างข้อมูลอ้างอิงสองรายการ มีการสร้างวัตถุสองชิ้น การอ้างอิงทั้งสองชี้ไปที่ออบเจ็กต์ที่แตกต่างกันสองรายการตามลำดับ
โค้ดสองส่วนข้างต้นแสดงให้เห็นว่าตราบใดที่ใช้ () ใหม่เพื่อสร้างออบเจ็กต์ใหม่ มันจะถูกสร้างขึ้นในฮีป และค่าสตริงของมันถูกจัดเก็บแยกกัน แม้ว่าจะเหมือนกับข้อมูลในสแต็กก็ตาม มันจะไม่ถูกแชร์กับข้อมูลในสแต็ก
6. ค่าของคลาส wrapper ชนิดข้อมูลไม่สามารถแก้ไขได้ ไม่เพียงแต่ค่าของคลาส String จะไม่สามารถแก้ไขได้ แต่คลาส wrapper ชนิดข้อมูลทั้งหมดไม่สามารถเปลี่ยนค่าภายในได้ 7. ข้อสรุปและข้อเสนอแนะ:
(1) เมื่อเรากำหนดคลาสโดยใช้รูปแบบเช่น String str = "abc" เราจะถือว่าเราสร้างอ็อบเจ็กต์ str ของคลาส String เสมอ หมดกังวลกับดัก! วัตถุอาจไม่ถูกสร้างขึ้น! สิ่งเดียวที่แน่นอนคือมีการสร้างการอ้างอิงถึงคลาส String สำหรับว่าการอ้างอิงนี้ชี้ไปที่ออบเจ็กต์ใหม่หรือไม่ จะต้องพิจารณาตามบริบท เว้นแต่คุณจะสร้างออบเจ็กต์ใหม่อย่างชัดเจนผ่านเมธอด new () ดังนั้นคำสั่งที่แม่นยำยิ่งขึ้นคือเราสร้างตัวแปรอ้างอิง str ที่ชี้ไปที่อ็อบเจ็กต์ของคลาส String ชี้ไปที่คลาส String ที่มีค่าเป็น "abc" ความเข้าใจที่ชัดเจนเกี่ยวกับสิ่งนี้มีประโยชน์มากในการกำจัดจุดบกพร่องที่หายากในโปรแกรม
(2) การใช้ String str = "abc"; สามารถปรับปรุงความเร็วในการรันของโปรแกรมได้ในระดับหนึ่ง เนื่องจาก JVM จะกำหนดโดยอัตโนมัติว่าจำเป็นต้องสร้างอ็อบเจ็กต์ใหม่ตามสถานการณ์จริงของข้อมูลในสแต็กหรือไม่ . ส่วนโค้ดของ String str = new String("abc"); วัตถุใหม่จะถูกสร้างขึ้นในฮีปเสมอ ไม่ว่าค่าสตริงจะเท่ากันหรือจำเป็นต้องสร้างวัตถุใหม่หรือไม่ก็ตาม จึงเพิ่มภาระ บนโปรแกรม แนวคิดนี้ควรเป็นแนวคิดของโหมดฟลายเวท แต่ไม่ทราบว่าการใช้งาน JDK ภายในจะใช้โหมดนี้หรือไม่
(3) เมื่อเปรียบเทียบว่าค่าในคลาสบรรจุภัณฑ์เท่ากันหรือไม่ ให้ใช้วิธีเท่ากับ() เมื่อทดสอบว่าการอ้างอิงของคลาสบรรจุภัณฑ์ทั้งสองชี้ไปที่วัตถุเดียวกันหรือไม่ ให้ใช้ ==
(4) เนื่องจากลักษณะของคลาส String ที่ไม่เปลี่ยนรูป เมื่อตัวแปร String จำเป็นต้องเปลี่ยนค่าบ่อยครั้ง คุณควรพิจารณาใช้คลาส StringBuffer เพื่อปรับปรุงประสิทธิภาพของโปรแกรม