อาร์เรย์ JavaScript ขนาดใหญ่


105

อาร์เรย์ใน JavaScript สามารถแก้ไขได้ง่ายมากโดยการเพิ่มและลบรายการ ค่อนข้างปกปิดข้อเท็จจริงที่ว่าอาร์เรย์ภาษาส่วนใหญ่มีขนาดคงที่และต้องการการดำเนินการที่ซับซ้อนในการปรับขนาด ดูเหมือนว่า JavaScript ทำให้ง่ายต่อการเขียนโค้ดอาร์เรย์ที่มีประสิทธิภาพต่ำ สิ่งนี้นำไปสู่คำถาม:

ประสิทธิภาพใด (ในแง่ของความซับซ้อนของเวลา O ขนาดใหญ่) ที่ฉันสามารถคาดหวังได้จากการใช้งาน JavaScript เกี่ยวกับประสิทธิภาพของอาร์เรย์

ฉันคิดว่าการใช้งาน JavaScript ที่สมเหตุสมผลทั้งหมดมี O ขนาดใหญ่ดังต่อไปนี้

  • การเข้าถึง - O (1)
  • ต่อท้าย - O (n)
  • ล่วงหน้า - O (n)
  • การแทรก - O (n)
  • การลบ - O (n)
  • การแลกเปลี่ยน - O (1)

JavaScript ให้คุณเติมอาร์เรย์ล่วงหน้าตามขนาดที่กำหนดโดยใช้new Array(length)ไวยากรณ์ (คำถามโบนัส: การสร้างอาร์เรย์ในลักษณะนี้ O (1) หรือ O (n)) นี่เป็นเหมือนอาร์เรย์ทั่วไปและหากใช้เป็นอาร์เรย์ที่มีขนาดล่วงหน้าสามารถอนุญาตให้ O (1) ต่อท้ายได้ หากมีการเพิ่มตรรกะบัฟเฟอร์แบบวงกลมคุณสามารถบรรลุ O (1) prepending หากใช้อาร์เรย์ที่ขยายแบบไดนามิก O (log n) จะเป็นกรณีเฉลี่ยสำหรับทั้งสองตัว

ฉันสามารถคาดหวังประสิทธิภาพที่ดีขึ้นสำหรับบางสิ่งมากกว่าสมมติฐานของฉันได้หรือไม่ ฉันไม่คาดหวังว่าจะมีการระบุไว้ในข้อกำหนดใด ๆ แต่ในทางปฏิบัติอาจเป็นไปได้ว่าการใช้งานที่สำคัญทั้งหมดใช้อาร์เรย์ที่ปรับให้เหมาะสมอยู่เบื้องหลัง มีการขยายอาร์เรย์แบบไดนามิกหรืออัลกอริธึมเพิ่มประสิทธิภาพอื่น ๆ ในที่ทำงานหรือไม่?

ปล

เหตุผลที่ฉันสงสัยคือฉันกำลังค้นคว้าอัลกอริทึมการเรียงลำดับซึ่งส่วนใหญ่ดูเหมือนว่าการต่อท้ายและการลบเป็นการดำเนินการ O (1) เมื่ออธิบาย O ขนาดใหญ่โดยรวมของพวกเขา


6
ตัวสร้าง Array ที่มีขนาดค่อนข้างไร้ประโยชน์ในการใช้งาน JavaScript สมัยใหม่ แทบจะไม่ทำอะไรเลยในรูปแบบพารามิเตอร์เดียวนั้น (มันตั้งค่า.lengthแต่มันเกี่ยวกับมัน) อาร์เรย์ไม่แตกต่างจากอินสแตนซ์ Object ธรรมดามากนัก
Pointy

3
การตั้งค่าlengthคุณสมบัติและการจัดสรรพื้นที่ล่วงหน้าเป็นสองสิ่งที่แตกต่างกันอย่างสิ้นเชิง
Pointy

