Khi nào nên sử dụng LinkedList trên ArrayList trong Java?


3122

Tôi đã luôn luôn là một để sử dụng đơn giản:

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

Tôi sử dụng giao diện làm tên loại cho tính di động , để khi tôi đặt câu hỏi như vậy, tôi có thể làm lại mã của mình.

Khi nào nên LinkedListsử dụng hơn ArrayListvà ngược lại?



1
Chỉ cần xem trích dẫn từ tác giả của LinkedList stackoverflow.com/a/42529652/2032701 và bạn sẽ hiểu được thực tế của vấn đề.
Ruslan

Xem bài viết Java LinkedList vs ArrayList , mô tả sự khác biệt và bao gồm một số bài kiểm tra hiệu năng.
alonana

Câu trả lời:


3371

Tóm tắt ArrayList với ArrayDequeđược ưu tiên trong nhiều trường hợp sử dụng hơn LinkedList. Nếu bạn không chắc chắn - chỉ cần bắt đầu với ArrayList.


LinkedListArrayListlà hai triển khai khác nhau của giao diện Danh sách. LinkedListthực hiện nó với một danh sách liên kết đôi. ArrayListthực hiện nó với một mảng kích thước lại động.

Như với danh sách liên kết tiêu chuẩn và các hoạt động mảng, các phương thức khác nhau sẽ có thời gian chạy thuật toán khác nhau.

Dành cho LinkedList<E>

  • get(int index)O (n) (với n / 4 bước trung bình), nhưng O (1) khi index = 0hoặc index = list.size() - 1(trong trường hợp này, bạn cũng có thể sử dụng getFirst()getLast()). Một trong những lợi ích chính của LinkedList<E>
  • add(int index, E element)O (n) (với n / 4 bước trung bình), nhưng O (1) khi index = 0hoặc index = list.size() - 1(trong trường hợp này, bạn cũng có thể sử dụng addFirst()addLast()/ add()). Một trong những lợi ích chính của LinkedList<E>
  • remove(int index)O (n) (với n / 4 bước trung bình), nhưng O (1) khi index = 0hoặc index = list.size() - 1(trong trường hợp này, bạn cũng có thể sử dụng removeFirst()removeLast()). Một trong những lợi ích chính của LinkedList<E>
  • Iterator.remove()O (1) . Một trong những lợi ích chính của LinkedList<E>
  • ListIterator.add(E element)O (1) . Một trong những lợi ích chính của LinkedList<E>

Lưu ý: Nhiều thao tác cần trung bình n / 4 bước, số bước không đổi trong trường hợp tốt nhất (ví dụ: index = 0) và n / 2 bước trong trường hợp xấu nhất (giữa danh sách)

Dành cho ArrayList<E>

  • get(int index)O (1) . Lợi ích chính của ArrayList<E>
  • add(E element)O (1) được khấu hao, nhưng trường hợp xấu nhất của O (n) vì mảng phải được thay đổi kích thước và sao chép
  • add(int index, E element)O (n) (với n / 2 bước trung bình)
  • remove(int index)O (n) (với n / 2 bước trung bình)
  • Iterator.remove()O (n) (với n / 2 bước trung bình)
  • ListIterator.add(E element)O (n) (với n / 2 bước trung bình)

Lưu ý: Nhiều thao tác cần trung bình n / 2 bước, số bước không đổi trong trường hợp tốt nhất (cuối danh sách), n bước trong trường hợp xấu nhất (bắt đầu danh sách)

LinkedList<E>cho phép chèn hoặc xóa thời gian liên tục bằng cách sử dụng các trình vòng lặp , nhưng chỉ truy cập tuần tự các phần tử. Nói cách khác, bạn có thể đi bộ danh sách tiến hoặc lùi, nhưng việc tìm một vị trí trong danh sách cần thời gian tỷ lệ thuận với kích thước của danh sách. Javadoc nói rằng "các hoạt động lập chỉ mục vào danh sách sẽ đi qua danh sách từ đầu hoặc cuối, bất kỳ gần hơn" , vì vậy các phương thức đó trung bình là O (n) ( n / 4 bước), mặc dù O (1) cho index = 0.

ArrayList<E>mặt khác, cho phép truy cập đọc ngẫu nhiên nhanh, do đó bạn có thể lấy bất kỳ yếu tố nào trong thời gian không đổi. Nhưng việc thêm hoặc xóa từ bất cứ đâu nhưng kết thúc đòi hỏi phải dịch chuyển tất cả các yếu tố sau, để tạo ra một khoảng trống hoặc lấp đầy khoảng trống. Ngoài ra, nếu bạn thêm nhiều phần tử hơn dung lượng của mảng bên dưới, một mảng mới (gấp 1,5 lần kích thước) sẽ được phân bổ và mảng cũ được sao chép sang mảng mới, vì vậy, thêm vào một ArrayListO (n) trong điều tồi tệ nhất trường hợp nhưng không đổi trung bình.

Vì vậy, tùy thuộc vào các hoạt động bạn định làm, bạn nên chọn việc thực hiện cho phù hợp. Lặp đi lặp lại trên một trong hai loại Danh sách thực tế là rẻ như nhau. (Lặp lại mộtArrayList kỹ thuật nhanh hơn, nhưng trừ khi bạn đang làm một cái gì đó thực sự nhạy cảm với hiệu suất, bạn không nên lo lắng về điều này - cả hai đều là hằng số.)

Những lợi ích chính của việc sử dụng LinkedListphát sinh khi bạn sử dụng lại các trình vòng lặp hiện có để chèn và loại bỏ các phần tử. Các thao tác này sau đó có thể được thực hiện trong O (1) bằng cách chỉ thay đổi danh sách cục bộ. Trong một danh sách mảng, phần còn lại của mảng cần phải được di chuyển (tức là sao chép). Mặt khác, tìm kiếm một LinkedListphương tiện theo các liên kết trong O (n) ( n / 2 bước) trong trường hợp xấu nhất, trong khi ở ArrayListvị trí mong muốn có thể được tính toán và truy cập trong O (1) .

Một lợi ích khác của việc sử dụng LinkedListphát sinh khi bạn thêm hoặc xóa khỏi phần đầu của danh sách, vì các thao tác đó là O (1) , trong khi chúng là O (n) cho ArrayList. Lưu ý rằng ArrayDequecó thể là một thay thế tốt để LinkedListthêm và xóa khỏi đầu, nhưng nó không phải là một List.

Ngoài ra, nếu bạn có danh sách lớn, hãy nhớ rằng việc sử dụng bộ nhớ cũng khác nhau. Mỗi phần tử của a LinkedListcó nhiều chi phí hơn do các con trỏ tới các phần tử tiếp theo và trước đó cũng được lưu trữ. ArrayListskhông có chi phí này Tuy nhiên, ArrayListschiếm nhiều bộ nhớ như được phân bổ cho dung lượng, bất kể các yếu tố có thực sự được thêm vào hay không.

Dung lượng ban đầu mặc định của một ArrayListkhá nhỏ (10 từ Java 1.4 - 1.8). Nhưng vì triển khai cơ bản là một mảng, mảng phải được thay đổi kích thước nếu bạn thêm nhiều phần tử. Để tránh chi phí thay đổi kích thước cao khi bạn biết bạn sẽ thêm nhiều yếu tố, hãy xây dựng ArrayListvới công suất ban đầu cao hơn.


182
Quên đề cập đến chi phí chèn. Trong LinkedList, một khi bạn có vị trí chính xác, chi phí chèn O (1), trong khi trong ArrayList, nó sẽ tăng lên O (n) - tất cả các yếu tố qua điểm chèn phải được di chuyển.
David Rodríguez - dribeas

26
Về việc sử dụng Vector: Thực sự không cần phải quay lại Vector. Cách để thực hiện việc này là với việc triển khai Danh sách ưa thích của bạn và gọi đến đồng bộ hóa ListList để cung cấp cho nó một trình bao bọc được đồng bộ hóa. Xem: java.sun.com/docs/books/tutorial/collections/imâyations / trên
Ryan Cox

