So sánh Array và LinkedList

ArrayList và LinkedList là 2 implementation nổi tiếng của List interface, chúng được sinh ra với các mục đích sử dụng khác nhau mà trong bài viết này chúng ta sẽ cùng nhau tìm hiểu.

ArrayList

Nội bộ bên trong ArrayList sử dụng một array để triển khai List interface. Vì array có kích thước cố định nên ArrayList sẽ tại một array với kích thước cố định ban đầu. Trong quá trình sử dụng nếu số lượng phần tử cần được lưu vượt quá kích thước của array thì nó sẽ khởi tạo một array mới với kích thước lớn hơn để lưu trữ.

Thêm phần tử

Khi chúng ta khởi tạo một ArrayList rỗng, nó sẽ khởi tạo một array nội bộ bên trong [gọi là backing array] với kích thước mặc định là 10.

Khi thêm một phần tử vào ArrayList mảng backing array vẫn còn chỗ chứa thì cung việc đơn giản chỉ là thêm nó vào vị trí tiếp theo liền kề vị trí cuối cùng được thêm vào backing array.

backingArray[size] = newItem; size++;

Vì vậy trong trường hợp tốt nhất và trung bình thì time complexity là O[1]. khá nhanh. Tuy nhiên, khi backing array trở nên nên đầy thì việc thêm phần tử trở nên phức tạp hơn.

Để thêm một phần tử mới, chúng ta cần khởi tạo một backing array mới với dung lượng lớn hơn, sau đó sao chép toàn bộ phần tử từ backing array cũ sang backing array mới. Cuối cùng là thêm phần tử mới vào backing array mới. Vì thế trong trường hợp này việc thêm phần tử có time complexity là O[n].

Truy cập theo index

Việc truy xuất các phần tử theo index trong ArrayList là một điều tuyệt vời, chúng ta chỉ tốn O[1] để lấy một phần tử tại vị trí index được chỉ định.

Xoá  phần tử tại vị trí index

Giả sử chúng ta muốn xoá phần tử tại vị trí index 6 ra khởi ArrayList. Quá trình này sẽ diễn ra như hình bên dưới.

Sau khi xoá phần tử tại vị trí index 6, chúng ta phải du chuyển các phần tử ngay sau đó về phía bên trái một đơn vị.Vì vậy, time complexity là O[1] ở trường hợp tốt nhất và O[n] ở mức trung bình và trường hợp xấu nhất.

Ứng dụng và hạn chế

hông thường, ArrayList là lựa chọn mặc định cho nhiều nhà phát triển khi họ cần List implementation. Thực tế thì nó sẽ là một lựa chọn tốt khi các hoạt động đọc dữ liệu diễn ra nhiều hơn số lần ghi.

Đôi khi chúng ta cần đọc và ghi thường xuyên như nhau. Nếu chúng ta có ước tính về số lượng phần tử tối đa có thể, thì việc sử dụng ArrayList vẫn có ý nghĩa. Nếu đúng như vậy, chúng ta có thể khởi tạo ArrayList với dung lượng ban đầu:

int possibleUpperBound = 10_000; List items = new ArrayList[possibleUpperBound];

Việc này sẽ giúp chúng ta giảm thiểu tối đa việc phải phân bổ lại array khi kích thước của nó không còn đủ để lưu trữ.

Ngoài ra, array được đánh chỉ mục với giá trị kiểu int trong Java, vì vậy số lượng phần tử tối đa được lưu trữ bên trong ArrayList không vượt quá 2^32.

LinkedList

LinkedList, như cái tên thì nó sử dụng các node liên kết để lưu trữ và truy xuất các phần tử. Ví dụ

Mỗi node chứa 2 con trỏ, trỏ đến phần tử liền trước và liền sau nó, cấu trúc dữ liệu này còn được gọi là doubly linked.

Thêm phần tử

Để thêm một phần tử trong LinkedList, chúng ta chỉ cần trỏ last element đến phần tử được thêm mới.

Và sau đó cập nhật con trỏ last.

Trải qua 2 hoạt động trên, time complexity để thêm một phần tử trên Linkedlist luôn là O[1].

Truy cập theo index

Không giống như ArrayList, để truy cập một phần tử tại vị trí index chúng ta cần đi qua lần lượt từng phần tử trước đó đến khi đi đến được vị trí của phần tử cần truy xuất. Vì vậy trong trường hợp trung bình và xấu nhất thì time complexity là O[n].

Xoá phần tử tại vị trí index

Để xoá một phần tử trong Linkedlist, chúng ta cũng cần duyệt qua các phần tử cho đến khi gặp được phần tử cần xoá và sau đó ngắt lên kết giữa node bị xoá với node trước và sau nó. Vì vậy trong trường hợp trung bình và xấu nhất thì time complexity là O[n].

