เมื่อใดที่จะใช้ LinkedList ผ่าน ArrayList ใน Java


3122

ฉันเป็นคนหนึ่งที่เคยใช้:

List<String> names = new ArrayList<>();

ฉันใช้อินเทอร์เฟซเป็นชื่อประเภทสำหรับการพกพาดังนั้นเมื่อฉันถามคำถามเช่นสิ่งเหล่านี้ฉันสามารถทำงานซ้ำรหัสของฉันได้

เมื่อควรจะLinkedListนำมาใช้มากกว่าArrayListและในทางกลับกัน?



1
เพียงแค่ดูข้อความที่อ้างถึงจากผู้เขียน LinkedList stackoverflow.com/a/42529652/2032701และคุณจะเข้าใจถึงปัญหาได้จริง
Ruslan

ดูโพสต์Java LinkedList vs ArrayListซึ่งอธิบายความแตกต่างและรวมถึงการทดสอบประสิทธิภาพบางอย่าง
alonana

คำตอบ:


3371

สรุป ArrayListกับArrayDequeเป็นที่นิยมในหลายLinkedListเพิ่มเติมกรณีการใช้งานมากกว่า หากคุณไม่แน่ใจว่า - ArrayListเพียงแค่เริ่มต้นด้วย


LinkedListและArrayListเป็นการใช้งานสองแบบที่แตกต่างกันของรายการอินเตอร์เฟส LinkedListดำเนินการด้วยรายการที่ลิงก์ซ้ำกัน ArrayListใช้กับอาเรย์การปรับขนาดแบบไดนามิก

เช่นเดียวกับการเชื่อมโยงรายการมาตรฐานและการดำเนินการอาร์เรย์วิธีการต่าง ๆ จะมีอัลกอริธึมที่แตกต่าง

สำหรับ LinkedList<E>

  • get(int index)คือO (n) ( โดยเฉลี่ยอยู่ที่n / 4ก้าว) แต่O (1)เมื่อindex = 0หรือindex = list.size() - 1(ในกรณีนี้คุณสามารถใช้getFirst()และgetLast()) ประโยชน์หลักอย่างหนึ่งของ LinkedList<E>
  • add(int index, E element)คือO (n) (โดยมีขั้นตอนเฉลี่ยn / 4 ) แต่O (1)เมื่อindex = 0หรือindex = list.size() - 1(ในกรณีนี้คุณสามารถใช้addFirst()และaddLast()/ add()) ประโยชน์หลักอย่างหนึ่งของ LinkedList<E>
  • remove(int index)คือO (n) ( โดยเฉลี่ยอยู่ที่n / 4ก้าว) แต่O (1)เมื่อindex = 0หรือindex = list.size() - 1(ในกรณีนี้คุณสามารถใช้removeFirst()และremoveLast()) ประโยชน์หลักอย่างหนึ่งของ LinkedList<E>
  • Iterator.remove()เป็นO (1) ประโยชน์หลักอย่างหนึ่งของ LinkedList<E>
  • ListIterator.add(E element)เป็นO (1) ประโยชน์หลักอย่างหนึ่งของ LinkedList<E>

หมายเหตุ: หลายของการดำเนินงานต้องn / 4ขั้นตอนโดยเฉลี่ยคงที่จำนวนของขั้นตอนในกรณีที่ดีที่สุด (เช่นดัชนี = 0) และn / 2ขั้นตอนในกรณีที่เลวร้ายที่สุด (กลางของรายการ)

สำหรับ ArrayList<E>

  • get(int index)เป็นO (1) ประโยชน์หลักของ ArrayList<E>
  • add(E element)เป็นO (1)ตัดจำหน่าย แต่O (n)กรณีที่แย่ที่สุดเนื่องจากอาร์เรย์ต้องถูกปรับขนาดและคัดลอก
  • add(int index, E element)คือO (n) ( โดยเฉลี่ยn / 2ขั้นตอน)
  • remove(int index)คือO (n) ( โดยเฉลี่ยn / 2ขั้นตอน)
  • Iterator.remove()คือO (n) ( โดยเฉลี่ยn / 2ขั้นตอน)
  • ListIterator.add(E element)คือO (n) ( โดยเฉลี่ยn / 2ขั้นตอน)

หมายเหตุ: หลายของการดำเนินงานต้องn / 2ขั้นตอนโดยเฉลี่ยคงที่จำนวนของขั้นตอนในกรณีที่ดีที่สุด (ตอนท้ายของรายการ) nขั้นตอนในกรณีที่เลวร้ายที่สุด (เริ่มต้นของรายการ)

LinkedList<E>อนุญาตให้มีการแทรกหรือลบเวลาคงที่โดยใช้ตัววนซ้ำแต่เข้าถึงองค์ประกอบได้อย่างต่อเนื่องเท่านั้น กล่าวอีกนัยหนึ่งคุณสามารถเดินรายการไปข้างหน้าหรือข้างหลัง แต่การค้นหาตำแหน่งในรายการนั้นต้องใช้เวลาตามสัดส่วนกับขนาดของรายการ javadoc กล่าวว่า"การดำเนินงานที่ดัชนีลงในรายการที่จะเข้าไปใน list จากจุดเริ่มต้นหรือจุดสิ้นสุดแล้วแต่จำนวนใดจะใกล้ชิด"ดังนั้นวิธีการเหล่านั้นเป็นO (n) ( n / 4ขั้นตอน) โดยเฉลี่ยแม้ว่าO (1)index = 0สำหรับ

ArrayList<E>ในทางกลับกันอนุญาตให้เข้าถึงการอ่านแบบสุ่มอย่างรวดเร็วเพื่อให้คุณสามารถคว้าองค์ประกอบใด ๆ ในเวลาคงที่ แต่การเพิ่มหรือลบออกจากที่ใดก็ได้ แต่จุดจบนั้นต้องการการเปลี่ยนองค์ประกอบหลังทั้งหมดเพื่อทำการเปิดหรือเติมช่องว่าง นอกจากนี้หากคุณเพิ่มองค์ประกอบมากกว่าความจุของอาร์เรย์ต้นแบบอาร์เรย์ใหม่ (1.5 เท่าของขนาด) จะถูกจัดสรรและอาร์เรย์เก่าจะถูกคัดลอกไปยังองค์ประกอบใหม่ดังนั้นการเพิ่มลงในArrayListคือO (n)ที่เลวร้ายที่สุด กรณี แต่คงที่โดยเฉลี่ย

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

ประโยชน์หลักของการใช้งานLinkedListเกิดขึ้นเมื่อคุณใช้ตัววนซ้ำที่มีอยู่อีกครั้งเพื่อแทรกและลบองค์ประกอบ การดำเนินการเหล่านี้สามารถทำได้ในO (1)โดยการเปลี่ยนรายการในเครื่องเท่านั้น ในรายการอาร์เรย์ต้องย้ายส่วนที่เหลือของอาร์เรย์(เช่นคัดลอก) ในด้านอื่น ๆ ที่กำลังมองหาในLinkedListวิธีการดังต่อไปนี้การเชื่อมโยงในO (n) ( n / 2ขั้นตอน) สำหรับกรณีที่เลวร้ายที่สุดในขณะที่อยู่ในArrayListตำแหน่งที่ต้องการสามารถคำนวณทางคณิตศาสตร์และเข้าถึงได้ในO (1)

ประโยชน์ของการใช้ก็LinkedListเกิดขึ้นเมื่อคุณเพิ่มหรือลบออกจากหัวของรายการเนื่องจากการดำเนินการเหล่านี้จะO (1)ในขณะที่พวกเขาเป็นO (n)ArrayListสำหรับ โปรดทราบว่าArrayDequeอาจจะเป็นทางเลือกที่ดีLinkedListสำหรับการเพิ่มและลบจากหัว Listแต่มันก็ไม่ได้เป็น

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

ความจุเริ่มต้นเริ่มต้นของ a ArrayListค่อนข้างเล็ก (10 จาก Java 1.4 - 1.8) แต่เนื่องจากการใช้งานพื้นฐานเป็นอาร์เรย์อาร์เรย์จะต้องปรับขนาดถ้าคุณเพิ่มองค์ประกอบจำนวนมาก เพื่อหลีกเลี่ยงการปรับขนาดค่าใช้จ่ายสูงเมื่อคุณรู้ว่าคุณกำลังจะเพิ่มองค์ประกอบจำนวนมากให้สร้างArrayListด้วยความจุเริ่มต้นที่สูงขึ้น


182
ลืมพูดถึงค่าใช้จ่ายในการแทรก ใน LinkedList เมื่อคุณมีตำแหน่งที่ถูกต้องค่าใช้จ่ายในการแทรก O (1) ในขณะที่ใน ArrayList ค่าจะสูงถึง O (n) - องค์ประกอบทั้งหมดที่ผ่านจุดแทรกต้องถูกย้าย
David Rodríguez - dribeas

26
เกี่ยวกับการใช้งาน Vector: ไม่จำเป็นต้องถอยกลับไปใช้ Vector จริงๆ วิธีการทำเช่นนี้คือการใช้งานรายการที่คุณต้องการและการเรียกไปยัง synchronList เพื่อให้ wrapper ตรงกัน โปรดดู: java.sun.com/docs/books/tutorial/collections/implementations/…
Ryan Cox

69
ไม่สำหรับ LinkedList นั้นการรับยังคงเป็น O (n) แม้ว่าคุณจะรู้ตำแหน่งเพราะเพื่อให้ได้ตำแหน่งนั้นการใช้งานพื้นฐานจะต้องเดินตามตัวชี้ "ถัดไป" ของรายการที่ลิงก์เพื่อรับค่าของตำแหน่งนั้น ไม่มีสิ่งเช่นการเข้าถึงแบบสุ่ม สำหรับตำแหน่งที่ 2 การเดินพอยน์เตอร์อาจจะราคาถูก แต่สำหรับตำแหน่งที่ 1 ล้านนั้นก็ไม่ถูก ประเด็นคือมันเป็นสัดส่วนกับตำแหน่งซึ่งหมายความว่ามันเป็น O (n)
Jonathan Tran

