ฉันเป็นคนหนึ่งที่เคยใช้:
List<String> names = new ArrayList<>();
ฉันใช้อินเทอร์เฟซเป็นชื่อประเภทสำหรับการพกพาดังนั้นเมื่อฉันถามคำถามเช่นสิ่งเหล่านี้ฉันสามารถทำงานซ้ำรหัสของฉันได้
เมื่อควรจะLinkedList
นำมาใช้มากกว่าArrayList
และในทางกลับกัน?
ฉันเป็นคนหนึ่งที่เคยใช้:
List<String> names = new ArrayList<>();
ฉันใช้อินเทอร์เฟซเป็นชื่อประเภทสำหรับการพกพาดังนั้นเมื่อฉันถามคำถามเช่นสิ่งเหล่านี้ฉันสามารถทำงานซ้ำรหัสของฉันได้
เมื่อควรจะLinkedList
นำมาใช้มากกว่าArrayList
และในทางกลับกัน?
คำตอบ:
สรุป 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
ด้วยความจุเริ่มต้นที่สูงขึ้น
O(n/2)
O(n/4)
สัญกรณ์โอใหญ่จะบอกคุณว่าการดำเนินการชั่งน้ำหนักที่มีขนาดใหญ่n และการดำเนินการที่ต้องการn/2
ขั้นตอนจะปรับขนาดเหมือนกับการดำเนินการที่ต้องการn
ขั้นตอนซึ่งเป็นเหตุผลที่ทำให้การสรุปหรือปัจจัยคงที่ถูกลบออกไป O(n/2)
และมีทั้งที่เป็นเพียงแค่O(n/4)
และมีปัจจัยคงที่แตกต่างกันอยู่แล้วดังนั้นจึงไม่ควรเปรียบเทียบสิ่งหนึ่งกับอีกปัจจัยหนึ่งทั้งสองแสดงถึงการดำเนินการปรับขนาดเชิงเส้น O(n)
LinkedList
ArrayList
O(n/2)
O(n/4)
จนถึงขณะนี้ดูเหมือนว่าไม่มีใครได้กล่าวถึงรอยความทรงจำของแต่ละรายการเหล่านี้นอกเหนือจากฉันทามติทั่วไปว่าLinkedList
"มากกว่า" ArrayList
ฉันจึงได้ทำตัวเลขจำนวนหนึ่งเพื่อแสดงให้เห็นว่ารายการทั้งสองนั้นใช้สำหรับการอ้างอิง N N
ตั้งแต่ลำดับที่มีทั้ง 32 หรือ 64 บิต (แม้เมื่อ null) บนระบบญาติของพวกเขาผมได้รวม 4 ชุดของข้อมูลสำหรับ 32 และ 64 บิตและLinkedLists
ArrayLists
หมายเหตุ:ขนาดที่แสดงสำหรับArrayList
บรรทัดสำหรับรายการที่ถูกตัด - ในทางปฏิบัติความจุของอาร์เรย์สำรองใน a ArrayList
นั้นใหญ่กว่าจำนวนองค์ประกอบปัจจุบัน
หมายเหตุ 2: (ขอบคุณ BeeOnRope)เนื่องจาก CompressedOops เป็นค่าเริ่มต้นตั้งแต่กลาง JDK6 ขึ้นไปค่าด้านล่างสำหรับเครื่อง 64- บิตโดยทั่วไปจะตรงกับคู่ 32- บิตของพวกเขาเว้นแต่ว่าคุณจะปิดมันโดยเฉพาะ
ผลลัพธ์แสดงให้เห็นอย่างชัดเจนว่า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)
int
4 หรือ 8 ไบต์เท่านั้น ในรายการที่เชื่อมโยงมีค่าใช้จ่าย 4 "คำ" เป็นหลัก กราฟของคุณให้การแสดงผลที่รายการที่เชื่อมโยงใช้ "ห้าครั้ง" ที่เก็บข้อมูลของรายการอาร์เรย์ นี่เป็นสิ่งที่ผิด ค่าโสหุ้ยคือ 16 หรือ 32 ไบต์ต่อวัตถุเป็นการปรับปรุงเพิ่มเติมไม่ใช่ปัจจัยการปรับ
CompressedOops
ตอนนี้เป็นค่าเริ่มต้นใน JDKs ล่าสุดทั้งหมด (7, 8 และการปรับปรุง 6 เป็นเวลาสองสามปี) ดังนั้น 64- บิตจะไม่สร้างความแตกต่างArrayList
หรือLinkedList
ขนาดยกเว้นว่าคุณได้ปิด oops ที่บีบอัดไว้อย่างชัดเจนสำหรับ เหตุผลบางอย่าง.
ArrayList
LinkedList
ArrayList
คือสิ่งที่คุณต้องการ LinkedList
เกือบทุกข้อบกพร่อง (ประสิทธิภาพ)
ทำไมLinkedList
ดูด:
ArrayList
ใช้ArrayList
แต่ก็อาจช้าลงอย่างมากLinkedList
ในแหล่งที่มาเพราะอาจเป็นทางเลือกที่ผิดในฐานะที่เป็นคนที่ทำงานด้านวิศวกรรมประสิทธิภาพการทำงานบนเว็บเซอร์วิส SOA ขนาดใหญ่มากเป็นเวลาประมาณหนึ่งทศวรรษฉันชอบพฤติกรรมของ LinkedList มากกว่า ArrayList ในขณะที่ปริมาณงานคงที่ของ LinkedList นั้นแย่ลงและอาจนำไปสู่การซื้อฮาร์ดแวร์มากขึ้นพฤติกรรมของ ArrayList ที่อยู่ภายใต้แรงกดดันอาจนำไปสู่แอปในกลุ่มที่ขยายอาร์เรย์ในอาร์เรย์ใกล้เคียงกันและสำหรับอาร์เรย์ขนาดใหญ่อาจทำให้ไม่มีการตอบสนอง ในแอพและไฟดับขณะอยู่ภายใต้แรงกดดันซึ่งเป็นพฤติกรรมหายนะ
ในทำนองเดียวกันคุณสามารถรับปริมาณงานที่ดีขึ้นในแอปจากตัวเก็บขยะที่ผ่านการรับค่าเริ่มต้น แต่เมื่อคุณได้รับแอป java ที่มีฮีป 10GB คุณสามารถปิดท้ายแอพได้เป็นเวลา 25 วินาทีในระหว่าง Full GCs ซึ่งทำให้หมดเวลา และพัด SLA ของคุณถ้ามันเกิดขึ้นบ่อยเกินไป แม้ว่าตัวรวบรวม CMS จะใช้ทรัพยากรมากขึ้นและไม่ได้รับปริมาณงานที่เท่ากัน แต่ก็เป็นตัวเลือกที่ดีกว่าเพราะมีความสามารถในการคาดการณ์และเวลาแฝงที่น้อยลง
ArrayList เป็นเพียงตัวเลือกที่ดีกว่าสำหรับประสิทธิภาพหากสิ่งที่คุณหมายถึงโดยประสิทธิภาพคือปริมาณงานและคุณสามารถเพิกเฉยต่อความล่าช้าได้ จากประสบการณ์ในงานของฉันฉันไม่สามารถเพิกเฉยต่อความล่าช้าในการพิมพ์
LinkedList
เสมอกว่าแหล่งอ้างอิงทั่วไปดังนั้นArrayList
2.5 ครั้งที่ต้องการยังคงใช้หน่วยความจำน้อยกว่าชั่วคราวแม้ว่าหน่วยความจำจะไม่ถูกเรียกคืน เนื่องจากการจัดสรรอาร์เรย์ขนาดใหญ่ข้ามพื้นที่ Eden พวกเขาไม่ได้มีผลกระทบใด ๆ กับพฤติกรรมของ GC เว้นแต่จะมีหน่วยความจำไม่เพียงพอจริง ๆ ซึ่งในกรณีดังกล่าวLinkedList
ก็ระเบิดขึ้นเร็วกว่านี้มาก ...
LinkedList
ต้องการหน่วยความจำว่างฟรีเพียงเล็กน้อยเพื่อจัดสรรสำหรับองค์ประกอบถัดไป ArrayList
จะต้องมีพื้นที่ว่างขนาดใหญ่และต่อเนื่องเพื่อจัดสรรอาร์เรย์ที่ปรับขนาดแล้ว หากฮีปได้รับการแยกส่วนแล้ว GC อาจสิ้นสุดการเรียงลำดับฮีปทั้งหมดใหม่เพื่อเพิ่มหน่วยความจำบล็อกเดียวที่เหมาะสม
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 นั้นเหมาะสำหรับการเขียนครั้งเดียวอ่านหลายคนหรือต่อท้าย แต่ไม่ดีที่การเพิ่ม / ลบจากด้านหน้าหรือกลาง
O(1)
ได้ มีการเรียกใช้ผ่านครึ่งรายการเพื่อค้นหาจุดแทรก
LinkedList
คือ O(1)
ถ้าคุณมี iterator ไปยังตำแหน่งแทรกคือListIterator.add
เป็นที่คาดคะเนสำหรับO(1)
LinkedList
ใช่ฉันรู้ว่านี่เป็นคำถามโบราณ แต่ฉันจะโยนในสองเซ็นต์ของฉัน:
LinkedList นั้นเป็นตัวเลือกที่ผิดเสมอไป มีอัลกอริทึมเฉพาะเจาะจงบางอย่างที่มีการเรียกใช้ LinkedList แต่มีน้อยมากและอัลกอริทึมจะขึ้นอยู่กับความสามารถของ LinkedList ในการแทรกและลบองค์ประกอบในรายการกลางอย่างรวดเร็วเมื่อคุณสำรวจที่นั่น ด้วย ListIterator
มีหนึ่งกรณีการใช้งานทั่วไปที่ LinkedList มีประสิทธิภาพสูงกว่า ArrayList: ของคิว อย่างไรก็ตามหากเป้าหมายของคุณคือประสิทธิภาพแทนที่จะเป็น LinkedList คุณควรพิจารณาใช้ ArrayBlockingQueue (หากคุณสามารถกำหนดขอบเขตบนของขนาดคิวของคุณล่วงหน้าและสามารถจัดสรรหน่วยความจำล่วงหน้าทั้งหมดได้) หรือการใช้งาน CircularArrayListนี้. (ใช่มันมาจากปี 2001 ดังนั้นคุณจะต้องสร้างมันขึ้นมา แต่ฉันได้อัตราส่วนประสิทธิภาพเทียบเคียงกับสิ่งที่ยกมาในบทความตอนนี้ใน JVM ล่าสุด)
ArrayDeque
คุณสามารถใช้ docs.oracle.com/javase/6/docs/api/java/util/ArrayDeque.html
ArrayDeque
ช้ากว่าLinkedList
เว้นแต่การดำเนินการทั้งหมดจะสิ้นสุดที่เดียวกัน ไม่เป็นไรเมื่อใช้เป็นสแต็ก แต่ไม่ได้สร้างคิวที่ดี
ArrayDeque
มีแนวโน้มที่จะเร็วกว่าStack
เมื่อใช้เป็นสแต็กและเร็วกว่าLinkedList
เมื่อใช้เป็นคิว
ArrayDeque
เอกสาร
มันเป็นคำถามที่มีประสิทธิภาพ LinkedList
รวดเร็วสำหรับการเพิ่มและลบองค์ประกอบ แต่ช้าในการเข้าถึงองค์ประกอบเฉพาะ ArrayList
รวดเร็วสำหรับการเข้าถึงองค์ประกอบเฉพาะ แต่สามารถช้าที่จะเพิ่มที่ปลายทั้งสองและโดยเฉพาะอย่างยิ่งช้าที่จะลบตรงกลาง
อาร์เรย์ VS ArrayList VS LinkedList VS เวกเตอร์ไปเพิ่มเติมในเชิงลึกเช่นไม่ รายการที่เชื่อมโยง
ถูกต้องหรือไม่ถูกต้อง: กรุณาทำการทดสอบในพื้นที่และตัดสินใจด้วยตัวเอง
แก้ไข / ลบได้เร็วขึ้นในกว่าLinkedList
ArrayList
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);
}
}
LinkedList
มีโอเวอร์เฮดของหน่วยความจำมากกว่าเพราะสำหรับทุกองค์ประกอบจะมีโหนดวัตถุที่มีห้าฟิลด์ ในหลาย ๆ ระบบที่ใช้งานเกิน 20 ไบต์ ค่าใช้จ่ายหน่วยความจำเฉลี่ยต่อองค์ประกอบสำหรับArrayList
หนึ่งและครึ่งคำซึ่งทำให้ 6 ไบต์และ 8 ไบต์ในกรณีที่เลวร้ายที่สุด
removeIf(element -> condition)
ตำแหน่งที่เหมาะสมซึ่งเร็วกว่าอย่างมากArrayList
เมื่อเทียบกับการวนซ้ำและลบผ่านตัววนซ้ำเนื่องจากไม่จำเป็นต้องเลื่อนส่วนที่เหลือทั้งหมดสำหรับองค์ประกอบแต่ละส่วน ไม่ว่าจะเป็นการดำเนินการที่ดีกว่านี้หรือเลวร้ายยิ่งกว่าLinkedList
ขึ้นอยู่กับสถานการณ์โดยเฉพาะอย่างยิ่งในฐานะLinkedList
เป็น O (1) ในทฤษฎี แต่ลบเพียงโหนดเดียวต้องมีการเข้าถึงหน่วยความจำหลายอย่างซึ่งสามารถเกินจำนวนที่จำเป็นสำหรับArrayList
การถอดเป็นจำนวนมากขององค์ประกอบ .
ArrayList
เป็นอาร์เรย์ LinkedList
ดำเนินการเป็นรายการเชื่อมโยงสองครั้ง
ความget
สวยใส O (1) สำหรับArrayList
เนื่องจากArrayList
อนุญาตการเข้าถึงแบบสุ่มโดยใช้ดัชนี O (n) สำหรับLinkedList
เพราะจำเป็นต้องค้นหาดัชนีก่อน หมายเหตุ: มีรุ่นที่แตกต่างกันของและadd
remove
LinkedList
เพิ่มและลบได้เร็วขึ้น แต่รับช้ากว่า โดยสังเขปLinkedList
ควรเป็นที่ต้องการหาก:
=== ArrayList ===
=== LinkedList ===
เพิ่ม (E e)
เพิ่ม (ดัชนี int, องค์ประกอบ E)
นี่คือรูปจากprogramcreek.com ( add
และremove
เป็นประเภทแรกเช่นเพิ่มองค์ประกอบที่ส่วนท้ายของรายการและลบองค์ประกอบที่ตำแหน่งที่ระบุในรายการ):
Joshua Bloch ผู้แต่งของ LinkedList:
ใครบ้างที่ใช้ LinkedList ฉันเขียนมันและฉันไม่เคยใช้มัน
ลิงก์: https://twitter.com/joshbloch/status/583813919019573248
ฉันขอโทษสำหรับคำตอบที่ไม่ใช่ข้อมูลที่เป็นคำตอบอื่น ๆ แต่ฉันคิดว่ามันจะน่าสนใจที่สุดและอธิบายตนเอง
ArrayList
สามารถเข้าถึงได้แบบสุ่มในขณะที่LinkedList
ราคาถูกจริงๆในการขยายและลบองค์ประกอบออก สำหรับกรณีส่วนใหญ่ArrayList
เป็นเรื่องปกติ
หากคุณไม่ได้สร้างรายการขนาดใหญ่และวัดคอขวดคุณอาจไม่ต้องกังวลกับความแตกต่าง
TL; DRเนื่องจากสถาปัตยกรรมคอมพิวเตอร์ที่ทันสมัยArrayList
จะมีประสิทธิภาพมากขึ้นอย่างมากสำหรับเกือบทุกกรณีการใช้งานที่เป็นไปได้ - ดังนั้นจึงLinkedList
ควรหลีกเลี่ยงยกเว้นบางกรณีที่พิเศษและสุดขั้ว
ในทางทฤษฎี LinkedList มี O (1) สำหรับ add(E element)
การเพิ่มองค์ประกอบในช่วงกลางของรายการควรมีประสิทธิภาพมาก
การปฏิบัติแตกต่างกันมากเนื่องจาก LinkedList เป็นโครงสร้างข้อมูลแคชของศัตรู จากการปฏิบัติ POV - มีกรณีน้อยมากที่LinkedList
จะได้รับการปฏิบัติที่ดีขึ้นกว่าแคชง่าย ArrayList
นี่คือผลลัพธ์ของการทดสอบเกณฑ์มาตรฐานที่แทรกองค์ประกอบในสถานที่สุ่ม อย่างที่คุณเห็น - รายการอาร์เรย์ถ้ามีประสิทธิภาพมากขึ้นแม้ว่าในทางทฤษฎีแทรกแต่ละครั้งตรงกลางของรายการจะต้อง "ย้าย" องค์ประกอบnภายหลังของอาร์เรย์ (ค่าที่ต่ำกว่าดีกว่า):
ทำงานกับฮาร์ดแวร์รุ่นใหม่กว่า (แคชที่ใหญ่กว่าและมีประสิทธิภาพมากกว่า) - ผลลัพธ์มีข้อสรุปที่ชัดเจนยิ่งขึ้น:
LinkedList ใช้เวลามากขึ้นในการทำงานเดียวกันให้สำเร็จ แหล่งที่มา รหัสที่มา
มีสองเหตุผลหลักสำหรับสิ่งนี้:
ส่วนใหญ่ - ที่โหนดของLinkedList
จะกระจายไปทั่วหน่วยความจำแบบสุ่ม RAM ("หน่วยความจำเข้าถึงโดยสุ่ม") ไม่ใช่หน่วยสุ่มและบล็อกหน่วยความจำต้องถูกนำไปแคช การดำเนินการนี้ต้องใช้เวลาและเมื่อการดึงข้อมูลดังกล่าวเกิดขึ้นบ่อยครั้ง - หน้าหน่วยความจำในแคชต้องถูกแทนที่ตลอดเวลา -> แคชหายไป -> แคชไม่มีประสิทธิภาพ
ArrayList
องค์ประกอบจะถูกเก็บไว้ในหน่วยความจำอย่างต่อเนื่องซึ่งเป็นสิ่งที่สถาปัตยกรรม CPU สมัยใหม่ปรับให้เหมาะสม
รอง 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 ข้อมูลเพิ่มเติมสามารถพบได้ในบล็อกของเขา
หากรหัสของคุณมีadd(0)
และremove(0)
ให้ใช้LinkedList
และเป็นaddFirst()
และremoveFirst()
วิธีการที่ดีกว่า ArrayList
มิฉะนั้นการใช้งาน
และแน่นอนฝรั่ง 's ImmutableListเป็นเพื่อนที่ดีที่สุดของคุณ
ฉันรู้ว่านี้คือการโพสต์เก่า แต่ผมไม่สามารถเชื่อว่าไม่มีใครบอกว่าการดำเนินการLinkedList
Deque
เพียงแค่ดูวิธีการในDeque
(และQueue
); ถ้าคุณต้องการเปรียบเทียบยุติธรรมให้ลองทำงานLinkedList
กับArrayDeque
และทำการเปรียบเทียบคุณลักษณะสำหรับคุณลักษณะ
นี่คือสัญกรณ์ 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)
จากสิ่งเหล่านี้คุณต้องตัดสินใจว่าจะเลือกอะไร :)
LinkedList.add()
ถึงแม้ว่าส่วนใหญ่ของคำตอบที่นี่พูดอย่างนั้น
ลองเปรียบเทียบ LinkedList และ ArrayList wrt ด้านล่างพารามิเตอร์:
ArrayListคือการนำอาเรย์ไปใช้งานที่ปรับขนาดได้ของ list interface ในขณะที่
LinkedListเป็นการใช้งานลิสต์ของ Doubly-linked list ของ interface list
ArrayList get (int index) ทำงานในเวลาคงที่เช่น O (1) ในขณะที่
เวลาเรียกใช้การดำเนินการLinkedList get (ดัชนี int) คือ O (n)
เหตุผลที่อยู่เบื้องหลังArrayListนั้นเร็วกว่า LinkedList นั่นก็คือ ArrayList ใช้ระบบที่อิงกับดัชนีสำหรับองค์ประกอบเนื่องจากมันใช้โครงสร้างข้อมูลอาร์เรย์ภายในในทางกลับกัน
LinkedListไม่ได้จัดเตรียมการเข้าถึงแบบอิงดัชนีสำหรับองค์ประกอบของมันเนื่องจากจะวนซ้ำตั้งแต่เริ่มต้นหรือสิ้นสุด (แล้วแต่จำนวนใดจะใกล้เคียง) เพื่อดึงโหนดที่ดัชนีองค์ประกอบที่ระบุ
โดยทั่วไปแล้วการแทรกในLinkedListจะเร็วกว่าการเปรียบเทียบกับ ArrayList ใน LinkedList การเพิ่มหรือการแทรกเป็นการดำเนินการ O (1)
ในขณะที่อยู่ในArrayListถ้าอาเรย์นั้นเป็นกรณีที่เลวร้ายที่สุดอย่างเต็มรูปแบบมีค่าใช้จ่ายเพิ่มเติมในการปรับขนาดอาเรย์และการคัดลอกองค์ประกอบไปยังอาเรย์ใหม่ซึ่งทำให้การดำเนินการเพิ่มใน ArrayList O (n) .
การดำเนินการลบใน LinkedList โดยทั่วไปจะเหมือนกับ ArrayList เช่น O (n)
ในLinkedListมีวิธีการลบที่โอเวอร์โหลดสองวิธี หนึ่งคือ remove () โดยไม่มีพารามิเตอร์ใด ๆ ซึ่งจะลบส่วนหัวของรายการและทำงานในเวลาคงที่ O (1) เมธอดลบการโอเวอร์โหลดอื่น ๆ ใน LinkedList คือ remove (int) หรือ remove (Object) ซึ่งจะลบ Object หรือ int ที่ส่งผ่านเป็นพารามิเตอร์ เมธอดนี้สำรวจเส้นทาง LinkedList จนกว่าจะพบวัตถุและยกเลิกการเชื่อมโยงจากรายการเดิม ดังนั้นวิธีการนี้รันไทม์คือ O (n)
ในขณะที่วิธีArrayList remove (int) เกี่ยวข้องกับการคัดลอกองค์ประกอบจากอาร์เรย์เก่าไปยังอาร์เรย์ที่อัปเดตใหม่ดังนั้นรันไทม์ของมันคือ O (n)
LinkedListสามารถทำซ้ำในทิศทางย้อนกลับได้โดยใช้ descendingIterator () ในขณะที่
ไม่มี descendingIterator () ในArrayListดังนั้นเราต้องเขียนโค้ดของเราเองเพื่อวนซ้ำ ArrayList ในทิศทางตรงกันข้าม
หาก Constructor ไม่โหลดมากเกินไปArrayListจะสร้างรายการว่างของความจุเริ่มต้น 10 ในขณะที่
LinkedList จะสร้างรายการเปล่าโดยไม่ต้องมีความจุเริ่มต้น
ค่าใช้จ่ายในหน่วยความจำในLinkedListนั้นมากกว่า ArrayList ซึ่งเป็นโหนดใน LinkedList จำเป็นต้องรักษาที่อยู่ของโหนดถัดไปและโหนดก่อนหน้า ในขณะที่
ในArrayList แต่ละดัชนีเก็บเฉพาะวัตถุจริง (ข้อมูล)
ฉันมักจะใช้อย่างใดอย่างหนึ่งมากกว่าอื่นขึ้นอยู่กับความซับซ้อนของเวลาของการดำเนินงานที่ฉันจะแสดงในรายการนั้น
|---------------------|---------------------|--------------------|------------|
| 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 |
|--------------------------------------|
นอกจากนี้ยังมีข้อโต้แย้งที่ดีอื่น ๆ ข้างต้นคุณควรแจ้งให้ทราบArrayList
การดำเนินการRandomAccess
ติดต่อในขณะที่การดำเนินการLinkedList
Queue
ดังนั้นพวกเขาจึงจัดการกับปัญหาที่แตกต่างกันเล็กน้อยด้วยความแตกต่างของประสิทธิภาพและพฤติกรรม (ดูรายการวิธีการ)
ขึ้นอยู่กับว่าคุณจะทำอะไรมากขึ้นในรายการ
ArrayList
เร็วกว่าในการเข้าถึงค่าดัชนี มันจะแย่กว่ามากเมื่อใส่หรือลบวัตถุ
หากต้องการข้อมูลเพิ่มเติมอ่านบทความที่พูดถึงความแตกต่างระหว่างอาร์เรย์และรายการที่ลิงก์
รายการอาร์เรย์เป็นอาร์เรย์ที่มีวิธีการเพิ่มรายการเป็นต้น (และคุณควรใช้รายการทั่วไปแทน) มันคือชุดของรายการที่สามารถเข้าถึงได้ผ่านตัวสร้างดัชนี (เช่น [0]) มันหมายถึงความก้าวหน้าจากรายการหนึ่งไปยังอีก
รายการที่เชื่อมโยงระบุความก้าวหน้าจากรายการหนึ่งไปยังอีกรายการหนึ่ง (รายการก -> รายการข) คุณสามารถรับเอฟเฟกต์แบบเดียวกันกับรายการอาร์เรย์ได้ แต่รายการที่เชื่อมโยงจะบอกว่ารายการใดที่ควรจะติดตามก่อนหน้านี้
ดูชวาสอน - รายชื่อการใช้งาน
คุณสมบัติที่สำคัญของรายการที่เชื่อมโยง (ซึ่งฉันไม่ได้อ่านในคำตอบอื่น) คือการต่อข้อมูลสองรายการ ด้วยอาเรย์นี้คือ O (n) (+ ค่าใช้จ่ายของการปันส่วนบางส่วน) พร้อมรายการที่เชื่อมโยงนี่เป็นเพียง O (1) หรือ O (2) ;-)
สำคัญ : สำหรับ Java LinkedList
สิ่งนี้ไม่เป็นความจริง! ดูมีวิธีการเชื่อมโยงที่รวดเร็วสำหรับรายการที่ลิงก์ใน Java หรือไม่
next
จากรายการหนึ่งไปยังโหนดแรกในรายการที่สอง วิธีเดียวคือการใช้addAll()
ซึ่งเพิ่มองค์ประกอบตามลำดับแม้ว่าจะดีกว่าการวนซ้ำผ่านและเรียกadd()
แต่ละองค์ประกอบ ในการทำสิ่งนี้อย่างรวดเร็วใน O (1) คุณจะต้องมีคลาส compositing (เช่น org.apache.commons.collections.collection.CompositeCollection) แต่ก็ใช้ได้กับ List / Collection ทุกประเภท
ArrayList และ LinkedList มีข้อดีและข้อเสียของตัวเอง
ArrayList ใช้ที่อยู่หน่วยความจำต่อเนื่องเปรียบเทียบกับ LinkedList ซึ่งใช้ตัวชี้ไปยังโหนดถัดไป ดังนั้นเมื่อคุณต้องการค้นหาองค์ประกอบใน ArrayList จะเร็วกว่าการทำ n ซ้ำด้วย LinkedList
ในทางกลับกันการแทรกและการลบใน LinkedList นั้นง่ายกว่ามากเพราะคุณเพียงแค่เปลี่ยนพอยน์เตอร์ในขณะที่ ArrayList หมายถึงการใช้การดำเนินการกะสำหรับการแทรกหรือการลบใด ๆ
หากคุณมีการดำเนินการดึงข้อมูลบ่อยครั้งในแอปของคุณให้ใช้ ArrayList หากคุณมีการแทรกและลบบ่อยครั้งให้ใช้ LinkedList
ฉันได้อ่านคำตอบแล้ว แต่มีสถานการณ์หนึ่งที่ฉันจะใช้ LinkedList เหนือ ArrayList ที่ฉันต้องการแบ่งปันเพื่อรับฟังความคิดเห็นเสมอ:
ทุกครั้งที่ฉันมีวิธีที่ส่งคืนรายการข้อมูลที่ได้รับจากฐานข้อมูลฉันจะใช้ LinkedList ทุกครั้ง
เหตุผลของฉันคือเพราะเป็นไปไม่ได้ที่จะรู้ว่าฉันได้รับผลลัพธ์จำนวนเท่าไรจะไม่มีการสูญเสียความทรงจำ (เช่นเดียวกับใน ArrayList ที่มีความแตกต่างระหว่างความจุและจำนวนองค์ประกอบจริง) และไม่ต้องเสียเวลาพยายาม ทำซ้ำความจุ
สำหรับ ArrayList ฉันยอมรับว่าอย่างน้อยที่สุดคุณควรใช้ Constructor กับความสามารถเริ่มต้นเพื่อลดการทำซ้ำของ ArrayList ให้น้อยที่สุดเท่าที่จะทำได้
ArrayList
และLinkedList
การดำเนินการList interface
และวิธีการและผลลัพธ์ของพวกเขาเกือบจะเหมือนกัน อย่างไรก็ตามมีความแตกต่างเล็กน้อยระหว่างสิ่งเหล่านั้นซึ่งทำให้ดีขึ้นกว่ากันขึ้นอยู่กับข้อกำหนด
1) Search:
ArrayList
การดำเนินการค้นหาค่อนข้างเร็วเมื่อเทียบกับการLinkedList
ค้นหา get(int index)
ในการArrayList
ช่วยให้ประสิทธิภาพการทำงานของO(1)
ในขณะที่ประสิทธิภาพการทำงานLinkedList
O(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 เปรียบเทียบ
iterator
และlistIterator
ส่งกลับโดยคลาสเหล่านี้คือfail-fast
(ถ้ารายการถูกปรับเปลี่ยนโครงสร้างได้ตลอดเวลาหลังจากที่มีการสร้างตัววนซ้ำในลักษณะใด ๆ ยกเว้นผ่านวิธีการiterator’s
เอาออกหรือเพิ่มของตัวเอง iterator จะthrow
a ConcurrentModificationException
)(O(1))
ในเมื่อเทียบกับ
LinkedList
ArrayList(O(n))
ดังนั้นหากมีความต้องการของการเพิ่มและการลบบ่อยครั้งในแอปพลิเคชันแล้ว LinkedList เป็นตัวเลือกที่ดีที่สุด
get method
การดำเนินการSearch ( ) นั้นรวดเร็วArraylist (O(1))
แต่ไม่ได้อยู่ในLinkedList (O(n))
ดังนั้นหากมีการเพิ่มและลบการดำเนินงานน้อยลงและความต้องการการดำเนินการค้นหาเพิ่มเติม ArrayList จะเป็นทางออกที่ดีที่สุดของคุณ
การดำเนินงานได้รับ (i) ใน ArrayList จะเร็วกว่า LinkedList เพราะ:
ArrayList:การดำเนินงานที่ปรับขนาดได้อาร์เรย์ของอินเตอร์เฟซรายการ
LinkedList:ทวีคูณเชื่อมโยงการดำเนินงานรายชื่อของรายการและ Deque อินเตอร์เฟซ
การดำเนินการที่ดัชนีลงในรายการจะข้ามรายการจากจุดเริ่มต้นหรือจุดสิ้นสุดแล้วแต่จำนวนใดจะใกล้เคียงกับดัชนีที่ระบุมากขึ้น
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
หนึ่งในการทดสอบที่ฉันเห็นที่นี่ดำเนินการทดสอบเพียงครั้งเดียว แต่สิ่งที่ฉันสังเกตเห็นคือคุณต้องทำการทดสอบเหล่านี้หลายครั้งและในที่สุดเวลาของพวกเขาจะมาบรรจบกัน โดยทั่วไป 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;
}
ทั้ง 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 เสมอ
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)