69
Không, đối với LinkedList, get vẫn là O (n) ngay cả khi bạn biết vị trí, bởi vì để đến vị trí đó, việc triển khai cơ bản phải đi theo con trỏ "tiếp theo" của danh sách được liên kết để đến giá trị của vị trí đó. Không có những thứ như truy cập ngẫu nhiên. Đối với vị trí 2, đi bộ con trỏ có thể rẻ, nhưng đối với vị trí 1 triệu, không quá rẻ. Vấn đề là, nó tỷ lệ thuận với vị trí, có nghĩa là nó O (n).
Jonathan Trần

53
@Kevin Có thể vấn đề là bộ nhớ "gần nhau". Phần cứng sẽ lưu trữ các khối bộ nhớ liền kề (RAM động) vào RAM tĩnh nhanh hơn trong bộ đệm L1 hoặc L2. Về lý thuyết và hầu hết thời gian thực tế, bộ nhớ có thể được coi là truy cập ngẫu nhiên. Nhưng trong thực tế, đọc qua bộ nhớ tuần tự sẽ nhanh hơn một chút so với thứ tự ngẫu nhiên. Đối với một vòng lặp hiệu suất quan trọng, điều này có thể quan trọng. Họ gọi nó là "địa phương không gian" hoặc địa phương tham chiếu .
Jonathan Trần

92
Không có những thứ như O(n/2)hoặc O(n/4). Ký hiệu O lớn cho bạn biết làm thế nào một quy mô hoạt động với n lớn hơn . và một hoạt động cần n/2các bước quy mô chính xác như một hoạt động cần ncác bước, đó là lý do tại sao các triệu tập hoặc các yếu tố liên tục bị loại bỏ. O(n/2)O(n/4)cả hai chỉ O(n). LinkedListArrayListdù sao cũng có các yếu tố không đổi khác nhau, do đó, sẽ không có ý định so sánh O(n/2)cái này với cái O(n/4)kia, cả hai chỉ biểu thị các hoạt động chia tỷ lệ tuyến tính.
Holger

630

Cho đến nay, dường như không ai giải quyết được dấu chân bộ nhớ của mỗi danh sách này ngoài sự đồng thuận chung rằng LinkedList"nhiều hơn" so với một số ArrayListnên tôi đã thực hiện một số con số để chứng minh chính xác cả hai danh sách chiếm bao nhiêu cho các tham chiếu N null.

Vì các tham chiếu là 32 hoặc 64 bit (ngay cả khi null) trên các hệ thống tương đối của chúng, tôi đã bao gồm 4 bộ dữ liệu cho 32 và 64 bit LinkedListsArrayLists.

Ghi chú: Kích thước hiển thị cho các ArrayListdòng dành cho danh sách được cắt xén - Trong thực tế, dung lượng của mảng sao lưu trong một ArrayListthường lớn hơn số phần tử hiện tại của nó.

Lưu ý 2: (cảm ơn BeeOnRope) Vì hiện được mặc định từ giữa JDK6 trở lên, các giá trị bên dưới cho các máy 64 bit về cơ bản sẽ khớp với các đối tác 32 bit của chúng, trừ khi bạn đặc biệt tắt nó đi.


Biểu đồ của LinkedList và ArrayList Số phần tử x Byte


Kết quả cho thấy rõ rằng LinkedList là nhiều hơn rất nhiều ArrayList, đặc biệt là với số lượng phần tử rất cao. Nếu bộ nhớ là một yếu tố, hãy tránh xa LinkedLists.

Các công thức tôi đã sử dụng làm theo, cho tôi biết nếu tôi đã làm gì sai và tôi sẽ sửa nó. 'b' là 4 hoặc 8 cho các hệ thống 32 hoặc 64 bit và 'n' là số phần tử. Lưu ý lý do cho các mod là bởi vì tất cả các đối tượng trong java sẽ chiếm nhiều không gian 8 byte bất kể nó có được sử dụng hay không.

Lập danh sách:

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)

Danh sách liên kết:

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
Khá thú vị khi thấy LinkedList yêu cầu nhiều bộ nhớ như ArrayList để lưu trữ một phần tử. Thật không trực quan! Điều gì xảy ra nếu bạn chạy ví dụ của mình với -XX: + UseCompressionOops?
jontejj

215
Vấn đề với toán học của bạn là đồ thị của bạn phóng đại rất nhiều tác động. Bạn đang mô hình hóa các đối tượng mà mỗi đối tượng chỉ chứa một int, vì vậy 4 hoặc 8 byte dữ liệu. Trong danh sách được liên kết, về cơ bản có 4 "từ" trên không. Do đó, biểu đồ của bạn mang lại ấn tượng rằng các danh sách được liên kết sử dụng "năm lần" việc lưu trữ danh sách mảng. Cái này sai. Chi phí hoạt động là 16 hoặc 32 byte cho mỗi đối tượng, dưới dạng điều chỉnh phụ gia, không phải là hệ số tỷ lệ.
Heath Hunnicutt

6
Không có đối tượng ArrayList / LinkedList / Node nào chỉ chứa một int, vì vậy tôi không hiểu những gì bạn đang nói ở đó. Tôi đã sắp xếp lại 'chi phí đối tượng' thành 'tiêu đề đối tượng' thành clarfy - có một tiêu đề 8 byte cho mọi đối tượng bất kể hệ thống, và vâng, bao gồm tất cả các đối tượng Node trong LinkedList, tất cả đều được tính chính xác theo khả năng của tôi nói. Tình cờ, khi nhìn lại nó, tôi đã tìm thấy một vài vấn đề khác với toán học của tôi trong LinkedList, điều này thực sự làm cho sự phân chia nó và ArrayList trở nên tồi tệ hơn . Tôi rất vui khi tiếp tục cập nhật nó vì vậy xin đừng ngần ngại làm rõ và trau chuốt hơn.
Numeron

6
Cần lưu ý rằng CompressedOopshiện tại là mặc định trong tất cả các JDK gần đây (7, 8 và cập nhật 6 trong một vài năm), vì vậy 64 bit sẽ không tạo ra sự khác biệt về kích thước ArrayListhoặc LinkedListkích thước, trừ khi bạn đã tắt oops nén một cách rõ ràng một số lý do.
BeeOnRope

1
@jontejj mức tăng dung lượng mặc định là 50%, vì vậy khi bạn điền vào ArrayListmà không chỉ định dung lượng ban đầu, nó vẫn sẽ sử dụng ít bộ nhớ hơn đáng kể so với a LinkedList.
Holger

244

ArrayListlà những gì bạn muốn. LinkedListhầu như luôn luôn là một lỗi (hiệu suất).

Tại sao LinkedListhút:

  • Nó sử dụng nhiều đối tượng bộ nhớ nhỏ và do đó ảnh hưởng đến hiệu suất trong suốt quá trình.
  • Rất nhiều đối tượng nhỏ có hại cho bộ nhớ cache cục bộ.
  • Bất kỳ hoạt động được lập chỉ mục nào cũng yêu cầu truyền tải, tức là có hiệu suất O (n). Điều này không rõ ràng trong mã nguồn, dẫn đến thuật toán O (n) chậm hơn so với khi ArrayListđược sử dụng.
  • Có được hiệu suất tốt là khó khăn.
  • Ngay cả khi hiệu suất big-O giống như ArrayList, dù sao thì nó cũng sẽ chậm hơn đáng kể.
  • Thật xúc động khi thấy LinkedListtrong nguồn bởi vì nó có thể là lựa chọn sai.

237
Lấy làm tiếc. đánh dấu bạn xuống LinkedList không hút. Có những tình huống mà LinkedList là lớp chính xác để sử dụng. Tôi đồng ý rằng không có nhiều tình huống tốt hơn danh sách mảng, nhưng chúng tồn tại. Giáo dục những người làm những điều ngớ ngẩn!
David Turner