53
@ เควินมันอาจสำคัญว่าหน่วยความจำนั้น "ใกล้กัน" ฮาร์ดแวร์จะแคชบล็อกของหน่วยความจำ (RAM แบบไดนามิก) ลงใน RAM แบบคงที่เร็วขึ้นในแคช L1 หรือ L2 ในทางทฤษฎีและใช้เวลาส่วนใหญ่ในทางปฏิบัติหน่วยความจำสามารถถือว่าเป็นการเข้าถึงแบบสุ่ม แต่ในความเป็นจริงการอ่านผ่านหน่วยความจำตามลำดับจะเร็วกว่าเล็กน้อยตามลำดับแบบสุ่ม สำหรับลูปที่มีประสิทธิภาพที่สำคัญนี่อาจเป็นเรื่องสำคัญ พวกเขาเรียกมันว่า "ท้องที่อวกาศ" หรือท้องที่ของการอ้างอิง
Jonathan Tran

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

630

จนถึงขณะนี้ดูเหมือนว่าไม่มีใครได้กล่าวถึงรอยความทรงจำของแต่ละรายการเหล่านี้นอกเหนือจากฉันทามติทั่วไปว่าLinkedList"มากกว่า" ArrayListฉันจึงได้ทำตัวเลขจำนวนหนึ่งเพื่อแสดงให้เห็นว่ารายการทั้งสองนั้นใช้สำหรับการอ้างอิง N N

ตั้งแต่ลำดับที่มีทั้ง 32 หรือ 64 บิต (แม้เมื่อ null) บนระบบญาติของพวกเขาผมได้รวม 4 ชุดของข้อมูลสำหรับ 32 และ 64 บิตและLinkedListsArrayLists

หมายเหตุ:ขนาดที่แสดงสำหรับArrayListบรรทัดสำหรับรายการที่ถูกตัด - ในทางปฏิบัติความจุของอาร์เรย์สำรองใน a ArrayListนั้นใหญ่กว่าจำนวนองค์ประกอบปัจจุบัน

หมายเหตุ 2: (ขอบคุณ BeeOnRope)เนื่องจาก CompressedOops เป็นค่าเริ่มต้นตั้งแต่กลาง JDK6 ขึ้นไปค่าด้านล่างสำหรับเครื่อง 64- บิตโดยทั่วไปจะตรงกับคู่ 32- บิตของพวกเขาเว้นแต่ว่าคุณจะปิดมันโดยเฉพาะ


กราฟของ LinkedList และ ArrayList จำนวนองค์ประกอบ x ไบต์


ผลลัพธ์แสดงให้เห็นอย่างชัดเจนว่าLinkedListเป็นจำนวนมากArrayListโดยเฉพาะอย่างยิ่งเมื่อนับจำนวนองค์ประกอบที่สูงมาก หากหน่วยความจำเป็นปัจจัย, LinkedListsคัดท้ายชัดเจนของ

สูตรที่ฉันใช้ติดตามทำให้ฉันรู้ว่าฉันทำอะไรผิดและฉันจะแก้ไขมัน 'b' เป็นทั้ง 4 หรือ 8 สำหรับระบบ 32 หรือ 64 บิตและ 'n' คือจำนวนองค์ประกอบ หมายเหตุเหตุผลสำหรับ mods นั้นเป็นเพราะวัตถุทั้งหมดใน java จะใช้พื้นที่หลาย 8 ไบต์โดยไม่คำนึงว่ามันถูกใช้ทั้งหมดหรือไม่

ArrayList:

ArrayList object header + size integer + modCount integer + array reference + (array oject header + b * n) + MOD(array oject, 8) + MOD(ArrayList object, 8) == 8 + 4 + 4 + b + (12 + b * n) + MOD(12 + b * n, 8) + MOD(8 + 4 + 4 + b + (12 + b * n) + MOD(12 + b * n, 8), 8)

LinkedList:

LinkedList object header + size integer + modCount integer + reference to header + reference to footer + (node object overhead + reference to previous element + reference to next element + reference to element) * n) + MOD(node object, 8) * n + MOD(LinkedList object, 8) == 8 + 4 + 4 + 2 * b + (8 + 3 * b) * n + MOD(8 + 3 * b, 8) * n + MOD(8 + 4 + 4 + 2 * b + (8 + 3 * b) * n + MOD(8 + 3 * b, 8) * n, 8)


2
ค่อนข้างน่าสนใจที่จะเห็นว่า LinkedList ต้องการหน่วยความจำมากพอ ๆ กับ ArrayList สำหรับการจัดเก็บองค์ประกอบเดียว วิธีใช้งานง่าย! จะเกิดอะไรขึ้นถ้าคุณรันตัวอย่างด้วย -XX: + UseCompressedOops
jontejj

215
ปัญหาเกี่ยวกับคณิตศาสตร์ของคุณคือกราฟของคุณมีผลกระทบเกินจริงอย่างมาก คุณกำลังสร้างโมเดลวัตถุซึ่งแต่ละอันมีข้อมูลเพียงint4 หรือ 8 ไบต์เท่านั้น ในรายการที่เชื่อมโยงมีค่าใช้จ่าย 4 "คำ" เป็นหลัก กราฟของคุณให้การแสดงผลที่รายการที่เชื่อมโยงใช้ "ห้าครั้ง" ที่เก็บข้อมูลของรายการอาร์เรย์ นี่เป็นสิ่งที่ผิด ค่าโสหุ้ยคือ 16 หรือ 32 ไบต์ต่อวัตถุเป็นการปรับปรุงเพิ่มเติมไม่ใช่ปัจจัยการปรับ
Heath Hunnicutt

6
ไม่มีวัตถุ ArrayList / LinkedList / Node ที่มีเพียง int ดังนั้นฉันไม่ได้สิ่งที่คุณกำลังพูดอยู่ ฉันได้ reworded 'ค่าใช้จ่ายวัตถุ' เป็น 'ส่วนหัวของวัตถุ' เพื่อ clarfy - มีส่วนหัว 8 ไบต์สำหรับทุกวัตถุโดยไม่คำนึงถึงระบบและใช่ว่า inlcudes วัตถุโหนดทั้งหมดใน LinkedList ซึ่งถูกนับอย่างถูกต้องเท่าที่ฉันสามารถ บอก. อนึ่งในการมองหาที่มันอีกครั้งผมไม่ได้พบกับคู่ของปัญหาอื่น ๆ ด้วยคณิตศาสตร์ของฉันใน LinkedList ซึ่งอันที่จริงทำให้แบ่งมันและ ArrayList แย่ลง ฉันยินดีที่จะอัปเดตมันต่อไปดังนั้นโปรดอย่าลังเลที่จะชี้แจงและทำความเข้าใจกับ furuther อย่างละเอียด
ตัวเลข

6
ควรสังเกตว่าCompressedOopsตอนนี้เป็นค่าเริ่มต้นใน JDKs ล่าสุดทั้งหมด (7, 8 และการปรับปรุง 6 เป็นเวลาสองสามปี) ดังนั้น 64- บิตจะไม่สร้างความแตกต่างArrayListหรือLinkedListขนาดยกเว้นว่าคุณได้ปิด oops ที่บีบอัดไว้อย่างชัดเจนสำหรับ เหตุผลบางอย่าง.
BeeOnRope

1
@jontejj การเพิ่มขึ้นของกำลังการผลิตเริ่มต้นคือ 50% ดังนั้นเมื่อคุณเติมโดยไม่ต้องระบุความจุเริ่มต้นจะยังคงใช้หน่วยความจำอย่างมีนัยสำคัญน้อยกว่าArrayList LinkedList
ฮอลเกอร์

243

ArrayListคือสิ่งที่คุณต้องการ LinkedListเกือบทุกข้อบกพร่อง (ประสิทธิภาพ)

ทำไมLinkedListดูด:

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

236
ขอโทษ ทำเครื่องหมายคุณลง LinkedList จะไม่ดูด มีสถานการณ์ที่ LinkedList เป็นคลาสที่ถูกต้องที่จะใช้ ฉันยอมรับว่ามีหลายสถานการณ์ที่จะดีกว่า arraylist แต่มีอยู่จริง ให้ความรู้กับคนที่ทำสิ่งที่โง่!
David Turner