Ứng dụng

LinkedList phù hợp hơn ArrayList khi các hoạt động thêm phần tử xảy ra thường xuyên hơn là đọc. Ngoài ra, nó có thể được sử dụng trong các trường hợp việc đọc dữ liệu đa số là đọc phần tử đầu tiên và cuối cùng.

Nguồn

//www.baeldung.com/java-arraylist-linkedlist

Chiều 30 tết cuối năm, rảnh viết nốt bài để đón tết con Heo 😀

Tiếp tục là 1 bài về so sánh các thứ trong Java, cái này thi thoảng cũng gặp khi phỏng vấn nhưng bài này mình muốn giúp các bạn có thể hiểu rõ hơn về bản chất để sử dụng hợp lý hơn, đảm bảo hiệu năng tốt hơn.

Trước tiên mình muốn đề cập Array mô tả 1 cấu trúc dữ liệu mảng cơ bản từ C/C++. Array được lưu trữ trong 1 dãy các ô nhớ liền nhau trong bộ nhớ và có thể truy xuất trực tiếp đến từng phần tử thông qua số thứ tự [index] của phần tử trong mảng. Không có các tiện ích sẵn để add/get/remove… phần tử mà chỉ có thuộc tính length để lấy kích thước. Kích thước của Arraycố định và buộc phải định nghĩa khi khai báo.

Còn ArrayList LinkedListVector, cả 3 cái này đều là class implement List, trong đó List là 1 collection trong Java được thiết kế có đầy đủ các method cơ bản như get, add, remove, sort,… vì vậy ArrayList LinkedListVector implement lại List đều mang đầy đủ những tiện ích này từ List. List được lưu trữ dưới dạng các ô nhớ rải rác trong bộ nhớ trỏ đến nhau thông qua địa chỉ ô nhớ. Việc này nhằm tối ưu về bộ nhớ có thể tận dụng các ô nhớ ở các vị trí khác nhau không nhất thiết phải liền nhau, nhưng lại gây ra việc không thể truy xuất trực tiếp đến 1 phần từ bất kỳ mà phải duyệt từ đầu List để tìm.

Tìm hiểu chi tiết hơn.

LinkedList là class implement List và cũng mang đầy đủ tiện ích của List và được bổ sung thêm các tiện ích của Deque tối ưu cho các thao tác add/remove phần từ, hay các tiện ích của AbstractSequentialList giúp tối ưu khi duyệt phần tử bằng Iterator. Nhưng lại không được tối ưu cho việc truy xuất ngẫu nhiên qua số thứ tự phần tử nên ít khi được sử dụng. Kích thước danh sách là động và được tăng mỗi khi bổ sung phần tử.

ArrayList là class implement List, được mang thêm tiện ích của RandomAccess giúp tối ưu việc truy xuất ngẫu nhiên qua số thứ tự, nhưng lại không được implement Deque nên các thao tác add/remove không nhanh như LinkedList [mình sẽ có ví dụ test performce cụ thể ở dưới]. Kích thước ArrayList là cố định tại 1 thời điểm, nhưng có thể resize thêm 50% mỗi khi đạt giới hạn max hiện tại nên có thể coi là kích thước động.

Vector là class implement giống hệt ArrayList nên mang đầy đủ tiện ích như ArrayList có, và hơn thế nữa được xây dựng thêm các method thao tác phần tử như addElement setElement removeElement insertElement …. và đặc biệt các method của Vector đều được cài đặt synchronized nghĩa là safe nhưng fail-fast, an toàn cho dữ liệu khi sử dụng trong đa luồng nhưng lại gây chậm xử lý. Kích thước của Vector tương tự như ArrayList chỉ khác là sẽ tăng 100% mỗi khi đạt giới hạn max hiện tại.

Các bạn có thể đọc thêm docs của java hoặc đơn giản là decompile jdk hoặc đọc trong source nén của jdk để thấy rõ hơn về những sự giống và khác biệt này.

Tổng hợp lại dưới dạng bảng từ đó rút ra cân nhắc sử dụng cái nào trong từng trường hợp cụ thể.