40
Rất tiếc khi thấy bạn nhận được nhiều phiếu bầu cho việc này. Thực sự có rất ít lý do để sử dụng LinkedList của Java. Ngoài hiệu năng kém, nó còn sử dụng nhiều bộ nhớ hơn các lớp List cụ thể khác (mỗi nút có hai con trỏ bổ sung và mỗi nút là một đối tượng trình bao bọc riêng biệt với các byte trên cao đi kèm với chúng).
Kevin Brock

42
Đây là một trong những câu trả lời hữu ích nhất ở đây. Thật xấu hổ khi nhiều lập trình viên không hiểu (a) sự khác biệt giữa các kiểu dữ liệu trừu tượng và việc triển khai cụ thể và (b) tầm quan trọng trong thế giới thực của các yếu tố không đổi và chi phí bộ nhớ trong việc xác định hiệu suất.
porculus

50
-1: Đây là một cái nhìn khá chớp mắt. Vâng, đúng là ArrayList là một công cụ rất linh hoạt. Tuy nhiên, nó có những hạn chế. Có những trường hợp điều này sẽ gây rắc rối cho bạn và bạn sẽ phải sử dụng LinkedList. Tất nhiên, nó là một giải pháp rất chuyên dụng, và, như bất kỳ công cụ chuyên dụng nào, trong hầu hết các trường hợp, nó vượt trội hơn so với một công cụ đa năng. Nhưng điều đó không có nghĩa là nó "tệ" hay đại loại như thế, bạn chỉ cần biết khi nào nên sử dụng nó.
Malcolm

27
@DavidTurner: Chúng tồn tại, nhưng tôi nghĩ quan điểm của Tom là nếu bạn phải hỏi, có lẽ bạn muốn ArrayList.
dùng541686

139

Là một người đã thực hiện kỹ thuật hiệu năng hoạt động trên các dịch vụ web quy mô rất lớn trong khoảng một thập kỷ, tôi thích hành vi của LinkedList hơn ArrayList. Mặc dù thông lượng trạng thái ổn định của LinkedList kém hơn và do đó có thể dẫn đến việc mua nhiều phần cứng hơn - hành vi của ArrayList dưới áp lực có thể dẫn đến các ứng dụng trong cụm mở rộng mảng của chúng ở mức gần đồng bộ và đối với kích thước mảng lớn có thể dẫn đến thiếu phản ứng trong ứng dụng và mất điện, trong khi chịu áp lực, đó là hành vi thảm khốc.

Tương tự, bạn có thể có được thông lượng tốt hơn trong một ứng dụng từ trình thu gom rác được thuê thông lượng mặc định, nhưng khi bạn nhận được các ứng dụng java với 10 GB, bạn có thể khóa ứng dụng trong 25 giây trong một GC đầy đủ gây ra thời gian chờ và lỗi trong các ứng dụng SOA và thổi SLAs của bạn nếu nó xảy ra quá thường xuyên. Mặc dù trình thu thập CMS mất nhiều tài nguyên hơn và không đạt được cùng một thông lượng thô, nhưng đó là một lựa chọn tốt hơn nhiều vì nó có độ trễ dự đoán nhiều hơn và nhỏ hơn.

ArrayList chỉ là một lựa chọn tốt hơn cho hiệu suất nếu tất cả những gì bạn muốn nói về hiệu suất là thông lượng và bạn có thể bỏ qua độ trễ. Theo kinh nghiệm trong công việc của tôi, tôi không thể bỏ qua độ trễ trong trường hợp xấu nhất.


8
Sẽ không có giải pháp nào khác quản lý kích thước của danh sách theo chương trình bằng cách sử dụng phương thức notifyCapacity () của ArrayList? Câu hỏi của tôi là tại sao có quá nhiều thứ được lưu trữ trong một loạt các cấu trúc dữ liệu dễ vỡ khi chúng có thể được lưu trữ tốt hơn trong một cơ chế bộ đệm hoặc db? Tôi đã có một cuộc phỏng vấn vào một ngày khác, nơi họ thề lên xuống về những tệ nạn của ArrayList, nhưng tôi đến đây và tôi thấy rằng phân tích phức tạp là tốt hơn tất cả! ĐIỂM TUYỆT VỜI ĐỂ THẢO LUẬN, NGHE. CẢM ƠN!
ingyhere

22
một khi bạn nhận được các ứng dụng java với đống 10GB, bạn có thể kết thúc việc khóa ứng dụng trong 25 giây trong một GC đầy đủ gây ra thời gian chờ Trên thực tế với LinkedList bạn giết người thu gom rác trong các GC đầy đủ, nó phải lặp đi lặp lại với ListList quá lớn với lỗi bộ nhớ cache từng nút.
bestsss

5
Đó là ... một giải pháp khủng khiếp. Về cơ bản, bạn phụ thuộc vào việc dọn dẹp GC cho bạn, điều này cực kỳ tốn kém, khi bạn chỉ có thể gọi notifyCapacity () trên một danh
Philip Devine

5
@Andreas: A LinkedList luôn phân bổ bộ nhớ năm lần so với một mảng tham chiếu đơn giản, do đó, ArrayListyêu cầu tạm thời 2,5 lần vẫn tiêu tốn ít bộ nhớ hơn, ngay cả khi bộ nhớ không được thu hồi. Vì phân bổ mảng lớn bỏ qua không gian Eden, nên chúng không có bất kỳ tác động nào đến hành vi của GC, trừ khi thực sự không có đủ bộ nhớ, trong trường hợp đó, nó LinkedListđã xuất hiện sớm hơn nhiều so với trước
Holger

5
@Andreas Vấn đề khác là cách phân bổ bộ nhớ. LinkedListchỉ cần một phần nhỏ của bộ nhớ trống để phân bổ cho phần tử tiếp theo. ArrayListsẽ cần một khối không gian trống lớn và liên tục để phân bổ mảng đã thay đổi kích thước. Nếu heap bị phân mảnh, thì cuối cùng, GC có thể sắp xếp lại toàn bộ heap chỉ để giải phóng một khối bộ nhớ duy nhất phù hợp.
Piotr Kusmierchot

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)

Thuật toán: Ký hiệu Big-Oh

ArrayLists tốt cho ghi-đọc-đọc-nhiều hoặc nối thêm, nhưng xấu khi thêm / xóa từ trước hoặc giữa.


42
Bạn không thể so sánh trực tiếp các giá trị O lớn mà không nghĩ về các yếu tố không đổi. Đối với các danh sách nhỏ (và hầu hết các danh sách đều nhỏ), O (N) của ArrayList nhanh hơn O (1) của LinkedList.
porculus

4
Tôi không quan tâm đến hiệu suất danh sách nhỏ và máy tính của tôi cũng vậy, trừ khi nó được sử dụng trong một vòng lặp bằng cách nào đó.
Maarten Bodewes

45
LinkedList không thể thực sự chèn vào giữa O(1). Nó phải chạy qua một nửa danh sách để tìm điểm chèn.
Thomas Ahle

8
LinkedList: chèn vào giữa O (1) - là SAI! Tôi phát hiện ra rằng thậm chí việc chèn vào vị trí 1/10 của kích thước LinkedList còn chậm hơn so với việc chèn một phần tử vào vị trí 1/10 của ArrayList. Và thậm chí tệ hơn: sự kết thúc của bộ sưu tập. chèn vào các vị trí cuối cùng (không phải cuối cùng) của ArrayList sẽ nhanh hơn vào các vị trí cuối cùng (không phải cuối cùng) của LinkedList
kachanov

14
@kachanov Chèn vào LinkedList O(1) nếu bạn có một trình vòng lặp đến vị trí chèn , nghĩa ListIterator.addlà được O(1)cho là cho a LinkedList.
Có QUIT - Anony-Mousse

107

Vâng, tôi biết, đây là một câu hỏi cổ xưa, nhưng tôi sẽ ném vào hai xu của mình:

LinkedList gần như luôn luôn là sự lựa chọn sai lầm, hiệu năng-khôn ngoan. Có một số thuật toán rất cụ thể trong đó LinkedList được yêu cầu, nhưng những thuật toán này rất, rất hiếm và thuật toán thường sẽ đặc biệt phụ thuộc vào khả năng chèn và xóa các phần tử ở giữa danh sách một cách nhanh chóng, khi bạn đã điều hướng ở đó với một ListIterator.