40
ขออภัยที่ต้องเห็นว่าคุณได้รับการโหวตจำนวนมากสำหรับสิ่งนี้ มีเหตุผลน้อยมากที่จะใช้ LinkedList ของ Java นอกเหนือจากประสิทธิภาพที่ไม่ดีมันยังใช้หน่วยความจำมากกว่าคลาส List คอนกรีตอื่น ๆ อีกด้วย (ทุกโหนดมีตัวชี้เพิ่มเติมสองตัว
เควินบร็อค

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

50
-1: นี่เป็นมุมมองที่ค่อนข้างกะพริบ ใช่มันเป็นความจริงที่ ArrayList เป็นเครื่องมือที่หลากหลายมาก อย่างไรก็ตามมันมีข้อ จำกัด มีหลายกรณีที่สิ่งนี้จะทำให้คุณเดือดร้อนและคุณจะต้องใช้ LinkedList แน่นอนว่ามันเป็นโซลูชั่นที่พิเศษมากและในฐานะที่เป็นเครื่องมือพิเศษใด ๆ ในกรณีส่วนใหญ่มันมีประสิทธิภาพที่เหนือกว่าด้วยเครื่องมืออเนกประสงค์ แต่นั่นไม่ได้หมายความว่ามัน "ดูด" หรืออะไรแบบนั้นคุณแค่ต้องรู้ว่าจะใช้เมื่อไหร่
Malcolm

27
@DavidTurner: มีอยู่จริง แต่ฉันคิดว่าประเด็นของทอมคือถ้าคุณต้องถามคุณอาจต้องการ ArrayList
user541686

139

ในฐานะที่เป็นคนที่ทำงานด้านวิศวกรรมประสิทธิภาพการทำงานบนเว็บเซอร์วิส SOA ขนาดใหญ่มากเป็นเวลาประมาณหนึ่งทศวรรษฉันชอบพฤติกรรมของ LinkedList มากกว่า ArrayList ในขณะที่ปริมาณงานคงที่ของ LinkedList นั้นแย่ลงและอาจนำไปสู่การซื้อฮาร์ดแวร์มากขึ้นพฤติกรรมของ ArrayList ที่อยู่ภายใต้แรงกดดันอาจนำไปสู่แอปในกลุ่มที่ขยายอาร์เรย์ในอาร์เรย์ใกล้เคียงกันและสำหรับอาร์เรย์ขนาดใหญ่อาจทำให้ไม่มีการตอบสนอง ในแอพและไฟดับขณะอยู่ภายใต้แรงกดดันซึ่งเป็นพฤติกรรมหายนะ

ในทำนองเดียวกันคุณสามารถรับปริมาณงานที่ดีขึ้นในแอปจากตัวเก็บขยะที่ผ่านการรับค่าเริ่มต้น แต่เมื่อคุณได้รับแอป java ที่มีฮีป 10GB คุณสามารถปิดท้ายแอพได้เป็นเวลา 25 วินาทีในระหว่าง Full GCs ซึ่งทำให้หมดเวลา และพัด SLA ของคุณถ้ามันเกิดขึ้นบ่อยเกินไป แม้ว่าตัวรวบรวม CMS จะใช้ทรัพยากรมากขึ้นและไม่ได้รับปริมาณงานที่เท่ากัน แต่ก็เป็นตัวเลือกที่ดีกว่าเพราะมีความสามารถในการคาดการณ์และเวลาแฝงที่น้อยลง

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


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

22
เมื่อคุณได้รับแอป Java ที่มีฮีป 10GB คุณสามารถปิดการล็อคแอพเป็นเวลา 25 วินาทีในระหว่าง GC เต็มรูปแบบซึ่งทำให้หมดเวลาจริงด้วย LinkedList ที่คุณฆ่าตัวเก็บขยะในระหว่าง GCs เต็มมันจะต้องย้ำ LinkedList ขนาดใหญ่เกินไป แต่ละโหนด
bestsss

5
นั่นคือ ... ทางออกที่น่ากลัว โดยทั่วไปคุณพึ่งการทำความสะอาด GC ให้กับคุณซึ่งมีราคาแพงอย่างไม่น่าเชื่อเมื่อคุณสามารถโทรไปที่ sureCapacity () ใน arraylist แทน ...
Philip Devine

5
@Andreas: A จัดสรรหน่วยความจำห้าเท่าLinkedList เสมอกว่าแหล่งอ้างอิงทั่วไปดังนั้นArrayList2.5 ครั้งที่ต้องการยังคงใช้หน่วยความจำน้อยกว่าชั่วคราวแม้ว่าหน่วยความจำจะไม่ถูกเรียกคืน เนื่องจากการจัดสรรอาร์เรย์ขนาดใหญ่ข้ามพื้นที่ Eden พวกเขาไม่ได้มีผลกระทบใด ๆ กับพฤติกรรมของ GC เว้นแต่จะมีหน่วยความจำไม่เพียงพอจริง ๆ ซึ่งในกรณีดังกล่าวLinkedListก็ระเบิดขึ้นเร็วกว่านี้มาก ...
Holger

5
@Andreas ปัญหาอื่น ๆ คือวิธีการจัดสรรหน่วยความจำ LinkedListต้องการหน่วยความจำว่างฟรีเพียงเล็กน้อยเพื่อจัดสรรสำหรับองค์ประกอบถัดไป ArrayListจะต้องมีพื้นที่ว่างขนาดใหญ่และต่อเนื่องเพื่อจัดสรรอาร์เรย์ที่ปรับขนาดแล้ว หากฮีปได้รับการแยกส่วนแล้ว GC อาจสิ้นสุดการเรียงลำดับฮีปทั้งหมดใหม่เพื่อเพิ่มหน่วยความจำบล็อกเดียวที่เหมาะสม
Piotr Kusmierczyk

128
Algorithm           ArrayList   LinkedList
seek front            O(1)         O(1)
seek back             O(1)         O(1)
seek to index         O(1)         O(N)
insert at front       O(N)         O(1)
insert at back        O(1)         O(1)
insert after an item  O(N)         O(1)

อัลกอริทึม: โน้ตบิ๊กโอ

ArrayLists นั้นเหมาะสำหรับการเขียนครั้งเดียวอ่านหลายคนหรือต่อท้าย แต่ไม่ดีที่การเพิ่ม / ลบจากด้านหน้าหรือกลาง


42
คุณไม่สามารถเปรียบเทียบค่า big-O โดยตรงได้โดยไม่ต้องคิดถึงปัจจัยคงที่ สำหรับรายการขนาดเล็ก (และรายการส่วนใหญ่มีขนาดเล็ก), O (N) ของ ArrayList นั้นเร็วกว่า O ของ LinkedList (1)
Porculus

4
ฉันไม่สนใจเกี่ยวกับประสิทธิภาพของรายการขนาดเล็กและไม่ใช้คอมพิวเตอร์ของฉันยกเว้นว่าจะใช้แบบวนซ้ำก็ตาม
Maarten Bodewes

45
LinkedList ไม่สามารถแทรกตรงกลางO(1)ได้ มีการเรียกใช้ผ่านครึ่งรายการเพื่อค้นหาจุดแทรก
โทมัส Ahle

8
LinkedList: แทรกตรงกลาง O (1) - ผิดพลาด! ฉันพบว่าแม้การแทรกตำแหน่ง 1 / 10th ของขนาด LinkedList ช้ากว่าการแทรกองค์ประกอบลงใน 1 / 10th ตำแหน่งของ ArrayList และยิ่งแย่ไปกว่านั้นคือจุดจบของการสะสม การแทรกเข้าไปในตำแหน่งสุดท้าย (ไม่ใช่ตำแหน่งสุดท้าย) ของ ArrayList นั้นเร็วกว่าแล้วในตำแหน่งสุดท้าย (ไม่ใช่ตำแหน่งสุดท้าย) ของ LinkedList
kachanov

14
@kachanov ใส่ในLinkedList คือ O(1) ถ้าคุณมี iterator ไปยังตำแหน่งแทรกคือListIterator.addเป็นที่คาดคะเนสำหรับO(1) LinkedList
มี QUIT - Anony-Mousse

107

ใช่ฉันรู้ว่านี่เป็นคำถามโบราณ แต่ฉันจะโยนในสองเซ็นต์ของฉัน:

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

มีหนึ่งกรณีการใช้งานทั่วไปที่ LinkedList มีประสิทธิภาพสูงกว่า ArrayList: ของคิว อย่างไรก็ตามหากเป้าหมายของคุณคือประสิทธิภาพแทนที่จะเป็น LinkedList คุณควรพิจารณาใช้ ArrayBlockingQueue (หากคุณสามารถกำหนดขอบเขตบนของขนาดคิวของคุณล่วงหน้าและสามารถจัดสรรหน่วยความจำล่วงหน้าทั้งหมดได้) หรือการใช้งาน CircularArrayListนี้. (ใช่มันมาจากปี 2001 ดังนั้นคุณจะต้องสร้างมันขึ้นมา แต่ฉันได้อัตราส่วนประสิทธิภาพเทียบเคียงกับสิ่งที่ยกมาในบทความตอนนี้ใน JVM ล่าสุด)


39
จาก Java 6 ArrayDequeคุณสามารถใช้ docs.oracle.com/javase/6/docs/api/java/util/ArrayDeque.html
Thomas Ahle

1
ArrayDequeช้ากว่าLinkedListเว้นแต่การดำเนินการทั้งหมดจะสิ้นสุดที่เดียวกัน ไม่เป็นไรเมื่อใช้เป็นสแต็ก แต่ไม่ได้สร้างคิวที่ดี
รายการเจเรมี

2
ไม่จริง - อย่างน้อยสำหรับการใช้งานของ Oracle ใน jdk1.7.0_60 และในการทดสอบต่อไปนี้ ฉันสร้างการทดสอบโดยที่ฉันวนซ้ำ 10 ล้านครั้งและมีจำนวนเต็ม 10 เท่าของการสุ่ม ข้างในลูปผมสำรวจองค์ประกอบหนึ่งจากและเสนอองค์ประกอบคงที่ ในคอมพิวเตอร์ของฉัน LinkedList ช้ากว่า ArrayDeque มากกว่า 10 เท่าและใช้หน่วยความจำน้อยกว่า) เหตุผลก็คือไม่เหมือน ArrayList ArrayDeque เก็บตัวชี้ไว้ที่ส่วนหัวของอาร์เรย์เพื่อไม่ให้ย้ายองค์ประกอบทั้งหมดเมื่อหัวถูกเอาออก
Henno Vermeulen

6
ArrayDequeมีแนวโน้มที่จะเร็วกว่าStackเมื่อใช้เป็นสแต็กและเร็วกว่าLinkedListเมื่อใช้เป็นคิว
akhil_mittal

3
โปรดทราบว่าความคิดเห็นของ akhil_mittal เป็นคำพูดจากArrayDequeเอกสาร
Stuart Marks

65

มันเป็นคำถามที่มีประสิทธิภาพ LinkedListรวดเร็วสำหรับการเพิ่มและลบองค์ประกอบ แต่ช้าในการเข้าถึงองค์ประกอบเฉพาะ ArrayListรวดเร็วสำหรับการเข้าถึงองค์ประกอบเฉพาะ แต่สามารถช้าที่จะเพิ่มที่ปลายทั้งสองและโดยเฉพาะอย่างยิ่งช้าที่จะลบตรงกลาง

อาร์เรย์ VS ArrayList VS LinkedList VS เวกเตอร์ไปเพิ่มเติมในเชิงลึกเช่นไม่ รายการที่เชื่อมโยง


54

ถูกต้องหรือไม่ถูกต้อง: กรุณาทำการทดสอบในพื้นที่และตัดสินใจด้วยตัวเอง

แก้ไข / ลบได้เร็วขึ้นในกว่าLinkedListArrayList

ArrayListได้รับการสนับสนุนโดยArrayที่จะต้องมีสองเท่าของขนาดที่จะเลวร้ายยิ่งในการประยุกต์ใช้ในปริมาณมาก

ด้านล่างนี้คือผลการทดสอบหน่วยสำหรับการใช้งานแต่ละครั้งการกำหนดเวลาเป็นหน่วยนาโนวินาที


Operation                       ArrayList                      LinkedList  

AddAll   (Insert)               101,16719                      2623,29291 

Add      (Insert-Sequentially)  152,46840                      966,62216

Add      (insert-randomly)      36527                          29193

remove   (Delete)               20,56,9095                     20,45,4904