1
@Pointy: ฉันคาดหวังมากเกินไปเมื่อผมคาดว่าการตั้งค่าarray[5]บนnew Array(10)เป็น O (1)?
Kendall Frey

1
ในขณะที่ ECMAScript ไม่ได้กำหนดวิธีการใช้งานออบเจ็กต์ Array (กำหนดเฉพาะกฎความหมายบางอย่าง) แต่ก็เป็นไปได้มากที่การใช้งานที่แตกต่างกันจะปรับให้เหมาะสมกับกรณีที่คาดไว้ (เช่นมี "อาร์เรย์จริง" สำรองสำหรับอาร์เรย์ที่มีขนาดน้อยกว่า n บางส่วน ). ฉันไม่ค่อยเข้าใจในการใช้งาน แต่จะแปลกใจจริงๆถ้าสิ่งนี้ไม่ได้ทำที่ไหนสักแห่ง ...

5
@KendallFrey "คำตอบที่ดีที่สุด" น่าจะเขียนกรณีทดสอบ jsperf สำหรับรูปแบบ n / access ที่แตกต่างกันและดูว่ามีอะไรบ้าง ;-)

คำตอบ:


111

หมายเหตุ: แม้ว่าคำตอบนี้จะถูกต้องในปี 2012 แต่เอ็นจินใช้การแทนค่าภายในที่แตกต่างกันมากสำหรับทั้งอ็อบเจ็กต์และอาร์เรย์ในปัจจุบัน คำตอบนี้อาจเป็นจริงหรือไม่ก็ได้

ในทางตรงกันข้ามกับภาษาส่วนใหญ่ซึ่งใช้อาร์เรย์ด้วยอาร์เรย์ใน Javascript Arrays เป็นวัตถุและค่าจะถูกเก็บไว้ในแฮชแท็กเช่นเดียวกับค่าวัตถุทั่วไป ดังต่อไปนี้:

  • การเข้าถึง - O (1)
  • การต่อท้าย - ค่าตัดจำหน่าย O (1) (บางครั้งจำเป็นต้องปรับขนาดแฮชแท็กโดยปกติจะต้องมีการแทรกเท่านั้น)
  • Prepending - O (n) ผ่านunshiftเนื่องจากต้องกำหนดดัชนีทั้งหมดใหม่
  • การแทรก - ตัดจำหน่าย O (1) หากไม่มีค่า O (n) หากคุณต้องการเปลี่ยนค่าที่มีอยู่ (เช่นโดยใช้splice)
  • การลบ - ตัดบัญชี O (1) เพื่อเอาค่า O (n) spliceถ้าคุณต้องการดัชนีมอบหมายผ่าน
  • การแลกเปลี่ยน - O (1)

โดยทั่วไปการตั้งค่าหรือยกเลิกการตั้งค่าคีย์ใด ๆ ใน dict จะถูกตัดจำหน่าย O (1) และเช่นเดียวกันกับอาร์เรย์ไม่ว่าดัชนีจะเป็นอย่างไร การดำเนินการใด ๆ ที่ต้องเปลี่ยนหมายเลขค่าที่มีอยู่ใหม่คือ O (n) เพียงเพราะคุณต้องอัปเดตค่าที่ได้รับผลกระทบทั้งหมด


4
ไม่ควรนำหน้า O (n)? เนื่องจากต้องมีการเลื่อนดัชนีทั้งหมด เช่นเดียวกับการแทรกและการลบ (ที่ดัชนีโดยพลการและเลื่อน / ยุบองค์ประกอบ)
nhahtdh

2
นอกจากนี้ยังlengthตั้งค่าการกลายพันธุ์ของ Array หรือgetว่ามันจะได้ความยาวและอาจจะจำได้?
alex