Có một trường hợp sử dụng phổ biến trong đó LinkedList vượt trội hơn ArrayList: đó là hàng đợi. Tuy nhiên, nếu mục tiêu của bạn là hiệu suất, thay vì LinkedList, bạn cũng nên xem xét sử dụng ArrayBlockingQueue (nếu bạn có thể xác định giới hạn trên về kích thước hàng đợi của mình trước thời hạn và có thể đủ khả năng phân bổ tất cả bộ nhớ lên phía trước), hoặc việc triển khai Thông tư này . (Vâng, đó là từ năm 2001, vì vậy bạn sẽ cần phải khái quát nó, nhưng tôi đã có tỷ lệ hiệu suất tương đương với những gì được trích dẫn trong bài viết vừa rồi trong một JVM gần đây)


39
Từ Java 6 bạn có thể sử dụng ArrayDeque. docs.oracle.com/javase/6/docs/api/java/util/ArrayDeque.html
Thomas Ahle

1
ArrayDequechậm hơn LinkedListtrừ khi tất cả các hoạt động ở cùng một kết thúc. Nó ổn khi được sử dụng như một ngăn xếp nhưng nó không tạo ra một hàng đợi tốt.
Danh sách Jeremy

2
Không đúng - ít nhất là cho việc triển khai của Oracle trong jdk1.7.0_60 và trong thử nghiệm sau. Tôi đã tạo một bài kiểm tra trong đó tôi lặp 10 triệu lần và tôi có Deque là 10 triệu số nguyên ngẫu nhiên. Trong vòng lặp, tôi thăm dò một yếu tố từ và đưa ra một yếu tố không đổi. Trên máy tính của tôi, LinkedList chậm hơn 10 lần so với ArrayDeque và sử dụng ít bộ nhớ hơn). Lý do là không giống như ArrayList, ArrayDeque giữ một con trỏ tới phần đầu của mảng để nó không phải di chuyển tất cả các phần tử khi phần đầu bị loại bỏ.
Henno Vermeulen

6
ArrayDequecó khả năng nhanh hơn Stackkhi được sử dụng như một ngăn xếp và nhanh hơn so với LinkedListkhi được sử dụng như một hàng đợi.
akhil_mittal

3
Lưu ý rằng nhận xét của akhil_mittal là một trích dẫn từ ArrayDequetài liệu.
Stuart Marks

65

Đó là một câu hỏi hiệu quả. LinkedListlà nhanh để thêm và xóa các phần tử, nhưng chậm để truy cập một phần tử cụ thể. ArrayListlà nhanh để truy cập một yếu tố cụ thể nhưng có thể chậm để thêm vào một trong hai và đặc biệt là chậm để xóa ở giữa.

Array vs ArrayList vs LinkedList vs Vector đi sâu hơn, cũng như Danh sách liên kết .


54

Đúng hoặc Không chính xác: Vui lòng thực hiện kiểm tra cục bộ và tự quyết định!

Chỉnh sửa / Xóa nhanh LinkedListhơn ArrayList.

ArrayList, được hỗ trợ bởi Array, cần phải tăng gấp đôi kích thước, là tồi tệ hơn trong ứng dụng khối lượng lớn.

Dưới đây là kết quả kiểm tra đơn vị cho từng thao tác. Việc này được đưa ra trong Nanoseconds.


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

Đây là mã:

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
Chính xác thì ArrayList không cần phải nhân đôi. Vui lòng kiểm tra các nguồn đầu tiên.
Thủy thủ Danubian

Cần lưu ý rằng ví dụ của bạn không hoàn hảo ... Bạn đang xóa khỏi chuỗi giữa: 18 + [2, 12] byte ("true0false", "true500000false"), trung bình 25 byte, là kích thước của các phần tử ở giữa. Người ta biết rằng khi kích thước byte phần tử tăng danh sách liên kết hoạt động tốt hơn, khi kích thước danh sách tăng, một mảng (danh sách) liền kề sẽ làm tốt hơn. Quan trọng nhất, bạn đang thực hiện .equals () trên chuỗi - đây không phải là một hoạt động rẻ tiền. Nếu bạn thay vì sử dụng số nguyên, tôi nghĩ sẽ có một sự khác biệt.
Centril

2
"... Tệ hơn trong ứng dụng khối lượng lớn ": Đây là một sự hiểu lầm. LinkedListcó nhiều chi phí bộ nhớ hơn vì với mỗi phần tử có một đối tượng nút có năm trường. Trên nhiều hệ thống tạo ra 20 byte trên đầu. Chi phí bộ nhớ trung bình cho mỗi phần tử ArrayListlà một từ rưỡi, tạo ra 6 byte và 8 byte trong trường hợp xấu nhất.
Lii

1
Tôi đã thực hiện một phiên bản tốt hơn của điểm chuẩn của bạn ở đây, với kết quả - hiệu suất bổ sung cho danh sách mảng thấp một cách giả tạo, bởi vì addAll đang cung cấp một mảng lưu trữ có kích thước ban đầu CHÍNH XÁC, do đó, lần chèn đầu tiên luôn kích hoạt nội soi. Ngoài ra, điều này bao gồm các chu kỳ khởi động để cho phép biên dịch JIT trước khi dữ liệu được thu thập.
BobMcGee

4
@BillK kể từ Java 8, bạn có thể sử dụng removeIf(element -> condition)nơi phù hợp, có thể nhanh hơn đáng kể cho một ArrayList, so với việc lặp và xóa qua iterator, vì không bắt buộc phải thay đổi toàn bộ phần còn lại cho mỗi phần tử riêng lẻ. Cho dù điều này hoạt động tốt hơn hay tồi tệ hơn LinkedListphụ thuộc vào kịch bản cụ thể, vì LinkedListtheo lý thuyết là O (1), nhưng việc loại bỏ chỉ một nút yêu cầu một số truy cập bộ nhớ, có thể dễ dàng vượt quá số lượng cần thiết ArrayListkhi loại bỏ một số lượng phần tử đáng kể .
Holger

50

ArrayListthực chất là một mảng. LinkedListđược thực hiện như một danh sách liên kết kép.

Điều getnày khá rõ ràng. O (1) cho ArrayList, vì ArrayListcho phép truy cập ngẫu nhiên bằng cách sử dụng chỉ mục. O (n) cho LinkedList, bởi vì nó cần tìm chỉ mục đầu tiên. Lưu ý: có các phiên bản khác nhau của addremove.

LinkedListlà nhanh hơn trong thêm và loại bỏ, nhưng chậm hơn trong get. Tóm lại, LinkedListnên được ưu tiên nếu:

  1. không có số lượng lớn truy cập ngẫu nhiên của phần tử
  2. có một số lượng lớn các hoạt động thêm / xóa

=== ArrayList ===

  • thêm (E e)
    • thêm vào cuối ArrayList
    • yêu cầu thay đổi kích thước bộ nhớ.
    • O (n) tệ nhất, O (1) được khấu hao
  • thêm (chỉ số int, phần tử E)
    • thêm vào một vị trí chỉ số cụ thể
    • yêu cầu thay đổi & chi phí thay đổi bộ nhớ có thể
    • Trên)
  • xóa (chỉ mục int)
    • loại bỏ một yếu tố được chỉ định
    • yêu cầu thay đổi & chi phí thay đổi bộ nhớ có thể
    • Trên)
  • xóa (đối tượng o)
    • xóa lần xuất hiện đầu tiên của phần tử đã chỉ định khỏi danh sách này
    • cần tìm kiếm phần tử trước, sau đó thay đổi & thay đổi chi phí bộ nhớ có thể
    • Trên)

