กองจาวากองและสแต็คในชวาแบ่งหน่วยความจำออกเป็นสองประเภท: หนึ่งคือหน่วยความจำสแต็กและอีกอันคือหน่วยความจำกอง
ตัวแปรพื้นฐานบางประเภทที่กำหนดไว้ในฟังก์ชั่นและตัวแปรอ้างอิงของวัตถุได้รับการจัดสรรในหน่วยความจำสแต็กของฟังก์ชั่น เมื่อตัวแปรถูกกำหนดในบล็อกของรหัส Java จะจัดสรรพื้นที่หน่วยความจำสำหรับตัวแปรในสแต็ก เมื่อขอบเขตของตัวแปรเกินขอบเขตของตัวแปร Java จะปล่อยพื้นที่หน่วยความจำที่จัดสรรโดยอัตโนมัติสำหรับตัวแปรและพื้นที่หน่วยความจำสามารถใช้แยกกันได้ทันที
หน่วยความจำฮีปใช้ในการจัดเก็บวัตถุและอาร์เรย์ที่สร้างขึ้นโดยใหม่ หน่วยความจำที่จัดสรรในฮีปได้รับการจัดการโดยเครื่องเก็บขยะอัตโนมัติ Java Virtual Machine หลังจากอาร์เรย์หรือวัตถุถูกสร้างขึ้นในฮีปตัวแปรพิเศษสามารถกำหนดได้ในสแต็ก ค่าของตัวแปรนี้เท่ากับที่อยู่แรกของอาร์เรย์หรือวัตถุในหน่วยความจำฮีป ตัวแปรพิเศษนี้ในสแต็กกลายเป็นตัวแปรอ้างอิงสำหรับอาร์เรย์หรือวัตถุ ในอนาคตคุณสามารถใช้ตัวแปรอ้างอิงในหน่วยความจำสแต็กในโปรแกรมเพื่อเข้าถึงอาร์เรย์หรือวัตถุในฮีป ตัวแปรอ้างอิงเทียบเท่ากับนามแฝงหรือชื่อรหัสสำหรับอาร์เรย์หรือวัตถุ
ตัวแปรอ้างอิงเป็นตัวแปรธรรมดา เมื่อกำหนดหน่วยความจำจะถูกจัดสรรบนสแต็ก ตัวแปรอ้างอิงจะถูกปล่อยออกมานอกขอบเขตเมื่อโปรแกรมทำงาน อาร์เรย์และวัตถุนั้นถูกจัดสรรในกอง แม้ว่าโปรแกรมจะทำงานนอกบล็อกโค้ดซึ่งข้อความที่ใช้ใหม่เพื่อสร้างอาร์เรย์และวัตถุนั้นอยู่ในตำแหน่งหน่วยความจำฮีปที่ถูกครอบครองโดยอาร์เรย์และวัตถุจะไม่ถูกปล่อยออกมา อาร์เรย์และวัตถุกลายเป็นขยะเมื่อไม่มีตัวแปรอ้างอิงชี้ไปที่มันและไม่สามารถใช้งานได้อีกต่อไป แต่ยังคงครอบครองหน่วยความจำและถูกปล่อยโดยนักสะสมขยะในเวลาที่ไม่แน่นอน นี่เป็นเหตุผลหลักว่าทำไม Java ใช้ความทรงจำมากขึ้น ในความเป็นจริงตัวแปรในจุดสแต็กเป็นตัวแปรในหน่วยความจำฮีปซึ่งเป็นตัวชี้ใน Java!
กองและสแต็คในชวา
Java แบ่งหน่วยความจำออกเป็นสองประเภท: หนึ่งคือหน่วยความจำสแต็กและอีกอันคือหน่วยความจำกอง
1. สแต็คและกองเป็นทั้งสองสถานที่ที่ใช้โดย Java เพื่อจัดเก็บข้อมูลใน RAM ซึ่งแตกต่างจาก C ++, Java จัดการกองซ้อนและกองโดยอัตโนมัติและโปรแกรมเมอร์ไม่สามารถตั้งค่าสแต็คหรือกองโดยตรงได้
2. ข้อดีของสแต็กคือความเร็วในการเข้าถึงนั้นเร็วกว่าฮีปรองจากการลงทะเบียนที่อยู่ในซีพียูโดยตรง แต่ข้อเสียคือขนาดของข้อมูลและอายุการใช้งานในสแต็กจะต้องกำหนดและขาดความยืดหยุ่น นอกจากนี้ยังสามารถใช้ข้อมูลสแต็กได้ ข้อได้เปรียบของกองคือมันสามารถจัดสรรขนาดหน่วยความจำแบบไดนามิกและอายุการใช้งานไม่จำเป็นต้องบอกกับคอมไพเลอร์ล่วงหน้า นักสะสมขยะของ Java จะรวบรวมข้อมูลที่ไม่ได้ใช้โดยอัตโนมัติอีกต่อไป แต่ข้อเสียคือหน่วยความจำจะต้องได้รับการจัดสรรแบบไดนามิกที่รันไทม์ความเร็วในการเข้าถึงจะช้าลง
3. ข้อมูลสองประเภทใน Java
หนึ่งคือประเภทพื้นฐาน (ประเภทดั้งเดิม) มี 8 ประเภทคือ int, สั้น, ยาว, ไบต์, ลอย, สอง, บูลีน, ถ่าน (หมายเหตุ,
ไม่มีประเภทพื้นฐานของสตริง) คำจำกัดความประเภทนี้ถูกกำหนดโดยแบบฟอร์มเช่น int a = 3; ยาว b = 255L; และเรียกว่าตัวแปรอัตโนมัติ เป็นที่น่าสังเกตว่าตัวแปรอัตโนมัติมีค่าตัวอักษรไม่ใช่อินสแตนซ์ของคลาสนั่นคือไม่ใช่การอ้างอิงถึงคลาสและไม่มีชั้นเรียนที่นี่ ตัวอย่างเช่น int a = 3; นี่คือการอ้างอิงที่ชี้ไปที่ประเภท int
ชี้ไปที่ค่าตามตัวอักษร 3 เนื่องจากขนาดของค่าตัวอักษรเหล่านี้อายุการใช้งานของค่าตัวอักษรเหล่านี้สามารถทราบได้ (ค่าตัวอักษรเหล่านี้ถูกกำหนดไว้อย่างสม่ำเสมอในบล็อกโปรแกรมและหลังจากบล็อกของโปรแกรมออกค่าฟิลด์จะหายไป)
เพื่อประโยชน์ในการไล่ตามความเร็วมันมีอยู่ในสแต็ค
นอกจากนี้คุณลักษณะพิเศษที่สำคัญมากของสแต็กคือข้อมูลในสแต็กสามารถแชร์ได้ สมมติว่าเรากำหนดในเวลาเดียวกัน:
int a = 3;
int b = 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 จะไม่ส่งผลกระทบต่อค่า b
ประเภทอื่นคือข้อมูลคลาสบรรจุภัณฑ์เช่นจำนวนเต็มสตริงคู่ ฯลฯ ที่ห่อหุ้มชนิดข้อมูลพื้นฐานที่สอดคล้องกัน ข้อมูลคลาสทั้งหมดเหล่านี้มีอยู่ในกอง Java ใช้คำสั่งใหม่ () เพื่อแสดงคอมไพเลอร์และสร้างขึ้นอย่างมีชีวิตชีวาตามความจำเป็นในการรันไทม์ดังนั้นจึงมีความยืดหยุ่นมากขึ้น แต่ข้อเสียคือต้องใช้เวลามากขึ้น
ใน Java มีสถานที่ต่าง ๆ หกแห่งที่สามารถเก็บข้อมูลได้:
1. ลงทะเบียน นี่คือพื้นที่จัดเก็บที่เร็วที่สุดเนื่องจากตั้งอยู่ในสถานที่ที่แตกต่างจากพื้นที่จัดเก็บอื่น ๆ - โปรเซสเซอร์ อย่างไรก็ตามจำนวนการลงทะเบียนมี จำกัด อย่างมากดังนั้นการลงทะเบียนจะถูกจัดสรรโดยคอมไพเลอร์ตามข้อกำหนด คุณไม่สามารถควบคุมได้โดยตรงและคุณไม่สามารถรู้สึกถึงสัญญาณใด ๆ ของการมีอยู่ของการลงทะเบียนในโปรแกรม
2. สแต็ค ตั้งอยู่ใน RAM โดยทั่วไป แต่ด้วย "ตัวชี้สแต็ก" คุณจะได้รับการสนับสนุนจากโปรเซสเซอร์ หากตัวชี้สแต็กเคลื่อนที่ลงหน่วยความจำใหม่จะถูกจัดสรร ถ้ามันขยับขึ้นหน่วยความจำเหล่านั้นจะเป็นอิสระ นี่เป็นวิธีที่รวดเร็วและมีประสิทธิภาพในการจัดสรรพื้นที่เก็บข้อมูลรองจากการลงทะเบียนเท่านั้น เมื่อสร้างโปรแกรมคอมไพเลอร์ Java จะต้องรู้ขนาดและวงจรชีวิตที่แน่นอนของข้อมูลทั้งหมดที่เก็บไว้ในสแต็กเพราะจะต้องสร้างรหัสที่เกี่ยวข้องเพื่อย้ายตัวชี้สแต็กขึ้นและลง ข้อ จำกัด นี้จำกัดความยืดหยุ่นของโปรแกรมดังนั้นแม้ว่าข้อมูล JA VA บางตัวจะถูกเก็บไว้ในสแต็กโดยเฉพาะอย่างยิ่งการอ้างอิงวัตถุวัตถุ Java จะไม่ถูกเก็บไว้ในนั้น
3. กอง สระว่ายน้ำหน่วยความจำสากล (มีอยู่ใน RAM) สำหรับการจัดเก็บวัตถุ Java ที่เรียกว่า ข้อได้เปรียบของกองคือคอมไพเลอร์ไม่จำเป็นต้องรู้ว่ามีพื้นที่เก็บข้อมูลจำนวนเท่าใดที่จะจัดสรรจากกองและไม่จำเป็นต้องรู้ว่าข้อมูลที่เก็บไว้จะอยู่ในกองนานเท่าใด ดังนั้นจึงมีความยืดหยุ่นอย่างมากในการจัดสรรที่เก็บในกอง เมื่อคุณต้องการสร้างวัตถุคุณจะต้องเขียนโค้ดบรรทัดง่ายๆในใหม่ เมื่อดำเนินการรหัสบรรทัดนี้มันจะจัดเก็บและจัดสรรในกองโดยอัตโนมัติ แน่นอนว่าต้องชำระรหัสที่สอดคล้องกันเพื่อความยืดหยุ่นนี้ ต้องใช้เวลามากขึ้นในการจัดสรรที่เก็บด้วยกองมากกว่าเก็บไว้ในสแต็ก
4. ที่เก็บข้อมูลคงที่ "คงที่" ที่นี่หมายถึง "ในตำแหน่งคงที่" Static Storage เก็บข้อมูลที่มีอยู่เสมอเมื่อโปรแกรมทำงานอยู่ คุณสามารถใช้คำหลักคงที่เพื่อระบุว่าองค์ประกอบเฉพาะของวัตถุนั้นคงที่ แต่วัตถุ Java นั้นไม่เคยเก็บไว้ในพื้นที่เก็บข้อมูลแบบคงที่
5. การจัดเก็บคงที่ ค่าคงที่มักจะถูกเก็บไว้โดยตรงภายในรหัสโปรแกรมและปลอดภัยที่จะทำเช่นนั้นเพราะจะไม่เปลี่ยนแปลง บางครั้งในระบบฝังตัวค่าคงที่ของตัวเองจะถูกแยกออกจากส่วนอื่นดังนั้นในกรณีนี้มันเป็นทางเลือกที่จะใส่ไว้ใน ROM
6. ที่เก็บข้อมูลที่ไม่ใช่ RAM หากข้อมูลมีชีวิตอยู่นอกโปรแกรมอย่างสมบูรณ์สามารถทิ้งไว้ได้โดยไม่ต้องควบคุมโปรแกรมและสามารถมีอยู่เมื่อโปรแกรมไม่ทำงาน
ในแง่ของความเร็วมีความสัมพันธ์ดังนี้:
ลงทะเบียน <stack <heap <อื่น ๆ
"ข้อความข้างต้นสกัดจาก" การคิดใน Java ""
คำถามที่ 1:
string str1 = "abc"; string str2 = "abc"; System.out.println (str1 == str2); //จริง
คำถามที่ 2:
string str1 = สตริงใหม่ ("abc"); string str2 = สตริงใหม่ ("abc"); System.out.println (str1 == str2); // เท็จ คำถามที่ 3:
สตริง s1 = "ja"; สตริง s2 = "va"; สตริง s3 = "java"; สตริง S4 = S1 + S2; System.out.println (s3 == s4); // false system.out.println (s3.equals (s4)); // true
ตัวแปรพื้นฐานบางประเภทที่กำหนดไว้ในฟังก์ชันและตัวแปรอ้างอิงของวัตถุทั้งหมดได้รับการจัดสรรในหน่วยความจำสแต็กของฟังก์ชั่น
เมื่อตัวแปรถูกกำหนดในบล็อกของรหัส Java จะจัดสรรพื้นที่หน่วยความจำสำหรับตัวแปรนี้ในสแต็ก เมื่อขอบเขตของตัวแปรเกินกว่าตัวแปร Java จะปล่อยพื้นที่หน่วยความจำที่จัดสรรโดยอัตโนมัติสำหรับตัวแปรและพื้นที่หน่วยความจำสามารถใช้แยกกันได้ทันที
หน่วยความจำฮีปใช้ในการจัดเก็บวัตถุและอาร์เรย์ที่สร้างขึ้นโดยใหม่
หน่วยความจำที่จัดสรรในกองได้รับการจัดการโดยเครื่องเก็บขยะอัตโนมัติของเครื่องเสมือนของ Java
หลังจากอาร์เรย์หรือวัตถุถูกสร้างขึ้นในฮีปตัวแปรพิเศษสามารถกำหนดได้ในสแต็กเพื่อให้ค่าของตัวแปรนี้ในสแต็กเท่ากับที่อยู่แรกของอาร์เรย์หรือวัตถุในหน่วยความจำฮีปและตัวแปรในสแต็กกลายเป็นตัวแปรอ้างอิงสำหรับอาร์เรย์หรือวัตถุ
ตัวแปรอ้างอิงเทียบเท่ากับชื่อที่กำหนดให้กับอาร์เรย์หรือวัตถุ คุณสามารถใช้ตัวแปรอ้างอิงในสแต็กในโปรแกรมเพื่อเข้าถึงอาร์เรย์หรือวัตถุในกอง
โดยเฉพาะ: สแต็กและกองเป็นทั้งสองสถานที่ที่ใช้โดย Java เพื่อจัดเก็บข้อมูลใน RAM ซึ่งแตกต่างจาก C ++, Java จัดการกองซ้อนและกองโดยอัตโนมัติและโปรแกรมเมอร์ไม่สามารถตั้งค่าสแต็คหรือกองโดยตรงได้
Java Heap เป็นพื้นที่ข้อมูลรันไทม์ซึ่งวัตถุจัดสรรพื้นที่ วัตถุเหล่านี้ถูกจัดตั้งขึ้นผ่านคำแนะนำเช่นใหม่, Newarray, Anewarray และ MultiAnewarray พวกเขาไม่ต้องการให้รหัสโปรแกรมได้รับการเผยแพร่อย่างชัดเจน กองรับผิดชอบการรวบรวมขยะ ข้อได้เปรียบของกองคือสามารถจัดสรรขนาดหน่วยความจำแบบไดนามิกและอายุการใช้งานไม่จำเป็นต้องบอกกับคอมไพเลอร์ล่วงหน้าเพราะมันจัดสรรหน่วยความจำแบบไดนามิกเมื่อรันไทม์ นักสะสมขยะของ Java จะรวบรวมข้อมูลที่ไม่ได้ใช้โดยอัตโนมัติอีกต่อไป แต่ข้อเสียคือเพราะจำเป็นต้องจัดสรรหน่วยความจำแบบไดนามิกที่รันไทม์ความเร็วในการเข้าถึงจะช้าลง
ข้อได้เปรียบของสแต็กคือความเร็วในการเข้าถึงนั้นเร็วกว่ากองรองลงมารองจากการลงทะเบียนเท่านั้นและสามารถใช้ข้อมูลสแต็กได้ แต่ข้อเสียคือขนาดของข้อมูลและอายุการใช้งานในสแต็กจะต้องกำหนดและขาดความยืดหยุ่น สแต็กส่วนใหญ่เก็บตัวแปรพื้นฐานบางประเภท (, int, สั้น, ยาว, ไบต์, ลอย, สอง, บูลีน, ถ่าน) และที่จับวัตถุ
คุณสมบัติพิเศษที่สำคัญมากของสแต็กคือข้อมูลที่มีอยู่ในสแต็กสามารถแชร์ได้ สมมติว่าเรากำหนดในเวลาเดียวกัน:
int a = 3;
int b = 3;
คอมไพเลอร์ประมวลผลครั้งแรก int a = 3; ก่อนอื่นมันจะสร้างการอ้างอิงในสแต็กด้วยตัวแปร A แล้วค้นหาว่ามีค่า 3 ในสแต็กหรือไม่ หากไม่พบมันจะเก็บ 3 แล้วชี้ไปที่ 3 จากนั้นประมวลผล int b = 3; หลังจากสร้างตัวแปรอ้างอิงของ B เนื่องจากมีค่า 3 ในสแต็กแล้ว B จะถูกชี้ไปที่ 3 ด้วยวิธีนี้ A และ B ทั้งสองชี้ไปที่ 3 ในเวลาเดียวกัน ในเวลานี้ถ้า A = 4 ถูกตั้งค่าอีกครั้ง; จากนั้นคอมไพเลอร์จะค้นหาอีกครั้งว่ามีค่า 4 ในสแต็กหรือไม่ ถ้าไม่เก็บ 4 และชี้ A ถึง 4; หากมีอยู่แล้วชี้ไปที่ที่อยู่นี้โดยตรง ดังนั้นการเปลี่ยนแปลงของมูลค่า A จะไม่ส่งผลกระทบต่อค่า b ควรสังเกตว่าการแบ่งปันข้อมูลนี้แตกต่างจากการแบ่งปันการอ้างอิงจากวัตถุสองชิ้นที่ชี้ไปที่วัตถุหนึ่งในเวลาเดียวกันเพราะในกรณีนี้การปรับเปลี่ยนของ A จะไม่ส่งผลกระทบต่อ B มันจะทำโดยคอมไพเลอร์ซึ่งเอื้อต่อการประหยัดพื้นที่ ตัวแปรอ้างอิงวัตถุจะปรับเปลี่ยนสถานะภายในของวัตถุนี้และจะส่งผลกระทบต่อตัวแปรอ้างอิงวัตถุอื่น
สตริงเป็นข้อมูลบรรจุภัณฑ์พิเศษ สามารถใช้:
string str = สตริงใหม่ ("abc"); string str = "abc"; มีสองรูปแบบที่จะสร้าง สิ่งแรกคือการใช้ใหม่ () เพื่อสร้างวัตถุใหม่ซึ่งจะถูกเก็บไว้ในกอง วัตถุใหม่ถูกสร้างขึ้นทุกครั้งที่เรียกว่า
ประเภทที่สองคือการสร้างตัวแปร STR ไปยังวัตถุของคลาสสตริงในสแต็กก่อนจากนั้นตรวจสอบว่ามี "ABC" ที่เก็บไว้ในสแต็กหรือไม่ ถ้าไม่เก็บ "abc" บนสแต็กแล้วปล่อยให้ str ชี้ไปที่ "abc" หากมี "ABC" แล้วให้ชี้ไปที่ "ABC" โดยตรง
เมื่อเปรียบเทียบว่าค่าในคลาสมีค่าเท่ากันให้ใช้วิธี Equals () หรือไม่ เมื่อทดสอบว่าการอ้างอิงของคลาส wrapper ทั้งสองชี้ไปที่วัตถุเดียวกันให้ใช้ == และใช้ตัวอย่างด้านล่างเพื่อแสดงทฤษฎีข้างต้น
string str1 = "abc"; string str2 = "abc"; System.out.println (str1 == str2); //จริง
จะเห็นได้ว่า str1 และ str2 ชี้ไปที่วัตถุเดียวกัน
string str1 = สตริงใหม่ ("abc"); string str2 = สตริงใหม่ ("abc"); System.out.println (str1 == str2); // เท็จ วิธีใหม่คือการสร้างวัตถุที่แตกต่างกัน สร้างทีละครั้ง
ดังนั้นในวิธีที่สองสตริง "ABC" หลายสายจึงถูกสร้างขึ้นและมีวัตถุเดียวในหน่วยความจำ วิธีการเขียนนี้มีประโยชน์และบันทึกพื้นที่หน่วยความจำ ในขณะเดียวกันก็สามารถปรับปรุงความเร็วในการทำงานของโปรแกรมในระดับหนึ่งเนื่องจาก JVM จะตัดสินใจโดยอัตโนมัติว่าจำเป็นต้องสร้างวัตถุใหม่ตามสถานการณ์จริงของข้อมูลในสแต็กหรือไม่ สำหรับรหัสของสตริง str = สตริงใหม่ ("ABC");, วัตถุใหม่ถูกสร้างขึ้นในฮีปโดยไม่คำนึงว่าค่าสตริงของพวกเขาเท่ากันหรือไม่ไม่ว่าจะเป็นสิ่งจำเป็นในการสร้างวัตถุใหม่ซึ่งจะเป็นการเพิ่มภาระในโปรแกรม
ในทางกลับกันหมายเหตุ: เมื่อเรากำหนดคลาสโดยใช้รูปแบบเช่น String str = "ABC";, เรามักจะรับมันเพื่อให้เราสร้างวัตถุ Str ของคลาสสตริง (ไม่จำเป็นเพราะถ้าไม่มีล่วงหน้ามันจะถูกสร้างขึ้นนี่คือการสร้างวัตถุถ้ามีอยู่แล้วชี้ไปที่วัตถุต้นฉบับ)! วัตถุอาจไม่ได้ถูกสร้างขึ้น! และอาจจะชี้ไปที่วัตถุที่ถูกสร้างขึ้นก่อนหน้านี้ ผ่านวิธีใหม่ () เท่านั้นเราสามารถตรวจสอบให้แน่ใจว่ามีการสร้างวัตถุใหม่ทุกครั้ง เนื่องจากลักษณะที่ไม่เปลี่ยนรูปของคลาสสตริงเมื่อตัวแปรสตริงจำเป็นต้องแปลงค่าบ่อยครั้งคุณควรพิจารณาใช้คลาส StringBuffer เพื่อปรับปรุงประสิทธิภาพของโปรแกรม