Array ArrayList LinkedList Vector
Là mảng nguyên thủy, chỉ có thuộc tính length Là class implement List, có các method hỗ trợ get/add/remove/…
Kích thước cố định Tăng 50% kích thước khi đạt giới hạn Kích thước động Tăng 100% kích thước khi đạt giới hạn
Lưu kiểu dữ liệu nguyên thủy và đối tượng Chỉ lưu kiểu dữ liệu đối tượng, từ Java 5 kiểu nguyên thủy được tự động chuyển đổi thành đối tượng để lưu trữ [auto-boxing]
Tốc độ lưu trữ và truy xuất nhanh Tốc độ truy xuất theo index nhanh Tốc độ truy xuất theo index chậm Tốc độ truy xuất theo index nhanh
Rất kém trong việc insert/remove phần tử ở giữa mảng Tốc độ insert/remove phần tử trong mảng trung bình Tốc độ insert/remove phần tử trong mảng nhanh Tốc độ insert/remove phần tử trong mảng trung bình
Không hỗ trợ synchronized, dễ mất dữ liệu khi xử lý đa luồng nhưng nhanh Không hỗ trợ synchronized, dễ mất dữ liệu khi xử lý đa luồng nhưng nhanh Không hỗ trợ synchronized, dễ mất dữ liệu khi xử lý đa luồng nhưng nhanh Hỗ trợ synchronized, an toàn dữ liệu khi xử lý đa luồng nhưng chậm
Thường dùng với các thao tác bổ sung vào cuối mảng và truy xuất thông thường, sử dụng khi nghiệp vụ đơn giản ít nâng cấp Sử dụng để lưu trữ và truy xuất dữ liệu cơ bản, ít khi thay đổi Sử dụng khi cần thao tác thay đổi dữ liệu nhiều, cho các nghiệp vụ phức tạp Sử dụng khi cần thao tác với nhiều luồng xử lý

Dưới đây là các đoạn test performance khi thực hiện add/get/remove trên Array/ArrayList/LinkedList để nhìn trực quan hơn về sự khác biệt. Các kết quả có thể sẽ không chính xác như ví dụ với từng lần chạy nhưng về cơ bản vẫn có độ chênh lệch có thể nhìn thấy

package net.tunghuynh.listcollection; import org.apache.log4j.Logger; import java.util.*; /** * net.tunghuynh.listcollection.Main * by Tùng Huynh * at 04/02/2019 13:12 AM */ public class Main { static Logger logger = Logger.getLogger[Main.class]; static int n = 100000; public static void main[String[] args] { arrayListVsLinkedList[]; } public static void arrayListVsLinkedList[] { Integer[] array = new Integer[n]; List arrayList = new ArrayList[]; List linkedList = new LinkedList[]; logger.info["==== Add ===="]; long start = System.nanoTime[]; for [int i = 0; i < n; i++] { array[i] = i; } logger.info["Array : " + [System.nanoTime[] - start]/1000000.0 + " ms"]; start = System.nanoTime[]; for [int i = 0; i < n; i++] { arrayList.add[i]; } logger.info["ArrayList : " + [System.nanoTime[] - start]/1000000.0 + " ms"]; start = System.nanoTime[]; for [int i = 0; i < n; i++] { linkedList.add[i]; } logger.info["LinkedList: " + [System.nanoTime[] - start]/1000000.0 + " ms"]; logger.info["==== Get ===="]; start = System.nanoTime[]; for [int i = 0; i < n; i++] { Integer x = array[i]; } logger.info["Array loop index : " + [System.nanoTime[] - start]/1000000.0 + " ms"]; start = System.nanoTime[]; for [int i = 0; i < n; i++] { arrayList.get[i]; } logger.info["ArrayList loop index : " + [System.nanoTime[] - start]/1000000.0 + " ms"]; start = System.nanoTime[]; for [int i = 0; i < n; i++] { linkedList.get[i]; } logger.info["LinkedList loop index : " + [System.nanoTime[] - start]/1000000.0 + " ms"]; start = System.nanoTime[]; for [Object item : arrayList] { Object x = item; } logger.info["ArrayList loop interator : " + [System.nanoTime[] - start]/1000000.0 + " ms"]; start = System.nanoTime[]; for [Object item : linkedList] { Object x = item; } logger.info["LinkedList loop interator: " + [System.nanoTime[] - start]/1000000.0 + " ms"]; logger.info["==== Remove ===="]; start = System.nanoTime[]; for [int i = 9999; i >=0; i--] { arrayList.remove[i]; } logger.info["ArrayList : " + [System.nanoTime[] - start]/1000000.0 + " ms"]; start = System.nanoTime[]; for [int i = 9999; i >=0; i--] { linkedList.remove[i]; } logger.info["LinkedList: " + [System.nanoTime[] - start]/1000000.0 + " ms"]; } }

package net.tunghuynh.listcollection;

import org.apache.log4j.Logger;

* net.tunghuynh.listcollection.Main

    static Logger logger = Logger.getLogger[Main.class];

    public static void main[String[] args] {

    public static void arrayListVsLinkedList[] {

        Integer[] array = new Integer[n];

        List arrayList = new ArrayList[];

        List linkedList = new LinkedList[];

        logger.info["==== Add ===="];

        long start = System.nanoTime[];

        for [int i = 0; i

Chủ Đề