contains (Search)               186,15,704                     189,64,981

นี่คือรหัส:

import org.junit.Assert;
import org.junit.Test;

import java.util.*;

public class ArrayListVsLinkedList {
    private static final int MAX = 500000;
    String[] strings = maxArray();

    ////////////// ADD ALL ////////////////////////////////////////
    @Test
    public void arrayListAddAll() {
        Watch watch = new Watch();
        List<String> stringList = Arrays.asList(strings);
        List<String> arrayList = new ArrayList<String>(MAX);

        watch.start();
        arrayList.addAll(stringList);
        watch.totalTime("Array List addAll() = ");//101,16719 Nanoseconds
    }

    @Test
    public void linkedListAddAll() throws Exception {
        Watch watch = new Watch();
        List<String> stringList = Arrays.asList(strings);

        watch.start();
        List<String> linkedList = new LinkedList<String>();
        linkedList.addAll(stringList);
        watch.totalTime("Linked List addAll() = ");  //2623,29291 Nanoseconds
    }

    //Note: ArrayList is 26 time faster here than LinkedList for addAll()

    ///////////////// INSERT /////////////////////////////////////////////
    @Test
    public void arrayListAdd() {
        Watch watch = new Watch();
        List<String> arrayList = new ArrayList<String>(MAX);

        watch.start();
        for (String string : strings)
            arrayList.add(string);
        watch.totalTime("Array List add() = ");//152,46840 Nanoseconds
    }

    @Test
    public void linkedListAdd() {
        Watch watch = new Watch();

        List<String> linkedList = new LinkedList<String>();
        watch.start();
        for (String string : strings)
            linkedList.add(string);
        watch.totalTime("Linked List add() = ");  //966,62216 Nanoseconds
    }

    //Note: ArrayList is 9 times faster than LinkedList for add sequentially

    /////////////////// INSERT IN BETWEEN ///////////////////////////////////////

    @Test
    public void arrayListInsertOne() {
        Watch watch = new Watch();
        List<String> stringList = Arrays.asList(strings);
        List<String> arrayList = new ArrayList<String>(MAX + MAX / 10);
        arrayList.addAll(stringList);

        String insertString0 = getString(true, MAX / 2 + 10);
        String insertString1 = getString(true, MAX / 2 + 20);
        String insertString2 = getString(true, MAX / 2 + 30);
        String insertString3 = getString(true, MAX / 2 + 40);

        watch.start();

        arrayList.add(insertString0);
        arrayList.add(insertString1);
        arrayList.add(insertString2);
        arrayList.add(insertString3);

        watch.totalTime("Array List add() = ");//36527
    }

    @Test
    public void linkedListInsertOne() {
        Watch watch = new Watch();
        List<String> stringList = Arrays.asList(strings);
        List<String> linkedList = new LinkedList<String>();
        linkedList.addAll(stringList);

        String insertString0 = getString(true, MAX / 2 + 10);
        String insertString1 = getString(true, MAX / 2 + 20);
        String insertString2 = getString(true, MAX / 2 + 30);
        String insertString3 = getString(true, MAX / 2 + 40);

        watch.start();

        linkedList.add(insertString0);
        linkedList.add(insertString1);
        linkedList.add(insertString2);
        linkedList.add(insertString3);

        watch.totalTime("Linked List add = ");//29193
    }


    //Note: LinkedList is 3000 nanosecond faster than ArrayList for insert randomly.

    ////////////////// DELETE //////////////////////////////////////////////////////
    @Test
    public void arrayListRemove() throws Exception {
        Watch watch = new Watch();
        List<String> stringList = Arrays.asList(strings);
        List<String> arrayList = new ArrayList<String>(MAX);

        arrayList.addAll(stringList);
        String searchString0 = getString(true, MAX / 2 + 10);
        String searchString1 = getString(true, MAX / 2 + 20);

        watch.start();
        arrayList.remove(searchString0);
        arrayList.remove(searchString1);
        watch.totalTime("Array List remove() = ");//20,56,9095 Nanoseconds
    }

    @Test
    public void linkedListRemove() throws Exception {
        Watch watch = new Watch();
        List<String> linkedList = new LinkedList<String>();
        linkedList.addAll(Arrays.asList(strings));

        String searchString0 = getString(true, MAX / 2 + 10);
        String searchString1 = getString(true, MAX / 2 + 20);

        watch.start();
        linkedList.remove(searchString0);
        linkedList.remove(searchString1);
        watch.totalTime("Linked List remove = ");//20,45,4904 Nanoseconds
    }

    //Note: LinkedList is 10 millisecond faster than ArrayList while removing item.

    ///////////////////// SEARCH ///////////////////////////////////////////
    @Test
    public void arrayListSearch() throws Exception {
        Watch watch = new Watch();
        List<String> stringList = Arrays.asList(strings);
        List<String> arrayList = new ArrayList<String>(MAX);

        arrayList.addAll(stringList);
        String searchString0 = getString(true, MAX / 2 + 10);
        String searchString1 = getString(true, MAX / 2 + 20);

        watch.start();
        arrayList.contains(searchString0);
        arrayList.contains(searchString1);
        watch.totalTime("Array List addAll() time = ");//186,15,704
    }

    @Test
    public void linkedListSearch() throws Exception {
        Watch watch = new Watch();
        List<String> linkedList = new LinkedList<String>();
        linkedList.addAll(Arrays.asList(strings));

        String searchString0 = getString(true, MAX / 2 + 10);
        String searchString1 = getString(true, MAX / 2 + 20);

        watch.start();
        linkedList.contains(searchString0);
        linkedList.contains(searchString1);
        watch.totalTime("Linked List addAll() time = ");//189,64,981
    }

    //Note: Linked List is 500 Milliseconds faster than ArrayList

    class Watch {
        private long startTime;
        private long endTime;

        public void start() {
            startTime = System.nanoTime();
        }

        private void stop() {
            endTime = System.nanoTime();
        }

        public void totalTime(String s) {
            stop();
            System.out.println(s + (endTime - startTime));
        }
    }


    private String[] maxArray() {
        String[] strings = new String[MAX];
        Boolean result = Boolean.TRUE;
        for (int i = 0; i < MAX; i++) {
            strings[i] = getString(result, i);
            result = !result;
        }
        return strings;
    }

    private String getString(Boolean result, int i) {
        return String.valueOf(result) + i + String.valueOf(!result);
    }
}

1
ArrayList ไม่จำเป็นต้องเพิ่มเป็นสองเท่าเพื่อความแม่นยำ โปรดตรวจสอบแหล่งที่มาก่อน
Danubian Sailor

ควรสังเกตว่าตัวอย่างของคุณมีข้อบกพร่อง ... คุณกำลังนำออกจากสตริงระหว่าง: 18 + [2, 12] ไบต์ ("true0false", "true500000false") โดยเฉลี่ย 25 ​​ไบต์ซึ่งมีขนาดขององค์ประกอบ ระหว่างกลาง. เป็นที่ทราบกันว่าเมื่อขนาดองค์ประกอบของไบต์เพิ่มรายการที่ลิงก์จะทำงานได้ดีขึ้นเมื่อขนาดของรายการเพิ่มขึ้นอาเรย์ที่ต่อเนื่องกัน (รายการ) จะทำได้ดีกว่า สิ่งสำคัญที่สุดคือคุณกำลังทำ. equals () กับสตริง - ซึ่งไม่ใช่การดำเนินการที่ถูก หากคุณใช้จำนวนเต็มแทนฉันคิดว่าจะมีความแตกต่าง
Centril

2
"... แย่กว่าในแอปพลิเคชั่นปริมาณมาก ": นี่เป็นความเข้าใจผิด LinkedListมีโอเวอร์เฮดของหน่วยความจำมากกว่าเพราะสำหรับทุกองค์ประกอบจะมีโหนดวัตถุที่มีห้าฟิลด์ ในหลาย ๆ ระบบที่ใช้งานเกิน 20 ไบต์ ค่าใช้จ่ายหน่วยความจำเฉลี่ยต่อองค์ประกอบสำหรับArrayListหนึ่งและครึ่งคำซึ่งทำให้ 6 ไบต์และ 8 ไบต์ในกรณีที่เลวร้ายที่สุด
Lii

1
ฉันทำเบนช์มาร์กของคุณในเวอร์ชันที่ดีขึ้นพร้อมผลลัพธ์ - ประสิทธิภาพการทำงานแบบผนวกท้ายสำหรับอาร์เรย์นั้นต่ำสำหรับคุณเนื่องจาก addAll ให้อาร์เรย์หน่วยเก็บข้อมูลที่มีขนาดเริ่มต้นแน่นอนดังนั้นการแทรกครั้งแรกจะทำให้ arraycopy นอกจากนี้ยังรวมถึงวงจรการอุ่นเครื่องเพื่ออนุญาตให้มีการรวบรวม JIT ก่อนที่จะรวบรวมข้อมูล
BobMcGee

4
@BillK ตั้งแต่ Java 8 คุณสามารถใช้removeIf(element -> condition)ตำแหน่งที่เหมาะสมซึ่งเร็วกว่าอย่างมากArrayListเมื่อเทียบกับการวนซ้ำและลบผ่านตัววนซ้ำเนื่องจากไม่จำเป็นต้องเลื่อนส่วนที่เหลือทั้งหมดสำหรับองค์ประกอบแต่ละส่วน ไม่ว่าจะเป็นการดำเนินการที่ดีกว่านี้หรือเลวร้ายยิ่งกว่าLinkedListขึ้นอยู่กับสถานการณ์โดยเฉพาะอย่างยิ่งในฐานะLinkedListเป็น O (1) ในทฤษฎี แต่ลบเพียงโหนดเดียวต้องมีการเข้าถึงหน่วยความจำหลายอย่างซึ่งสามารถเกินจำนวนที่จำเป็นสำหรับArrayListการถอดเป็นจำนวนมากขององค์ประกอบ .
Holger

50

ArrayListเป็นอาร์เรย์ LinkedListดำเนินการเป็นรายการเชื่อมโยงสองครั้ง

ความgetสวยใส O (1) สำหรับArrayListเนื่องจากArrayListอนุญาตการเข้าถึงแบบสุ่มโดยใช้ดัชนี O (n) สำหรับLinkedListเพราะจำเป็นต้องค้นหาดัชนีก่อน หมายเหตุ: มีรุ่นที่แตกต่างกันของและaddremove