=== LinkedList ===

  • thêm (E e)

    • thêm vào cuối danh sách
    • Ô (1)
  • thêm (chỉ số int, phần tử E)

    • chèn vào vị trí quy định
    • cần tìm vị trí đầu tiên
    • Trên)
  • tẩy()
    • xóa phần tử đầu tiên của danh sách
    • Ô (1)
  • xóa (chỉ mục int)
    • xóa phần tử với chỉ mục đã chỉ định
    • cần tìm phần tử trước
    • Trên)
  • xóa (đối tượng o)
    • loại bỏ sự xuất hiện đầu tiên của phần tử được chỉ định
    • cần tìm phần tử trước
    • Trên)

Dưới đây là một con số từ chương trình.com ( addremovelà loại đầu tiên, nghĩa là thêm một phần tử ở cuối danh sách và xóa phần tử ở vị trí đã chỉ định trong danh sách.):

nhập mô tả hình ảnh ở đây


3
"LinkedList nhanh hơn add / remove". Sai, kiểm tra câu trả lời ở trên stackoverflow.com/a/7507740/638670
Nerrve

49

Joshua Bloch, tác giả của LinkedList:

Có ai thực sự sử dụng LinkedList không? Tôi đã viết nó, và tôi không bao giờ sử dụng nó.

Liên kết: https://twitter.com/joshbloch/status/583813919019573248

Tôi xin lỗi vì câu trả lời không có nhiều thông tin như các câu trả lời khác, nhưng tôi nghĩ nó sẽ thú vị và tự giải thích nhất.


34

ArrayListcó thể truy cập ngẫu nhiên, trong khi LinkedListthực sự rẻ để mở rộng và loại bỏ các yếu tố khỏi. Đối với hầu hết các trường hợp, ArrayListlà tốt.

Trừ khi bạn đã tạo danh sách lớn và đo lường nút cổ chai, có lẽ bạn sẽ không bao giờ phải lo lắng về sự khác biệt.


15
LinkedList không rẻ để thêm các yếu tố. Hầu như luôn luôn nhanh hơn để thêm một triệu phần tử vào một ArrayList so với việc thêm chúng vào LinkedList. Và hầu hết các danh sách trong mã thế giới thực thậm chí không dài một triệu phần tử.
porculus

10
Tại bất kỳ thời điểm nào, bạn đều biết chi phí thêm một mục vào LinkedList của mình. ArrayList bạn không (nói chung). Việc thêm một mục duy nhất vào một ArrayList chứa một triệu mục có thể mất một thời gian rất dài - đó là thao tác O (n) cộng với gấp đôi dung lượng lưu trữ trừ khi bạn sắp xếp không gian. Thêm một mục vào LinkedList là O (1). Tuyên bố cuối cùng của tôi đứng.
Dustin

4
Thêm một mục duy nhất vào ArrayList là O (1) bất kể đó là 1 triệu hay 1 tỷ. Thêm một mục vào LinkedList cũng là O (1). "Thêm" có nghĩa là THÊM VÀO KẾT THÚC.
kachanov

Bạn phải đọc cách thực hiện khác với tôi. Theo kinh nghiệm của tôi, sao chép mảng 1 tỷ phần tử mất nhiều thời gian hơn so với sao chép mảng phần tử 1 triệu.
Dustin

6
@kachanov bạn phải hiểu nhầm Dustin. Trừ khi bạn đã khai báo một mảng gồm 1 tỷ mục, cuối cùng bạn sẽ cần thay đổi kích thước mảng của mình, trong trường hợp đó bạn sẽ cần sao chép tất cả các phần tử vào một mảng mới lớn hơn do đó đôi khi bạn sẽ nhận được O (N) tuy nhiên với danh sách được liên kết, bạn sẽ luôn luôn nhận O (1)
Stan R.

29

TL; DR do kiến ​​trúc máy tính hiện đại, ArrayListsẽ hiệu quả hơn đáng kể đối với gần như mọi trường hợp sử dụng có thể - và do đó LinkedListnên tránh ngoại trừ một số trường hợp rất độc đáo và cực đoan.


Về lý thuyết, LinkedList có O (1) cho add(E element)

Ngoài ra, việc thêm một yếu tố vào giữa danh sách sẽ rất hiệu quả.

Thực tiễn là rất khác nhau, vì LinkedList là một cấu trúc dữ liệu lưu trữ bộ nhớ cache . Từ hiệu suất POV - có rất ít trường hợp LinkedListcó thể hoạt động tốt hơn so với thân thiện với Cache ArrayList .

Dưới đây là kết quả của một yếu tố chèn kiểm tra điểm chuẩn ở các vị trí ngẫu nhiên. Như bạn có thể thấy - danh sách mảng nếu hiệu quả hơn nhiều, mặc dù về lý thuyết, mỗi lần chèn ở giữa danh sách sẽ yêu cầu "di chuyển" n phần tử sau của mảng (giá trị thấp hơn sẽ tốt hơn):

nhập mô tả hình ảnh ở đây

Làm việc trên một phần cứng thế hệ sau (bộ nhớ cache lớn hơn, hiệu quả hơn) - kết quả thậm chí còn kết luận hơn:

nhập mô tả hình ảnh ở đây

LinkedList mất nhiều thời gian hơn để hoàn thành công việc tương tự. nguồn

Có hai lý do chính cho việc này:

  1. Chủ yếu - rằng các nút của LinkedListđược phân tán ngẫu nhiên trên bộ nhớ. RAM ("Bộ nhớ truy cập ngẫu nhiên") không thực sự ngẫu nhiên và các khối bộ nhớ cần được tìm nạp vào bộ đệm. Thao tác này cần có thời gian và khi việc tìm nạp như vậy xảy ra thường xuyên - các trang bộ nhớ trong bộ đệm cần phải được thay thế mọi lúc -> Lỗi bộ nhớ cache -> Bộ nhớ cache không hiệu quả. ArrayListcác phần tử được lưu trữ trên bộ nhớ liên tục - đó chính xác là những gì mà kiến ​​trúc CPU hiện đại đang tối ưu hóa.

  2. LinkedListYêu cầu thứ cấp để giữ lại con trỏ lùi / tiến, nghĩa là gấp 3 lần mức tiêu thụ bộ nhớ cho mỗi giá trị được lưu trữ so với ArrayList.

DynamicIntArray , btw, là một triển khai ArrayList tùy chỉnh Int(kiểu nguyên thủy) và không phải là Đối tượng - do đó tất cả dữ liệu thực sự được lưu trữ một cách ngẫu nhiên - do đó thậm chí còn hiệu quả hơn.

Một yếu tố quan trọng cần nhớ là chi phí tìm nạp khối bộ nhớ, có ý nghĩa lớn hơn chi phí truy cập vào một ô nhớ. Đó là lý do tại sao đầu đọc 1MB bộ nhớ tuần tự nhanh hơn tới x400 lần so với việc đọc lượng dữ liệu này từ các khối bộ nhớ khác nhau:

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

Nguồn: Số trễ Mỗi lập trình viên nên biết

Để làm cho điểm rõ ràng hơn, vui lòng kiểm tra điểm chuẩn của việc thêm các yếu tố vào đầu danh sách. Đây là một trường hợp sử dụng trong đó, về mặt lý thuyết, LinkedListnên thực sự tỏa sáng và ArrayListsẽ đưa ra kết quả trường hợp kém hoặc thậm chí tệ hơn:

nhập mô tả hình ảnh ở đây

Lưu ý: đây là điểm chuẩn của lib C ++ Std, nhưng trải nghiệm trước đây của tôi cho thấy kết quả C ++ và Java rất giống nhau. Mã nguồn

Sao chép một lượng lớn bộ nhớ tuần tự là một hoạt động được tối ưu hóa bởi các CPU hiện đại - thay đổi lý thuyết và thực sự, một lần nữa, ArrayList/ Vectorhiệu quả hơn nhiều


Tín dụng: Tất cả các điểm chuẩn được đăng ở đây được tạo bởi Kjell Hedström . Thậm chí nhiều dữ liệu có thể được tìm thấy trên blog của anh ấy