27
คำตอบนี้ไม่ถูกต้องอีกต่อไป เอ็นจินสมัยใหม่จะไม่จัดเก็บ Arrays (หรืออ็อบเจกต์ที่มีคีย์จำนวนเต็มดัชนี) เป็นแฮชแท็บ (แต่ก็เช่นกัน ... อาร์เรย์เช่นใน C) เว้นแต่ว่าจะเบาบาง เพื่อให้คุณเริ่มต้นที่นี่คือเกณฑ์มาตรฐาน 'คลาสสิก' ที่แสดงสิ่งนี้
Benjamin Gruenbaum

4
สิ่งนี้กำหนดโดยมาตรฐานหรือเป็นเพียงการนำไปใช้งานทั่วไปในเอ็นจิน JS V8 คืออะไร?
Albert

4
@BenjaminGruenbaum คงจะดีไม่น้อยหากคุณสามารถพัฒนาวิธีการจัดเก็บข้อมูลเหล่านี้ได้เล็กน้อย หรือให้แหล่งข้อมูลบางส่วน
Ced

1

รับประกัน

ไม่มีการรับประกันความซับซ้อนของเวลาที่ระบุสำหรับการดำเนินการอาร์เรย์ใด ๆ การทำงานของอาร์เรย์ขึ้นอยู่กับโครงสร้างข้อมูลพื้นฐานที่เครื่องยนต์เลือกใช้ เครื่องยนต์อาจมีการนำเสนอที่แตกต่างกันและสลับไปมาระหว่างกันขึ้นอยู่กับการวิเคราะห์พฤติกรรมบางอย่าง ขนาดอาร์เรย์เริ่มต้นอาจเป็นหรือไม่ใช่แบบฮิวริสติก

ความเป็นจริง

ตัวอย่างเช่น V8 ใช้ (ณ วันนี้) ทั้งแฮชแท็บและรายการอาร์เรย์เพื่อแสดงอาร์เรย์ นอกจากนี้ยังมีการแทนค่าต่างๆสำหรับวัตถุดังนั้นจึงไม่สามารถเปรียบเทียบอาร์เรย์และวัตถุได้ ดังนั้นการเข้าถึงอาร์เรย์จึงดีกว่า O (n) เสมอและอาจเร็วพอ ๆ กับการเข้าถึงอาร์เรย์ C ++ การต่อท้ายคือ O (1) เว้นแต่คุณจะถึงขนาดของโครงสร้างข้อมูลและจะต้องมีการปรับขนาด (ซึ่งก็คือ O (n)) Prepending แย่กว่า การลบอาจเลวร้ายยิ่งขึ้นหากคุณทำบางอย่างเช่นdelete array[index](อย่า!) เนื่องจากอาจบังคับให้เครื่องยนต์เปลี่ยนการเป็นตัวแทน

คำแนะนำ

ใช้อาร์เรย์สำหรับโครงสร้างข้อมูลที่เป็นตัวเลข นั่นคือสิ่งที่พวกเขามีไว้สำหรับ นั่นคือสิ่งที่เครื่องยนต์จะเพิ่มประสิทธิภาพให้กับมัน หลีกเลี่ยงอาร์เรย์ที่เบาบาง (หรือถ้าคุณต้องคาดหวังประสิทธิภาพที่แย่ลง) หลีกเลี่ยงอาร์เรย์ที่มีประเภทข้อมูลผสมกัน (เนื่องจากทำให้การแสดงข้อมูลภายในซับซ้อนขึ้น )

หากคุณต้องการเพิ่มประสิทธิภาพสำหรับเครื่องยนต์บางอย่าง (และเวอร์ชัน) ให้ตรวจสอบซอร์สโค้ดเพื่อหาคำตอบที่สมบูรณ์


รอสักครู่เราสามารถมีอาร์เรย์ที่มีประเภทข้อมูลผสมกันได้หรือไม่? Javascript เจ๋งมาก!
Anurag B.

@Anurag แน่นอน แต่ใน 99% ของกรณีคุณไม่ต้องการคุณสมบัตินี้
Desiigner
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.