LinkedListเพิ่มและลบได้เร็วขึ้น แต่รับช้ากว่า โดยสังเขปLinkedListควรเป็นที่ต้องการหาก:

  1. ไม่มีการเข้าถึงองค์ประกอบแบบสุ่มจำนวนมาก
  2. มีการดำเนินการเพิ่ม / ลบจำนวนมาก

=== ArrayList ===

  • เพิ่ม (E e)
    • เพิ่มที่ส่วนท้ายของ ArrayList
    • ต้องมีการปรับขนาดหน่วยความจำ
    • O (n) แย่ที่สุด, O (1) ตัดจำหน่าย
  • เพิ่ม (ดัชนี int, องค์ประกอบ E)
    • เพิ่มไปยังตำแหน่งดัชนีเฉพาะ
    • ต้องเปลี่ยนค่าใช้จ่ายในการปรับขนาดและหน่วยความจำที่เป็นไปได้
    • บน)
  • ลบ (ดัชนี int)
    • ลบองค์ประกอบที่ระบุ
    • ต้องเปลี่ยนค่าใช้จ่ายในการปรับขนาดและหน่วยความจำที่เป็นไปได้
    • บน)
  • ลบ (วัตถุ o)
    • ลบการเกิดขึ้นครั้งแรกขององค์ประกอบที่ระบุจากรายการนี้
    • จำเป็นต้องค้นหาองค์ประกอบก่อนจากนั้นจึงเลื่อนและปรับขนาดหน่วยความจำที่เป็นไปได้
    • บน)

=== LinkedList ===

  • เพิ่ม (E e)

    • เพิ่มในตอนท้ายของรายการ
    • O (1)
  • เพิ่ม (ดัชนี int, องค์ประกอบ E)

    • แทรกที่ตำแหน่งที่ระบุ
    • จำเป็นต้องค้นหาตำแหน่งก่อน
    • บน)
  • ลบ ()
    • ลบองค์ประกอบแรกของรายการ
    • O (1)
  • ลบ (ดัชนี int)
    • ลบองค์ประกอบที่มีดัชนีที่ระบุ
    • จำเป็นต้องค้นหาองค์ประกอบก่อน
    • บน)
  • ลบ (วัตถุ o)
    • ลบการเกิดขึ้นครั้งแรกขององค์ประกอบที่ระบุ
    • จำเป็นต้องค้นหาองค์ประกอบก่อน
    • บน)

นี่คือรูปจากprogramcreek.com ( addและremoveเป็นประเภทแรกเช่นเพิ่มองค์ประกอบที่ส่วนท้ายของรายการและลบองค์ประกอบที่ตำแหน่งที่ระบุในรายการ):

ป้อนคำอธิบายรูปภาพที่นี่


3
"LinkedList เร็วกว่าการเพิ่ม / ลบ" ไม่ถูกต้องตรวจสอบคำตอบด้านบนstackoverflow.com/a/7507740/638670
Nerrve

49

Joshua Bloch ผู้แต่งของ LinkedList:

ใครบ้างที่ใช้ LinkedList ฉันเขียนมันและฉันไม่เคยใช้มัน

ลิงก์: https://twitter.com/joshbloch/status/583813919019573248

ฉันขอโทษสำหรับคำตอบที่ไม่ใช่ข้อมูลที่เป็นคำตอบอื่น ๆ แต่ฉันคิดว่ามันจะน่าสนใจที่สุดและอธิบายตนเอง


34

ArrayListสามารถเข้าถึงได้แบบสุ่มในขณะที่LinkedListราคาถูกจริงๆในการขยายและลบองค์ประกอบออก สำหรับกรณีส่วนใหญ่ArrayListเป็นเรื่องปกติ

หากคุณไม่ได้สร้างรายการขนาดใหญ่และวัดคอขวดคุณอาจไม่ต้องกังวลกับความแตกต่าง


15
LinkedList ไม่ถูกที่จะเพิ่มองค์ประกอบเข้าไป การเพิ่มองค์ประกอบหลายล้านรายการลงใน ArrayList นั้นเร็วกว่าการเพิ่มองค์ประกอบเหล่านั้นไปยัง LinkedList และรายการส่วนใหญ่ในรหัสโลกแห่งความเป็นจริงนั้นไม่ได้มีความยาวถึงหนึ่งล้านองค์ประกอบ
Porculus

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

4
การเพิ่มรายการเดียวใน ArrayList คือ O (1) ไม่ว่าจะเป็น 1 ล้านหรือ 1 พันล้านรายการก็ตาม การเพิ่มไอเท็มไปยัง LinkedList ยังเป็น O (1) "การเพิ่ม" หมายถึงการเพิ่มไปยังจุดสิ้นสุด
kachanov

คุณต้องอ่านการนำไปปฏิบัติต่างจากฉัน จากประสบการณ์ของฉันการคัดลอกอาร์เรย์องค์ประกอบ 1 พันล้านใช้เวลานานกว่าการคัดลอกอาร์เรย์องค์ประกอบ 1 ล้านรายการ
ดัสติน

6
@kachanov คุณต้องเข้าใจผิดว่าดัสติน หากคุณไม่ได้ประกาศอาร์เรย์ของ 1 พันล้านรายการคุณจะต้องปรับขนาดอาร์เรย์ของคุณในที่สุดซึ่งในกรณีนี้คุณจะต้องคัดลอกองค์ประกอบทั้งหมดลงในอาร์เรย์ใหม่ที่ใหญ่กว่าดังนั้นบางครั้งคุณจะได้ O (N) อย่างไรก็ตามด้วยรายการที่เชื่อมโยง รับ O (1)
Stan R.

29

TL; DRเนื่องจากสถาปัตยกรรมคอมพิวเตอร์ที่ทันสมัยArrayListจะมีประสิทธิภาพมากขึ้นอย่างมากสำหรับเกือบทุกกรณีการใช้งานที่เป็นไปได้ - ดังนั้นจึงLinkedListควรหลีกเลี่ยงยกเว้นบางกรณีที่พิเศษและสุดขั้ว


ในทางทฤษฎี LinkedList มี O (1) สำหรับ add(E element)

การเพิ่มองค์ประกอบในช่วงกลางของรายการควรมีประสิทธิภาพมาก

การปฏิบัติแตกต่างกันมากเนื่องจาก LinkedList เป็นโครงสร้างข้อมูลแคชของศัตรู จากการปฏิบัติ POV - มีกรณีน้อยมากที่LinkedListจะได้รับการปฏิบัติที่ดีขึ้นกว่าแคชง่าย ArrayList

นี่คือผลลัพธ์ของการทดสอบเกณฑ์มาตรฐานที่แทรกองค์ประกอบในสถานที่สุ่ม อย่างที่คุณเห็น - รายการอาร์เรย์ถ้ามีประสิทธิภาพมากขึ้นแม้ว่าในทางทฤษฎีแทรกแต่ละครั้งตรงกลางของรายการจะต้อง "ย้าย" องค์ประกอบnภายหลังของอาร์เรย์ (ค่าที่ต่ำกว่าดีกว่า):

ป้อนคำอธิบายรูปภาพที่นี่

ทำงานกับฮาร์ดแวร์รุ่นใหม่กว่า (แคชที่ใหญ่กว่าและมีประสิทธิภาพมากกว่า) - ผลลัพธ์มีข้อสรุปที่ชัดเจนยิ่งขึ้น:

ป้อนคำอธิบายรูปภาพที่นี่

LinkedList ใช้เวลามากขึ้นในการทำงานเดียวกันให้สำเร็จ แหล่งที่มา รหัสที่มา

มีสองเหตุผลหลักสำหรับสิ่งนี้:

  1. ส่วนใหญ่ - ที่โหนดของLinkedListจะกระจายไปทั่วหน่วยความจำแบบสุ่ม RAM ("หน่วยความจำเข้าถึงโดยสุ่ม") ไม่ใช่หน่วยสุ่มและบล็อกหน่วยความจำต้องถูกนำไปแคช การดำเนินการนี้ต้องใช้เวลาและเมื่อการดึงข้อมูลดังกล่าวเกิดขึ้นบ่อยครั้ง - หน้าหน่วยความจำในแคชต้องถูกแทนที่ตลอดเวลา -> แคชหายไป -> แคชไม่มีประสิทธิภาพ ArrayListองค์ประกอบจะถูกเก็บไว้ในหน่วยความจำอย่างต่อเนื่องซึ่งเป็นสิ่งที่สถาปัตยกรรม CPU สมัยใหม่ปรับให้เหมาะสม

  2. รอง LinkedListต้องถือกลับ / ชี้ไปข้างหน้าซึ่งหมายถึง 3 ArrayListครั้งใช้หน่วยความจำต่อค่าที่เก็บไว้เมื่อเทียบกับ

DynamicIntArray , btw, คือการใช้งาน ArrayList แบบกำหนดเองInt(ประเภทดั้งเดิม) ไม่ใช่วัตถุ - ดังนั้นข้อมูลทั้งหมดจะถูกจัดเก็บอย่างติดกัน - จึงมีประสิทธิภาพมากขึ้น

องค์ประกอบสำคัญที่ต้องจำคือค่าใช้จ่ายในการดึงบล็อกหน่วยความจำมีความสำคัญมากกว่าค่าใช้จ่ายในการเข้าถึงเซลล์หน่วยความจำเดียว นั่นเป็นสาเหตุที่หน่วยความจำเรียงตามลำดับของผู้อ่าน 1MB เร็วกว่า x400 เท่าเมื่ออ่านข้อมูลจำนวนนี้จากบล็อกหน่วยความจำที่แตกต่างกัน:

Latency Comparison Numbers (~2012)
----------------------------------
L1 cache reference                           0.5 ns
Branch mispredict                            5   ns
L2 cache reference                           7   ns                      14x L1 cache
Mutex lock/unlock                           25   ns
Main memory reference                      100   ns                      20x L2 cache, 200x L1 cache
Compress 1K bytes with Zippy             3,000   ns        3 us
Send 1K bytes over 1 Gbps network       10,000   ns       10 us
Read 4K randomly from SSD*             150,000   ns      150 us          ~1GB/sec SSD
Read 1 MB sequentially from memory     250,000   ns      250 us
Round trip within same datacenter      500,000   ns      500 us
Read 1 MB sequentially from SSD*     1,000,000   ns    1,000 us    1 ms  ~1GB/sec SSD, 4X memory
Disk seek                           10,000,000   ns   10,000 us   10 ms  20x datacenter roundtrip
Read 1 MB sequentially from disk    20,000,000   ns   20,000 us   20 ms  80x memory, 20X SSD
Send packet CA->Netherlands->CA    150,000,000   ns  150,000 us  150 ms