Tôi sẽ không gọi một hàng đợi độc đáo hay cực đoan! Một hàng đợi fifo được thực hiện dễ dàng hơn nhiều trên LinkedList thay vì ArrayList. Đây thực sự là một cơn ác mộng đối với một ArrayList khi bạn phải theo dõi sự khởi đầu của chính mình, dừng lại và thực hiện việc tái phân bổ của riêng bạn, bạn cũng có thể sử dụng một mảng, nhưng Danh sách được liên kết là một phần năm. Tôi không chắc chắn về việc triển khai Java, nhưng LinkedList có thể thực hiện O (1) cho cả hoạt động hàng đợi và dequeue (Yêu cầu một con trỏ đặc biệt đến phần tử đuôi để xóa, mà tôi giả sử java có nhưng tôi đã kiểm tra hai lần .)
Bill K

24

Nếu mã của bạn có add(0)remove(0), hãy sử dụng LinkedListvà nó đẹp hơn addFirst()removeFirst()phương thức. Nếu không, sử dụng ArrayList.

Và tất nhiên, Danh sách bất biến của Guava là người bạn tốt nhất của bạn.


3
Đối với các danh sách nhỏ, ArrayList.add (0) vẫn sẽ luôn nhanh hơn LinkedList.addFirst ().
Porculus

1
@Porculus Tôi liên tục nghe thấy lập luận này rằng đối với các danh sách nhỏ ArrayList.add (0) sẽ nhanh hơn, nhỏ này nhỏ bao nhiêu? 10 yếu tố, 10 triệu ,?
garg10may

1
@ garg10may nhỏ hơn 10.
Jesse Wilson

@Porculus nhỏ có nghĩa là ít hơn dung lượng tối đa của mảng bên trong nằm dưới ArrayList.
Janac Meena ngày

21

Tôi biết đây là một bài viết cũ, nhưng thực lòng tôi không thể tin rằng không ai đề cập đến việc LinkedListthực hiện đó Deque. Chỉ cần nhìn vào các phương thức trong Deque(và Queue); nếu bạn muốn so sánh công bằng, hãy thử chạy LinkedListngược lại ArrayDequevà thực hiện so sánh giữa các tính năng.


18

Đây là ký hiệu Big-O ở cả ArrayListLinkedListCopyOnWrite-ArrayList:

Lập danh sách

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

Danh sách liên kết

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)

Dựa trên những điều này bạn phải quyết định chọn gì. :)


9
>>>> ArrayList thêm -> O (1) <- không tru. Trong một số trường hợp, ArrayList sẽ phải phát triển để thêm một phần tử nữa
kachanov

1
LinkedList remove không phải là O (1), nó sẽ cần tìm kiếm phần tử bị xóa, do đó, trường hợp xấu nhất là O (n) và trung bình O (n / 2)
garg10may

Không LinkedList.add(), mặc dù hầu hết các câu trả lời ở đây đều nói như vậy.
Hầu tước Lorne

18

Chúng ta hãy so sánh LinkedList và ArrayList wrt bên dưới các tham số:

1. Thực hiện

Lập danh sách là triển khai mảng có thể thay đổi kích thước của giao diện danh sách, trong khi

LinkedList là triển khai danh sách Liên kết đôi của giao diện danh sách.


2. Hiệu suất

  • get (int index) hoặc thao tác tìm kiếm

    Lập danh sáchHoạt động get (int index) chạy trong thời gian không đổi tức là O (1) trong khi

    Thời gian chạy hoạt động LinkedList get (int index) là O (n).

    Lý do đằng sau ArrayList nhanh hơn LinkedList là vì ArrayList sử dụng một hệ thống dựa trên chỉ mục cho các phần tử của nó vì bên trong nó sử dụng một cấu trúc dữ liệu mảng, mặt khác,

    LinkedList không cung cấp quyền truy cập dựa trên chỉ mục cho các phần tử của nó vì nó lặp lại từ đầu hoặc cuối (bất kỳ gần hơn) để lấy nút tại chỉ mục phần tử đã chỉ định.

  • thao tác chèn () hoặc thêm (Object)

    Các phần chèn thêm trong LinkedList thường nhanh so với ArrayList. Trong LinkedList thêm hoặc chèn là hoạt động O (1).

    Trong khi ở ArrayList , nếu mảng đầy đủ tức là trường hợp xấu nhất, sẽ có thêm chi phí thay đổi kích thước mảng và sao chép các phần tử sang mảng mới, điều này làm cho thời gian chạy của hoạt động thêm trong ArrayList O (n), nếu không thì đó là O (1) .

  • loại bỏ (int) hoạt động

    Xóa hoạt động trong LinkedList thường giống như ArrayList tức là O (n).

    Trong LinkedList , có hai phương thức gỡ bỏ quá tải. một là remove () mà không có bất kỳ tham số nào loại bỏ phần đầu của danh sách và chạy trong thời gian không đổi O (1). Phương thức loại bỏ quá tải khác trong LinkedList là remove (int) hoặc remove (Object) để loại bỏ Object hoặc int được truyền dưới dạng tham số. Phương thức này đi qua LinkedList cho đến khi tìm thấy Object và hủy liên kết nó khỏi danh sách ban đầu. Do đó thời gian chạy phương thức này là O (n).

    Trong khi trong phương thức ArrayList remove (int) liên quan đến việc sao chép các phần tử từ mảng cũ sang mảng được cập nhật mới, do đó thời gian chạy của nó là O (n).


3. Đảo ngược

LinkedList có thể được lặp theo hướng ngược lại bằng cách sử dụng desceinatingIterator () trong khi

không có desceinatingIterator () trong ArrayList , vì vậy chúng ta cần viết mã của riêng mình để lặp qua ArrayList theo hướng ngược lại.


4. Công suất ban đầu

Nếu hàm tạo không bị quá tải, thì ArrayList sẽ tạo một danh sách trống có dung lượng ban đầu 10, trong khi

LinkedList chỉ xây dựng danh sách trống mà không có bất kỳ dung lượng ban đầu.


5. Bộ nhớ trên cao

Chi phí bộ nhớ trong LinkedList nhiều hơn so với ArrayList như một nút trong LinkedList cần duy trì địa chỉ của nút tiếp theo và trước đó. Trong khi

Trong ArrayList, mỗi chỉ mục chỉ giữ đối tượng thực tế (dữ liệu).


Nguồn


18

Tôi thường sử dụng cái này dựa trên cái khác dựa trên độ phức tạp thời gian của các thao tác mà tôi thực hiện trên Danh sách cụ thể đó.

|---------------------|---------------------|--------------------|------------|
|      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 cân bằng mọi thứ hơn một chút đối với các mảng vì chèn / xóa mặt trước / mặt sau đều là O (1), điều duy nhất Danh sách liên kết vẫn chiến thắng là thêm / xóa trong khi di chuyển ngang (hoạt động của Iterator).
Bill K

14

Ngoài các đối số tốt khác ở trên, bạn nên chú ý ArrayListthực hiện RandomAccessgiao diện, trong khi LinkedListthực hiện Queue.

Vì vậy, bằng cách nào đó, họ giải quyết các vấn đề hơi khác nhau, với sự khác biệt về hiệu quả và hành vi (xem danh sách các phương pháp của họ).


10

Nó phụ thuộc vào những hoạt động bạn sẽ làm nhiều hơn trong Danh sách.

ArrayListnhanh hơn để truy cập một giá trị được lập chỉ mục. Nó tồi tệ hơn nhiều khi chèn hoặc xóa các đối tượng.

Để tìm hiểu thêm, hãy đọc bất kỳ bài viết nào nói về sự khác biệt giữa các mảng và danh sách được liên kết.


2
Để tìm hiểu thêm không đọc, chỉ cần viết mã. và bạn sẽ thấy rằng việc thực hiện ArrayList nhanh hơn sau đó LinkedList trong việc chèn và xóa.
kachanov

8

Một danh sách mảng về cơ bản là một mảng với các phương thức để thêm các mục, v.v. (và bạn nên sử dụng một danh sách chung thay thế). Nó là một tập hợp các mục có thể được truy cập thông qua một bộ chỉ mục (ví dụ [0]). Nó ngụ ý một sự tiến triển từ một mục này sang mục tiếp theo.