ที่มา: หมายเลขแฝงที่โปรแกรมเมอร์ทุกคนควรรู้

เพียงเพื่อให้จุดชัดเจนยิ่งขึ้นโปรดตรวจสอบมาตรฐานของการเพิ่มองค์ประกอบไปยังจุดเริ่มต้นของรายการ นี่คือกรณีใช้งานซึ่งในทางทฤษฎีผู้LinkedListควรส่องแสงจริงและArrayListควรแสดงผลลัพธ์ที่ไม่ดีหรือแย่ลง:

ป้อนคำอธิบายรูปภาพที่นี่

หมายเหตุ: นี่เป็นมาตรฐานของ C ++ Std lib แต่ประสบการณ์ที่ผ่านมาของฉันแสดงให้เห็นว่าผลลัพธ์ C ++ และ Java นั้นคล้ายคลึงกันมาก รหัสแหล่งที่มา

การคัดลอกหน่วยความจำต่อเนื่องเป็นจำนวนมากเป็นการดำเนินการที่ได้รับการปรับแต่งโดยซีพียูที่ทันสมัย ​​- ทฤษฎีที่เปลี่ยนแปลงและทำให้เกิดขึ้นจริงArrayList/ Vectorมีประสิทธิภาพมากขึ้น


เครดิต: มาตรฐานทั้งหมดที่โพสต์ที่นี่ถูกสร้างขึ้นโดยKjell Hedström ข้อมูลเพิ่มเติมสามารถพบได้ในบล็อกของเขา


ฉันจะไม่เรียกคิวที่ไม่ซ้ำใครหรือสุดขีด! มีการใช้งาน Fifo queue ได้ง่ายขึ้นใน LinkedList แทนที่จะเป็น ArrayList จริงๆแล้วมันเป็นฝันร้ายใน ArrayList ที่คุณต้องติดตามการเริ่มต้นของคุณเองหยุดและทำการจัดสรรใหม่ของคุณเองคุณอาจใช้อาเรย์ แต่รายการที่เชื่อมโยงคือเอฟเฟ็กต์ ฉันไม่แน่ใจเกี่ยวกับการใช้งาน Java แต่ LinkedList สามารถทำ O (1) สำหรับการดำเนินการคิวและการถอน (ต้องใช้ตัวชี้พิเศษไปที่องค์ประกอบหางสำหรับการลบซึ่งฉันคิดว่า java มี แต่ฉันไม่ได้ตรวจสอบอีกครั้ง .)
Bill K

24

หากรหัสของคุณมีadd(0)และremove(0)ให้ใช้LinkedListและเป็นaddFirst()และremoveFirst()วิธีการที่ดีกว่า ArrayListมิฉะนั้นการใช้งาน

และแน่นอนฝรั่ง 's ImmutableListเป็นเพื่อนที่ดีที่สุดของคุณ


3
สำหรับรายการขนาดเล็ก ArrayList.add (0) จะยังคงเร็วกว่า LinkedList.addFirst () เสมอ
Porculus

1
@Porculus ฉันได้ยินอาร์กิวเมนต์นี้อยู่เสมอว่าสำหรับรายการเล็ก ๆ ArrayList.add (0) จะเร็วขึ้นเล็ก ๆ นี้มีขนาดเล็กเท่าใด 10 องค์ประกอบ 10 ล้าน
garg10may

1
@ garg10may เล็กน้อยกว่า 10
Jesse Wilson

@Porculus ขนาดเล็กหมายถึงน้อยกว่าความจุสูงสุดของอาร์เรย์ภายในที่อ้างอิง ArrayList
Janac Meena

21

ฉันรู้ว่านี้คือการโพสต์เก่า แต่ผมไม่สามารถเชื่อว่าไม่มีใครบอกว่าการดำเนินการLinkedList Dequeเพียงแค่ดูวิธีการในDeque(และQueue); ถ้าคุณต้องการเปรียบเทียบยุติธรรมให้ลองทำงานLinkedListกับArrayDequeและทำการเปรียบเทียบคุณลักษณะสำหรับคุณลักษณะ


18

นี่คือสัญกรณ์ Big-O ทั้งArrayListและLinkedListและCopyOnWrite-ArrayList:

ArrayList

get                 O(1)
add                 O(1)
contains            O(n)
next                O(1)
remove              O(n)
iterator.remove     O(n)

LinkedList

get                 O(n)
add                 O(1)
contains            O(n)
next                O(1)
remove              O(1)
iterator.remove     O(1)

CopyOnWrite-ArrayList

get                 O(1)
add                 O(n)
contains            O(n)
next                O(1)
remove              O(n)
iterator.remove     O(n)

จากสิ่งเหล่านี้คุณต้องตัดสินใจว่าจะเลือกอะไร :)


9
>>>> เพิ่ม ArrayList -> O (1) <- ไม่ใช่ tru ในบางกรณี ArrayList จะต้องเติบโตเพื่อเพิ่มองค์ประกอบอีกหนึ่งรายการ
kachanov

1
การลบรายการที่เชื่อมโยงไม่ใช่ O (1) มันจะต้องค้นหาองค์ประกอบที่จะถูกลบออกดังนั้นกรณีที่แย่ที่สุด O (n) และค่าเฉลี่ย O (n / 2)
garg10may

ไม่เป็นLinkedList.add()ถึงแม้ว่าส่วนใหญ่ของคำตอบที่นี่พูดอย่างนั้น
user207421

18

ลองเปรียบเทียบ LinkedList และ ArrayList wrt ด้านล่างพารามิเตอร์:

1. การดำเนินการ

ArrayListคือการนำอาเรย์ไปใช้งานที่ปรับขนาดได้ของ list interface ในขณะที่

LinkedListเป็นการใช้งานลิสต์ของ Doubly-linked list ของ interface list


2. ประสิทธิภาพ

  • รับ (ดัชนี int) หรือการดำเนินการค้นหา

    ArrayList get (int index) ทำงานในเวลาคงที่เช่น O (1) ในขณะที่

    เวลาเรียกใช้การดำเนินการLinkedList get (ดัชนี int) คือ O (n)

    เหตุผลที่อยู่เบื้องหลังArrayListนั้นเร็วกว่า LinkedList นั่นก็คือ ArrayList ใช้ระบบที่อิงกับดัชนีสำหรับองค์ประกอบเนื่องจากมันใช้โครงสร้างข้อมูลอาร์เรย์ภายในในทางกลับกัน

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

  • การดำเนินการแทรก () หรือเพิ่ม (วัตถุ)

    โดยทั่วไปแล้วการแทรกในLinkedListจะเร็วกว่าการเปรียบเทียบกับ ArrayList ใน LinkedList การเพิ่มหรือการแทรกเป็นการดำเนินการ O (1)

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

  • ดำเนินการลบ (int)

    การดำเนินการลบใน LinkedList โดยทั่วไปจะเหมือนกับ ArrayList เช่น O (n)

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

    ในขณะที่วิธีArrayList remove (int) เกี่ยวข้องกับการคัดลอกองค์ประกอบจากอาร์เรย์เก่าไปยังอาร์เรย์ที่อัปเดตใหม่ดังนั้นรันไทม์ของมันคือ O (n)


3. ย้อนกลับ Iterator

LinkedListสามารถทำซ้ำในทิศทางย้อนกลับได้โดยใช้ descendingIterator () ในขณะที่

ไม่มี descendingIterator () ในArrayListดังนั้นเราต้องเขียนโค้ดของเราเองเพื่อวนซ้ำ ArrayList ในทิศทางตรงกันข้าม


4. กำลังการผลิตเริ่มต้น

หาก Constructor ไม่โหลดมากเกินไปArrayListจะสร้างรายการว่างของความจุเริ่มต้น 10 ในขณะที่

LinkedList จะสร้างรายการเปล่าโดยไม่ต้องมีความจุเริ่มต้น


5. หน่วยความจำเหนือศีรษะ

ค่าใช้จ่ายในหน่วยความจำในLinkedListนั้นมากกว่า ArrayList ซึ่งเป็นโหนดใน LinkedList จำเป็นต้องรักษาที่อยู่ของโหนดถัดไปและโหนดก่อนหน้า ในขณะที่

ในArrayList แต่ละดัชนีเก็บเฉพาะวัตถุจริง (ข้อมูล)


แหล่ง


18

ฉันมักจะใช้อย่างใดอย่างหนึ่งมากกว่าอื่นขึ้นอยู่กับความซับซ้อนของเวลาของการดำเนินงานที่ฉันจะแสดงในรายการนั้น

|---------------------|---------------------|--------------------|------------|
|      Operation      |     ArrayList       |     LinkedList     |   Winner   |
|---------------------|---------------------|--------------------|------------|
|     get(index)      |       O(1)          |         O(n)       | ArrayList  |
|                     |                     |  n/4 steps in avg  |            |
|---------------------|---------------------|--------------------|------------|
|      add(E)         |       O(1)          |         O(1)       | LinkedList |
|                     |---------------------|--------------------|            |
|                     | O(n) in worst case  |                    |            |
|---------------------|---------------------|--------------------|------------|
|    add(index, E)    |       O(n)          |         O(n)       | LinkedList |
|                     |     n/2 steps       |      n/4 steps     |            |
|                     |---------------------|--------------------|            |
|                     |                     |  O(1) if index = 0 |            |
|---------------------|---------------------|--------------------|------------|
|  remove(index, E)   |       O(n)          |         O(n)       | LinkedList |
|                     |---------------------|--------------------|            |
|                     |     n/2 steps       |      n/4 steps     |            |
|---------------------|---------------------|--------------------|------------|
|  Iterator.remove()  |       O(n)          |         O(1)       | LinkedList |
|  ListIterator.add() |                     |                    |            |
|---------------------|---------------------|--------------------|------------|