Một danh sách được liên kết chỉ định một sự tiến triển từ một mục sang mục tiếp theo (Mục a -> mục b). Bạn có thể có được hiệu ứng tương tự với một danh sách mảng, nhưng một danh sách được liên kết hoàn toàn cho biết mục nào được cho là tuân theo mục trước.



7

Một tính năng quan trọng của danh sách được liên kết (mà tôi không đọc trong câu trả lời khác) là nối hai danh sách. Với một mảng, đây là O (n) (+ tổng phí của một số phân bổ lại) với danh sách được liên kết, đây chỉ là O (1) hoặc O (2) ;-)

Quan trọng : Đối với Java, LinkedListđiều này không đúng! Xem Có phương pháp concat nhanh nào cho danh sách được liên kết trong Java không?


2
Làm như thế nào? Điều này có thể đúng với các cấu trúc dữ liệu danh sách được liên kết nhưng không phải là đối tượng Java LinkList. Bạn không thể chỉ một điểm nexttừ một danh sách đến nút đầu tiên trong danh sách thứ hai. Cách duy nhất là sử dụng addAll()để thêm các phần tử một cách tuần tự, mặc dù nó tốt hơn là lặp qua và gọi add()cho từng phần tử. Để thực hiện điều này một cách nhanh chóng trong O (1), bạn sẽ cần một lớp tổng hợp (như org.apache.commons.collections.collection.CompổngCollection) nhưng sau đó, nó sẽ hoạt động cho bất kỳ loại Danh sách / Bộ sưu tập nào.
Kevin Brock

Vâng đúng. Tôi chỉnh sửa câu trả lời cho phù hợp. nhưng hãy xem câu trả lời này để biết 'làm thế nào' với LinkedList: stackoverflow.com/questions/2494031/
mẹo

7

ArrayList và LinkedList có những ưu và nhược điểm riêng.

ArrayList sử dụng địa chỉ bộ nhớ liền kề so với LinkedList sử dụng các con trỏ hướng tới nút tiếp theo. Vì vậy, khi bạn muốn tìm kiếm một phần tử trong ArrayList nhanh hơn thực hiện n lần lặp với LinkedList.

Mặt khác, việc chèn và xóa trong LinkedList dễ dàng hơn nhiều vì bạn chỉ cần thay đổi các con trỏ trong khi ArrayList ngụ ý việc sử dụng thao tác thay đổi cho bất kỳ thao tác chèn hoặc xóa nào.

Nếu bạn có các hoạt động truy xuất thường xuyên trong ứng dụng của mình, hãy sử dụng ArrayList. Nếu bạn thường xuyên chèn và xóa, hãy sử dụng LinkedList.


6

Tôi đã đọc các phản hồi, nhưng có một kịch bản mà tôi luôn sử dụng LinkedList trên một ArrayList mà tôi muốn chia sẻ để nghe ý kiến:

Mỗi lần tôi có một phương thức trả về danh sách dữ liệu thu được từ DB, tôi luôn sử dụng LinkedList.

Lý do của tôi là bởi vì không thể biết chính xác tôi nhận được bao nhiêu kết quả, sẽ không bị lãng phí bộ nhớ (như trong ArrayList với sự khác biệt giữa dung lượng và số phần tử thực tế), và sẽ không lãng phí thời gian để cố gắng nhân đôi công suất.

Theo như ArrayList, tôi đồng ý rằng ít nhất bạn nên luôn luôn sử dụng hàm tạo với công suất ban đầu, để giảm thiểu sự trùng lặp của các mảng càng nhiều càng tốt.


5

ArrayListLinkedListcả hai dụng cụ List interface và phương pháp và kết quả của chúng gần như giống hệt nhau. Tuy nhiên, có một vài khác biệt giữa chúng làm cho cái này tốt hơn cái khác tùy theo yêu cầu.

ArrayList Vs LinkedList

1) Search: ArrayListthao tác tìm kiếm khá nhanh so với LinkedListthao tác tìm kiếm. get(int index)trong ArrayListcho hiệu suất O(1)trong khi LinkedListhiệu suất là O(n).

Reason: ArrayListduy trì hệ thống dựa trên chỉ mục cho các phần tử của nó vì nó sử dụng cấu trúc dữ liệu mảng một cách ngầm định giúp cho việc tìm kiếm một phần tử trong danh sách nhanh hơn. Mặt khácLinkedList thực hiện danh sách liên kết đôi đòi hỏi phải duyệt qua tất cả các phần tử để tìm kiếm một phần tử.

2) Deletion: LinkedListloại bỏ hoạt động mang lại O(1)hiệu suất trong khi ArrayListcho hiệu suất thay đổi: O(n)trong trường hợp xấu nhất (trong khi loại bỏ yếu tố đầu tiên) và O(1)trong trường hợp tốt nhất (Trong khi loại bỏ yếu tố cuối cùng).

Kết luận: Việc xóa phần tử LinkedList nhanh hơn so với ArrayList.

Lý do: Mỗi phần tử của LinkedList duy trì hai con trỏ (địa chỉ) trỏ đến cả hai phần tử lân cận trong danh sách. Do đó loại bỏ chỉ yêu cầu thay đổi vị trí con trỏ trong hai nút lân cận (phần tử) của nút sẽ bị loại bỏ. Trong khi trong ArrayList, tất cả các phần tử cần được dịch chuyển để điền vào khoảng trống được tạo bởi phần tử bị loại bỏ.

3) Inserts Performance: LinkedListthêm phương thức cho O(1)hiệu suất trong khi ArrayListđưa ra O(n)trong trường hợp xấu nhất. Lý do giống như giải thích để loại bỏ.

4) Memory Overhead: ArrayListduy trì các chỉ mục và dữ liệu phần tử trong khi LinkedListduy trì dữ liệu phần tử và hai con trỏ cho các nút lân cận

do đó mức tiêu thụ bộ nhớ tương đối cao trong LinkedList.

Có một vài điểm tương đồng giữa các lớp này như sau:

  • Cả ArrayList và LinkedList đều đang triển khai giao diện List.
  • Cả hai đều duy trì thứ tự chèn các phần tử có nghĩa là trong khi hiển thị các phần tử ArrayList và LinkedList, tập kết quả sẽ có cùng thứ tự trong đó các phần tử được chèn vào Danh sách.
  • Cả hai lớp này đều không được đồng bộ hóa và có thể được đồng bộ hóa rõ ràng bằng cách sử dụng phương thức Collections.syn syncizedList.
  • Các lớp này iteratorvà được listIteratortrả về là fail-fast(nếu danh sách được sửa đổi cấu trúc bất cứ lúc nào sau khi trình lặp được tạo, theo bất kỳ cách nào ngoại trừ thông qua các iterator’sphương thức loại bỏ hoặc thêm riêng, trình lặp sẽ throwa ConcurrentModificationException).

Khi nào nên sử dụng LinkedList và khi nào nên sử dụng ArrayList?

  • Như đã giải thích ở trên các hoạt động chèn và loại bỏ cho hiệu suất tốt (O(1))trong LinkedListso vớiArrayList(O(n)) .

    Do đó, nếu có yêu cầu bổ sung và xóa thường xuyên trong ứng dụng thì LinkedList là một lựa chọn tốt nhất.

  • Các get methodhoạt động tìm kiếm ( ) nhanh chóng Arraylist (O(1))nhưng không trongLinkedList (O(n))

    vì vậy, nếu có ít hoạt động thêm và xóa và yêu cầu hoạt động tìm kiếm nhiều hơn, ArrayList sẽ là lựa chọn tốt nhất của bạn.


5

Thao tác get (i) trong ArrayList nhanh hơn LinkedList, bởi vì:
ArrayList: Triển khai mảng có thể thay đổi kích thước của giao diện Danh sách
LinkedList: Thực hiện danh sách liên kết đôi của giao diện List và Deque

Các hoạt động lập chỉ mục vào danh sách sẽ duyệt qua danh sách từ đầu hoặc cuối, tùy theo chỉ số nào gần với chỉ mục được chỉ định.


5