|--------------------------------------|-----------------------------------|
|              ArrayList               |            LinkedList             |
|--------------------------------------|-----------------------------------|
|     Allows fast read access          |   Retrieving element takes O(n)   |
|--------------------------------------|-----------------------------------|
|   Adding an element require shifting | o(1) [but traversing takes time]  |
|       all the later elements         |                                   |
|--------------------------------------|-----------------------------------|
|   To add more elements than capacity |
|    new array need to be allocated    |
|--------------------------------------|

ArrayDeque สร้างความสมดุลให้กับอาร์เรย์มากขึ้นอีกเล็กน้อยเนื่องจากการแทรก / ลบด้านหน้า / ด้านหลังนั้นเป็น O (1) ทั้งหมดสิ่งเดียวที่เชื่อมโยงกับรายการที่ชนะก็คือการเพิ่ม / เอาออกขณะที่การสำรวจ (การดำเนินการ Iterator)
Bill K

14

นอกจากนี้ยังมีข้อโต้แย้งที่ดีอื่น ๆ ข้างต้นคุณควรแจ้งให้ทราบArrayListการดำเนินการRandomAccessติดต่อในขณะที่การดำเนินการLinkedListQueue

ดังนั้นพวกเขาจึงจัดการกับปัญหาที่แตกต่างกันเล็กน้อยด้วยความแตกต่างของประสิทธิภาพและพฤติกรรม (ดูรายการวิธีการ)


10

ขึ้นอยู่กับว่าคุณจะทำอะไรมากขึ้นในรายการ

ArrayListเร็วกว่าในการเข้าถึงค่าดัชนี มันจะแย่กว่ามากเมื่อใส่หรือลบวัตถุ

หากต้องการข้อมูลเพิ่มเติมอ่านบทความที่พูดถึงความแตกต่างระหว่างอาร์เรย์และรายการที่ลิงก์


2
เพื่อหาข้อมูลเพิ่มเติมอย่าอ่านเพียงแค่เขียนรหัส และคุณจะพบว่าการติดตั้ง ArrayList นั้นเร็วขึ้นจากนั้น LinkedList ในการแทรกและการลบ
kachanov

8

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

รายการที่เชื่อมโยงระบุความก้าวหน้าจากรายการหนึ่งไปยังอีกรายการหนึ่ง (รายการก -> รายการข) คุณสามารถรับเอฟเฟกต์แบบเดียวกันกับรายการอาร์เรย์ได้ แต่รายการที่เชื่อมโยงจะบอกว่ารายการใดที่ควรจะติดตามก่อนหน้านี้


8

ดูชวาสอน - รายชื่อการใช้งาน


2
สวัสดี @chharvey เชื่อมโยงคำตอบเท่านั้นรับ 6 Upvotes โปรดเพิ่มบางจุดที่สามารถรองรับลิงค์ได้ถ้าหาก Oracle ทำการเปลี่ยนแปลงลิงค์

7

คุณสมบัติที่สำคัญของรายการที่เชื่อมโยง (ซึ่งฉันไม่ได้อ่านในคำตอบอื่น) คือการต่อข้อมูลสองรายการ ด้วยอาเรย์นี้คือ O (n) (+ ค่าใช้จ่ายของการปันส่วนบางส่วน) พร้อมรายการที่เชื่อมโยงนี่เป็นเพียง O (1) หรือ O (2) ;-)

สำคัญ : สำหรับ Java LinkedListสิ่งนี้ไม่เป็นความจริง! ดูมีวิธีการเชื่อมโยงที่รวดเร็วสำหรับรายการที่ลิงก์ใน Java หรือไม่


2
มันเป็นอย่างไร สิ่งนี้อาจเป็นจริงกับโครงสร้างข้อมูลรายการที่ลิงก์ แต่ไม่ใช่วัตถุ Java LinkList คุณไม่สามารถชี้nextจากรายการหนึ่งไปยังโหนดแรกในรายการที่สอง วิธีเดียวคือการใช้addAll()ซึ่งเพิ่มองค์ประกอบตามลำดับแม้ว่าจะดีกว่าการวนซ้ำผ่านและเรียกadd()แต่ละองค์ประกอบ ในการทำสิ่งนี้อย่างรวดเร็วใน O (1) คุณจะต้องมีคลาส compositing (เช่น org.apache.commons.collections.collection.CompositeCollection) แต่ก็ใช้ได้กับ List / Collection ทุกประเภท
เควินบร็อค

ใช่จริง ฉันแก้ไขคำตอบตามนั้น แต่ดูคำตอบนี้สำหรับ 'วิธี' ทำได้ด้วย LinkedList: stackoverflow.com/questions/2494031/…
Karussell

7

ArrayList และ LinkedList มีข้อดีและข้อเสียของตัวเอง

ArrayList ใช้ที่อยู่หน่วยความจำต่อเนื่องเปรียบเทียบกับ LinkedList ซึ่งใช้ตัวชี้ไปยังโหนดถัดไป ดังนั้นเมื่อคุณต้องการค้นหาองค์ประกอบใน ArrayList จะเร็วกว่าการทำ n ซ้ำด้วย LinkedList

ในทางกลับกันการแทรกและการลบใน LinkedList นั้นง่ายกว่ามากเพราะคุณเพียงแค่เปลี่ยนพอยน์เตอร์ในขณะที่ ArrayList หมายถึงการใช้การดำเนินการกะสำหรับการแทรกหรือการลบใด ๆ

หากคุณมีการดำเนินการดึงข้อมูลบ่อยครั้งในแอปของคุณให้ใช้ ArrayList หากคุณมีการแทรกและลบบ่อยครั้งให้ใช้ LinkedList


6

ฉันได้อ่านคำตอบแล้ว แต่มีสถานการณ์หนึ่งที่ฉันจะใช้ LinkedList เหนือ ArrayList ที่ฉันต้องการแบ่งปันเพื่อรับฟังความคิดเห็นเสมอ:

ทุกครั้งที่ฉันมีวิธีที่ส่งคืนรายการข้อมูลที่ได้รับจากฐานข้อมูลฉันจะใช้ LinkedList ทุกครั้ง

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

สำหรับ ArrayList ฉันยอมรับว่าอย่างน้อยที่สุดคุณควรใช้ Constructor กับความสามารถเริ่มต้นเพื่อลดการทำซ้ำของ ArrayList ให้น้อยที่สุดเท่าที่จะทำได้


5

ArrayListและLinkedListการดำเนินการList interface และวิธีการและผลลัพธ์ของพวกเขาเกือบจะเหมือนกัน อย่างไรก็ตามมีความแตกต่างเล็กน้อยระหว่างสิ่งเหล่านั้นซึ่งทำให้ดีขึ้นกว่ากันขึ้นอยู่กับข้อกำหนด

ArrayList Vs LinkedList

1) Search: ArrayListการดำเนินการค้นหาค่อนข้างเร็วเมื่อเทียบกับการLinkedListค้นหา get(int index)ในการArrayListช่วยให้ประสิทธิภาพการทำงานของO(1)ในขณะที่ประสิทธิภาพการทำงานLinkedListO(n)

Reason: ArrayListรักษาระบบที่อิงดัชนีสำหรับองค์ประกอบในขณะที่มันใช้โครงสร้างข้อมูลอาร์เรย์โดยปริยายซึ่งทำให้เร็วขึ้นสำหรับการค้นหาองค์ประกอบในรายการ ในอีกด้านหนึ่งLinkedListใช้การเชื่อมโยงรายการที่ต้องผ่านการสำรวจผ่านองค์ประกอบทั้งหมดสำหรับการค้นหาองค์ประกอบ

2) Deletion: LinkedListการดำเนินการลบให้O(1)ประสิทธิภาพในขณะที่ArrayListให้ประสิทธิภาพของตัวแปร: O(n)ในกรณีที่แย่ที่สุด (ในขณะที่ลบองค์ประกอบแรก) และO(1)ในกรณีที่ดีที่สุด (ในขณะที่ลบองค์ประกอบสุดท้าย)

สรุป: การลบองค์ประกอบ LinkedList จะเร็วกว่าการเปรียบเทียบกับ ArrayList

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

3) Inserts Performance: LinkedListวิธีการเพิ่มให้O(1)ประสิทธิภาพในขณะที่ArrayListให้O(n)ในกรณีที่เลวร้ายที่สุด เหตุผลเป็นเช่นเดียวกับที่อธิบายไว้สำหรับการลบ

4) Memory Overhead: ArrayListเก็บรักษาดัชนีและข้อมูลองค์ประกอบในขณะที่LinkedListรักษาข้อมูลองค์ประกอบและสองตัวชี้สำหรับโหนดเพื่อนบ้าน

ดังนั้นปริมาณการใช้หน่วยความจำสูงใน LinkedList เปรียบเทียบ

มีความคล้ายคลึงกันเล็กน้อยระหว่างคลาสเหล่านี้ซึ่งมีดังนี้:

  • ทั้ง ArrayList และ LinkedList เป็นการใช้งานรายการอินเตอร์เฟส
  • พวกเขาทั้งสองรักษาลำดับการแทรกองค์ประกอบซึ่งหมายความว่าในขณะที่การแสดงองค์ประกอบ ArrayList และ LinkedList ชุดผลลัพธ์จะมีลำดับเดียวกันกับที่องค์ประกอบได้แทรกลงในรายการ
  • ทั้งสองคลาสนี้ไม่ได้ซิงโครไนซ์และสามารถซิงโครไนซ์อย่างชัดเจนโดยใช้วิธีการ Collections.synchronizedList
  • กระบวนการiteratorและlistIteratorส่งกลับโดยคลาสเหล่านี้คือfail-fast(ถ้ารายการถูกปรับเปลี่ยนโครงสร้างได้ตลอดเวลาหลังจากที่มีการสร้างตัววนซ้ำในลักษณะใด ๆ ยกเว้นผ่านวิธีการiterator’sเอาออกหรือเพิ่มของตัวเอง iterator จะthrowa ConcurrentModificationException)