1) Cơ cấu dữ liệu

Sự khác biệt đầu tiên giữa ArrayList và LinkedList đi kèm với thực tế là ArrayList được hỗ trợ bởi Array trong khi LinkedList được hỗ trợ bởi LinkedList. Điều này sẽ dẫn đến sự khác biệt hơn nữa trong hiệu suất.

2) LinkedList thực hiện Deque

Một điểm khác biệt giữa ArrayList và LinkedList là ngoài giao diện List, LinkedList còn thực hiện giao diện Deque, cung cấp các hoạt động đầu tiên trong các hoạt động đầu tiên cho add () và poll () và một số hàm Deque khác. 3) Thêm các phần tử trong ArrayList Thêm phần tử trong ArrayList là hoạt động O (1) nếu nó không kích hoạt lại kích thước của Array, trong trường hợp đó, nó trở thành O (log (n)), mặt khác, nối thêm một phần tử vào LinkedList là hoạt động O (1), vì nó không yêu cầu bất kỳ điều hướng nào.

4) Loại bỏ một yếu tố khỏi một vị trí

Để loại bỏ một phần tử khỏi một chỉ mục cụ thể, ví dụ như bằng cách gọi remove (index), ArrayList thực hiện một thao tác sao chép làm cho nó gần với O (n) trong khi LinkedList cần đi qua điểm đó cũng làm cho nó O (n / 2) , vì nó có thể đi qua từ một trong hai hướng dựa trên sự gần gũi.

5) Lặp lại trên ArrayList hoặc LinkedList

Lặp lại là hoạt động O (n) cho cả LinkedList và ArrayList trong đó n là một số phần tử.

6) Lấy phần tử từ một vị trí

Hoạt động get (index) là O (1) trong ArrayList trong khi O (n / 2) của nó trong LinkedList, vì nó cần đi qua cho đến mục đó. Mặc dù, trong ký hiệu Big O O (n / 2) chỉ là O (n) vì chúng ta bỏ qua các hằng số ở đó.

7) Bộ nhớ

LinkedList sử dụng một đối tượng trình bao bọc, Entry, là một lớp lồng tĩnh để lưu trữ dữ liệu và hai nút tiếp theo và trước đó trong khi ArrayList chỉ lưu trữ dữ liệu trong Array.

Vì vậy, yêu cầu bộ nhớ dường như ít hơn trong trường hợp của ArrayList so với LinkedList ngoại trừ trường hợp Array thực hiện thao tác kích thước lại khi nó sao chép nội dung từ mảng này sang mảng khác.

Nếu Array đủ lớn, nó có thể mất rất nhiều bộ nhớ tại thời điểm đó và kích hoạt bộ sưu tập Rác, điều này có thể làm chậm thời gian phản hồi.

Từ tất cả các khác biệt ở trên giữa ArrayList so với LinkedList, có vẻ ArrayList là lựa chọn tốt hơn so với LinkedList trong hầu hết các trường hợp, ngoại trừ khi bạn thực hiện thao tác add () thường xuyên hơn remove () hoặc get ().

Dễ dàng sửa đổi danh sách được liên kết hơn ArrayList, đặc biệt nếu bạn thêm hoặc xóa các phần tử khỏi đầu hoặc cuối vì danh sách được liên kết bên trong giữ các tham chiếu của các vị trí đó và chúng có thể truy cập được trong thời gian O (1).

Nói cách khác, bạn không cần phải duyệt qua danh sách được liên kết để đến vị trí mà bạn muốn thêm các phần tử, trong trường hợp đó, phép cộng trở thành thao tác O (n). Ví dụ: chèn hoặc xóa một phần tử ở giữa danh sách được liên kết.

Theo tôi, sử dụng ArrayList trên LinkedList cho hầu hết các mục đích thực tế trong Java.


1
Tôi nghĩ rằng đây là câu trả lời tốt nhất của toàn bộ nhóm ở đây. Đó là chính xác và nhiều thông tin. Tôi sẽ đề nghị thay đổi dòng cuối cùng - cuối cùng hãy thêm "ngoài hàng đợi", những cấu trúc rất quan trọng thực sự không có ý nghĩa đối với một danh sách được liên kết.
Bill K

3

Một trong những bài kiểm tra tôi thấy ở đây chỉ tiến hành bài kiểm tra một lần. Nhưng điều tôi nhận thấy là bạn cần chạy các bài kiểm tra này nhiều lần và cuối cùng thời gian của chúng sẽ hội tụ. Về cơ bản, JVM cần khởi động. Đối với trường hợp sử dụng cụ thể của tôi, tôi cần thêm / xóa các mục vào danh sách tăng lên khoảng 500 mục. Trong các thử nghiệm của tôi LinkedListxuất hiện nhanh hơn, với LinkedListkhoảng 50.000 NS và ArrayListđạt khoảng 90.000 NS ... cho hoặc nhận. Xem mã dưới đây.

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

Cả remove () và insert () đều có hiệu suất thời gian chạy là O (n) cho cả ArrayLists và LinkedLists. Tuy nhiên, lý do đằng sau thời gian xử lý tuyến tính đến từ hai lý do rất khác nhau:

Trong một ArrayList, bạn có được phần tử trong O (1), nhưng thực sự loại bỏ hoặc chèn một cái gì đó làm cho nó O (n) vì tất cả các phần tử sau cần phải được thay đổi.

Trong LinkedList, phải mất O (n) để thực sự đến phần tử mong muốn, bởi vì chúng ta phải bắt đầu ngay từ đầu cho đến khi chúng ta đạt được chỉ mục mong muốn. Trên thực tế, việc xóa hoặc chèn là không đổi, bởi vì chúng ta chỉ phải thay đổi 1 tham chiếu cho remove () và 2 tham chiếu cho insert ().

Cái nào trong hai cái nhanh hơn để chèn và loại bỏ phụ thuộc vào nơi nó xảy ra. Nếu chúng ta gần với sự khởi đầu thì LinkedList sẽ nhanh hơn, vì chúng ta phải trải qua tương đối ít yếu tố. Nếu chúng ta càng gần cuối thì một ArrayList sẽ nhanh hơn, bởi vì chúng ta đến đó trong thời gian không đổi và chỉ phải thay đổi một vài yếu tố còn lại theo sau nó. Khi được thực hiện chính xác ở giữa, LinkedList sẽ nhanh hơn vì đi qua n phần tử nhanh hơn di chuyển n giá trị.

Phần thưởng: Mặc dù không có cách nào để tạo hai phương thức O (1) này cho ArrayList, nhưng thực sự có một cách để làm điều này trong LinkedLists. Giả sử chúng tôi muốn thực hiện toàn bộ Danh sách loại bỏ và chèn các phần tử trên đường đi. Thông thường, bạn sẽ bắt đầu từ đầu cho mỗi phần tử bằng LinkedList, chúng tôi cũng có thể "lưu" phần tử hiện tại mà chúng tôi đang làm việc với Iterator. Với sự trợ giúp của Iterator, chúng ta có được hiệu quả O (1) để loại bỏ () và insert () khi làm việc trong LinkedList. Làm cho nó trở thành lợi ích hiệu suất duy nhất Tôi biết nơi LinkedList luôn tốt hơn ArrayList.


1

ArrayList mở rộng AbstractList và thực hiện Giao diện Danh sách. ArrayList là mảng động.
Có thể nói rằng về cơ bản nó đã được tạo ra để khắc phục nhược điểm của mảng

Lớp LinkedList mở rộng AbstractSequentialList và thực hiện giao diện List, Deque và Queue.
Hiệu suất
arraylist.get()là O (1) trong khi linkedlist.get()O (n)
arraylist.add()là O (1) và linkedlist.add()0 (1)
arraylist.contains()là O (n) và linkedlist.contains()O (n)
arraylist.next()là O (1) và linkedlist.next()O (1)
arraylist.remove()là O (n) trong khi đó linkedlist.remove()là O (1)
Trong danh sách mảng
iterator.remove()là O (n)
trong khi Trong danh sách liên kết
iterator.remove()là O (1)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.