จะใช้ LinkedList เมื่อใดและจะใช้ ArrayList เมื่อใด

  • ตามที่อธิบายไว้ข้างต้นใส่และถอดการดำเนินงานให้ผลงานที่ดี(O(1))ในเมื่อเทียบกับ LinkedListArrayList(O(n))

    ดังนั้นหากมีความต้องการของการเพิ่มและการลบบ่อยครั้งในแอปพลิเคชันแล้ว LinkedList เป็นตัวเลือกที่ดีที่สุด

  • get methodการดำเนินการSearch ( ) นั้นรวดเร็วArraylist (O(1))แต่ไม่ได้อยู่ในLinkedList (O(n))

    ดังนั้นหากมีการเพิ่มและลบการดำเนินงานน้อยลงและความต้องการการดำเนินการค้นหาเพิ่มเติม ArrayList จะเป็นทางออกที่ดีที่สุดของคุณ


5

การดำเนินงานได้รับ (i) ใน ArrayList จะเร็วกว่า LinkedList เพราะ:
ArrayList:การดำเนินงานที่ปรับขนาดได้อาร์เรย์ของอินเตอร์เฟซรายการ
LinkedList:ทวีคูณเชื่อมโยงการดำเนินงานรายชื่อของรายการและ Deque อินเตอร์เฟซ

การดำเนินการที่ดัชนีลงในรายการจะข้ามรายการจากจุดเริ่มต้นหรือจุดสิ้นสุดแล้วแต่จำนวนใดจะใกล้เคียงกับดัชนีที่ระบุมากขึ้น


5

1) โครงสร้างข้อมูลอ้างอิง

ความแตกต่างแรกระหว่าง ArrayList และ LinkedList มาพร้อมกับข้อเท็จจริงที่ว่า ArrayList ได้รับการสนับสนุนโดย Array ในขณะที่ LinkedList ได้รับการสนับสนุนโดย LinkedList สิ่งนี้จะนำไปสู่ความแตกต่างด้านประสิทธิภาพ

2) LinkedList ใช้ Deque

ความแตกต่างอีกอย่างหนึ่งระหว่าง ArrayList และ LinkedList ก็คือนอกเหนือจากรายการอินเตอร์เฟสแล้ว LinkedList ยังใช้ Deque interface ซึ่งให้การดำเนินการเป็นครั้งแรกสำหรับการเพิ่ม () และ Poll () และฟังก์ชัน Deque อื่น ๆ อีกมากมาย 3) การเพิ่มองค์ประกอบใน ArrayList การเพิ่มองค์ประกอบใน ArrayList คือการดำเนินการ O (1) ถ้ามันไม่ทริกเกอร์ขนาด Array ขนาดใหม่ซึ่งในกรณีนี้จะกลายเป็น O (log (n)) ในทางกลับกันการผนวกองค์ประกอบใน LinkedList เป็นการดำเนินการ O (1) เนื่องจากไม่ต้องการการนำทางใด ๆ

4) การลบองค์ประกอบออกจากตำแหน่ง

เพื่อที่จะลบองค์ประกอบออกจากดัชนีเฉพาะเช่นโดยเรียกการลบ (ดัชนี), ArrayList ทำการดำเนินการคัดลอกซึ่งทำให้มันใกล้กับ O (n) ในขณะที่ LinkedList ต้องข้ามไปยังจุดนั้นซึ่งทำให้ O (n / 2) เนื่องจากสามารถเคลื่อนที่จากทิศทางใดก็ได้ตามระยะทาง

5) วนซ้ำ ArrayList หรือ LinkedList

Iteration เป็นการดำเนินการ O (n) สำหรับทั้ง LinkedList และ ArrayList โดยที่ n คือจำนวนองค์ประกอบ

6) การดึงองค์ประกอบจากตำแหน่ง

การดำเนินการ get (ดัชนี) คือ O (1) ใน ArrayList ในขณะที่ O (n / 2) ใน LinkedList เนื่องจากต้องการสำรวจจนกว่ารายการนั้น แม้ว่าในสัญกรณ์ Big O O (n / 2) เป็นเพียง O (n) เพราะเราไม่สนใจค่าคงที่

7) หน่วยความจำ

LinkedList ใช้ออบเจกต์ wrapper, Entry ซึ่งเป็นคลาสที่ซ้อนกันแบบคงที่สำหรับการจัดเก็บข้อมูลและสองโหนดถัดไปและก่อนหน้าในขณะที่ ArrayList เพียงเก็บข้อมูลใน Array

ดังนั้นความต้องการหน่วยความจำดูเหมือนน้อยกว่าในกรณีของ ArrayList มากกว่า LinkedList ยกเว้นกรณีที่ Array ทำการดำเนินการขนาดใหม่เมื่อคัดลอกเนื้อหาจาก ArrayList หนึ่งไปยังอีก

ถ้า Array มีขนาดใหญ่พออาจใช้หน่วยความจำจำนวนมาก ณ จุดนั้นและเรียกใช้การรวบรวมขยะซึ่งอาจทำให้เวลาตอบสนองช้าลง

จากความแตกต่างด้านบนทั้งหมดระหว่าง ArrayList กับ LinkedList ดูเหมือน ArrayList เป็นตัวเลือกที่ดีกว่า LinkedList ในเกือบทุกกรณียกเว้นเมื่อคุณทำการดำเนินการ add () บ่อยครั้งกว่า remove () หรือ get ()

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

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

ในความคิดของฉันใช้ ArrayList ผ่าน LinkedList เพื่อวัตถุประสงค์เชิงปฏิบัติส่วนใหญ่ใน Java


1
ฉันคิดว่านี่เป็นคำตอบที่ดีที่สุดของกลุ่มทั้งหมดที่นี่ มันถูกต้องและให้ข้อมูล ฉันขอแนะนำให้เปลี่ยนบรรทัดสุดท้าย - ในตอนท้ายให้เพิ่ม "นอกเหนือจากคิว" ซึ่งเป็นโครงสร้างที่สำคัญมากซึ่งไม่สมเหตุสมผลสำหรับรายการที่เชื่อมโยงเลย
Bill K

3

หนึ่งในการทดสอบที่ฉันเห็นที่นี่ดำเนินการทดสอบเพียงครั้งเดียว แต่สิ่งที่ฉันสังเกตเห็นคือคุณต้องทำการทดสอบเหล่านี้หลายครั้งและในที่สุดเวลาของพวกเขาจะมาบรรจบกัน โดยทั่วไป JVM จำเป็นต้องอุ่นเครื่อง สำหรับกรณีการใช้งานเฉพาะของฉันฉันจำเป็นต้องเพิ่ม / ลบรายการไปยังรายการที่เติบโตไปประมาณ 500 รายการ ในการทดสอบของฉันLinkedListออกมาเร็วกว่าโดยมีLinkedListประมาณ 50,000 NS และArrayListเข้ามาที่ประมาณ 90,000 NS ... ให้หรือรับ ดูรหัสด้านล่าง

public static void main(String[] args) {
    List<Long> times = new ArrayList<>();
    for (int i = 0; i < 100; i++) {
        times.add(doIt());
    }
    System.out.println("avg = " + (times.stream().mapToLong(x -> x).average()));
}

static long doIt() {
    long start = System.nanoTime();
    List<Object> list = new LinkedList<>();
    //uncomment line below to test with ArrayList
    //list = new ArrayList<>();
    for (int i = 0; i < 500; i++) {
        list.add(i);
    }

    Iterator it = list.iterator();
    while (it.hasNext()) {
        it.next();
        it.remove();
    }
    long end = System.nanoTime();
    long diff = end - start;
    //uncomment to see the JVM warmup and get faster for the first few iterations
    //System.out.println(diff)
    return diff;
}

2

ทั้ง remove () และ insert () มีประสิทธิภาพรันไทม์ของ O (n) สำหรับทั้ง ArrayLists และ LinkedLists อย่างไรก็ตามเหตุผลที่อยู่เบื้องหลังเวลาประมวลผลเชิงเส้นนั้นมาจากสองสาเหตุที่แตกต่างกันมาก:

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

ใน LinkedList จะต้องใช้ O (n) เพื่อไปยังองค์ประกอบที่ต้องการเพราะเราจะต้องเริ่มต้นตั้งแต่ต้นจนมาถึงดัชนีที่ต้องการ การลบหรือแทรกจริง ๆ แล้วเป็นค่าคงที่เนื่องจากเราต้องเปลี่ยนการอ้างอิง 1 รายการสำหรับการลบ () และการอ้างอิง 2 รายการสำหรับการแทรก ()

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

โบนัส: แม้ว่าจะไม่มีวิธีในการทำทั้งสองวิธี O (1) สำหรับ ArrayList แต่จริงๆแล้วมีวิธีการทำเช่นนี้ใน LinkedLists สมมติว่าเราต้องการผ่านการลบรายการและการแทรกองค์ประกอบทั้งหมดในแบบของเรา โดยปกติแล้วคุณจะเริ่มต้นจากจุดเริ่มต้นมากสำหรับแต่ละองค์ประกอบโดยใช้ LinkedList เราสามารถ "บันทึก" องค์ประกอบปัจจุบันที่เรากำลังทำงานด้วย Iterator ด้วยความช่วยเหลือของ Iterator เราได้รับประสิทธิภาพ O (1) สำหรับการลบ () และ insert () เมื่อทำงานใน LinkedList ทำให้เป็นเพียงประโยชน์ด้านประสิทธิภาพเท่านั้นฉันจึงทราบว่าที่ใด LinkedList จะดีกว่า ArrayList เสมอ


1

ArrayList ขยาย AbstractList และใช้ List Interface ArrayList เป็นอาร์เรย์แบบไดนามิก
อาจกล่าวได้ว่ามันถูกสร้างขึ้นโดยทั่วไปเพื่อเอาชนะข้อเสียของอาร์เรย์

คลาส LinkedList จะขยาย AbstractSequentialList และใช้ List, Deque และ Queue interface
ประสิทธิภาพ
arraylist.get()คือ O (1) ในขณะที่linkedlist.get()O (n)
arraylist.add()คือ O (1) และlinkedlist.add()คือ 0 (1)
arraylist.contains()คือ O (n) และlinkedlist.contains()O (n)
arraylist.next()คือ O (1) และlinkedlist.next()O (1)
arraylist.remove()คือ O (n) โดยที่linkedlist.remove()O (1)
ใน arraylist
iterator.remove()คือ O (n)
ในขณะที่ Inlistlist
iterator.remove()คือ O (1)

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