الاختيار: ArrayList مقابل LinkedList مقابل ArrayDeque
في هذا الدرس 11 موضوعات
هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.
Lesson Overview
You now know how an array behaves, how a dynamic array grows, how a linked list rewires, and how stacks and queues restrict access. This short lesson turns all of that into a decision. When you need a list, a stack, or a queue in Java, which class do you reach for?
The method is simple: decide by the operation you do most. The costs you have already derived tell you the answer. Think of it as choosing a vehicle: a bicycle, a bus, or a motorbike is not "best" in general, only best for a given trip.
Why this matters: wrong choices rarely crash a program. They make it slow in ways that appear only with real data. Being able to justify a choice in one sentence is a mark of a careful developer, and reviewers ask for it.
This lesson builds on every lesson in this module and on "Choosing a collection: decision chart" in the Collections module (BEG-030).
Learning Objectives
By the end of this lesson you will be able to:
- State the main costs of
ArrayList,LinkedList, andArrayDequefor the common operations. - Explain why each cost is what it is, using arrays and nodes.
- Pick a structure for a list, a stack, and a queue, and justify the choice.
- Explain why
ArrayListis the default andArrayDequeis the default for stacks and queues. - Say what you must do before claiming one structure is faster than another.
Written Explanation
Three classes, three shapes
| Class | Shape inside | Interfaces you use it through |
|---|---|---|
ArrayList | A resizable array (Lessons 1 and 2) | List |
LinkedList | Doubly linked nodes (Lesson 3) | List and Deque |
ArrayDeque | A resizable array used from both ends, as in Lesson 5's wrapping idea | Deque and Queue |
The JDK documentation describes LinkedList as a doubly linked list that implements both List and Deque. ArrayDeque is a Deque backed by a resizable array. Both documentation pages describe ArrayList and ArrayDeque in terms of arrays, which is why their costs follow what you learned in Lessons 1 and 2.
The decision table
| Operation | ArrayList | LinkedList | ArrayDeque |
|---|---|---|---|
Get by index get(i) | O(1) | O(n) | not offered |
| Add at the end | O(1) amortized | O(1) | O(1) amortized |
| Add at the front | O(n) | O(1) | O(1) amortized |
| Remove from the front | O(n) | O(1) | O(1) |
| Remove from the end | O(1) | O(1) | O(1) |
| Insert or remove in the middle | O(n) | O(n) to find, O(1) to relink | not offered |
Search by value (contains) | O(n) | O(n) | O(n) |
Reasons behind the cells:
ArrayList.get(i)is the array arithmetic from Lesson 1; theArrayListdocumentation saysgetruns in constant time andaddin amortized constant time.ArrayListfront operations shift every item, as in Lesson 1.LinkedList.get(i)has to walk. The documentation says index operations traverse from the beginning or the end, whichever is closer, which halves the walk at best but is still O(n).LinkedListmiddle insert is O(1) only once you are already at the node, for example through an iterator. Finding it costs O(n). If you must search first, you pay the search.ArrayDequehas no index methods at all, which is deliberate: it is designed for the ends. Tryingd.get(0)on aDequedoes not compile, andjavacreportscannot find symbol ... method get(int).
Why LinkedList disappoints in practice
On paper the linked list wins at the front and in the middle. In practice it is rarely the best choice:
- Each item needs a node object plus references, so it uses more memory per item than an array.
- To use the "O(1) in the middle" advantage you need to already hold the position, which most programs do not.
- For stacks and queues,
ArrayDequecovers the ends just as well. Its documentation says it is likely to be faster thanLinkedListwhen used as a queue, and faster thanStackwhen used as a stack. - Arrays keep items next to each other in memory, which processors usually handle well. How much this matters depends on the machine and the program, so treat it as a reason to measure, not as a guarantee.
The module's rule of thumb is: ArrayList for lists, ArrayDeque for stacks and queues, LinkedList only when you have a specific reason you can explain.
Decide by the operation you do most
Ask three questions in order:
- Do I need to read by position? (
get(i),set(i, x).) Yes:ArrayList. - Do I only add and remove at the ends, newest-first (stack) or oldest-first (queue)? Yes:
ArrayDeque. - Do I need to remove or insert in the middle often, while already holding a position? Maybe
LinkedList, but first check whether a different structure (aSetorMapfrom the Collections module) fits better.
If none of these decide it, use ArrayList. Its documentation describes the constant-time operations that cover most everyday code.
Measure before claiming
A claim like "X is faster than Y" is only worth something with a measurement for your data and your machine. Quick timings written by hand in a main method are easily wrong, because the JVM warms up and optimizes as it runs. For this course, rely on the Big O reasoning above to choose, and use a proper benchmarking tool such as JMH in later work if performance really matters. Never write "linked lists are faster for insertion" as a blanket statement: it is true for the relinking step only.
Code against the interface
Declare variables with the interface, and choose the class once:
List<String> names = new ArrayList<>();
Deque<Integer> undo = new ArrayDeque<>();
Queue<String> jobs = new ArrayDeque<>();If a measurement later shows a different class would fit, you change one line. Writing ArrayList<String> names everywhere would spread the choice through the code.
Examples
Example 1: the same data in three structures (simple)
List<String> arr = new ArrayList<>(List.of("a", "b", "c", "d"));
List<String> linked = new LinkedList<>(List.of("a", "b", "c", "d"));
Deque<String> deque = new ArrayDeque<>(List.of("a", "b", "c", "d"));
System.out.println(arr.get(2) + linked.get(2));
arr.remove(0);
linked.remove(0);
deque.pollFirst();
deque.addLast("e");
System.out.println(arr + " " + linked + " " + deque);Real output:
cc
[b, c, d] [b, c, d] [b, c, d, e]The three structures behave the same (same results from get(2), same list after removing the front); they differ only in cost: arr.remove(0) shifted three items, linked.remove(0) relinked the head, deque.pollFirst() advanced inside its array. That is the key lesson: the choice is about performance and API, not about different answers.
Example 2: a task queue with urgent jobs (practical)
A service has a queue of background jobs. Normal jobs join at the back and workers take from the front. An urgent job jumps to the front. A Deque supports that; a plain Queue does not.
Deque<String> work = new ArrayDeque<>();
work.offerLast("email");
work.offerLast("report");
work.offerFirst("urgent-fix");
System.out.println(work.pollFirst() + ", " + work.pollLast() + ", " + work);Real output:
urgent-fix, report, [email]ArrayDeque is the right choice: every operation is at an end, nothing needs an index, and there are no nulls in jobs. If urgency came in several levels, a priority queue (a later topic) would fit better than inserting at the front.
Example 3: stack or queue from the same class (realistic)
Deque<Integer> stack = new ArrayDeque<>();
stack.push(1); stack.push(2); stack.push(3);
System.out.println(stack);
Queue<Integer> queue = new ArrayDeque<>();
queue.offer(1); queue.offer(2); queue.offer(3);
System.out.println(queue);Real output:
[3, 2, 1]
[1, 2, 3]Same class, same three items, opposite order of printing: push inserts at the front, offer at the back. The way you talk to the object decides whether it acts as a stack or a queue.
Backend Insight
In backend code the choice shows up in small places that add up:
- Result lists. A repository method that returns rows is usually a
List(anArrayList) because callers iterate and index it. - Work buffers. Request handlers and background workers use queue-like structures;
ArrayDequefor a single-threaded buffer. Structures shared between threads need special care that this module does not cover; do not assumeArrayDequeis safe to share between threads. - Review conversations. "Why a
LinkedList?" is a common code-review question. A good answer names the operation, not a vague feeling.
A common trap is looping over a LinkedList with for (int i = 0; i < list.size(); i++) list.get(i). Each get(i) walks, so the loop is O(n²). A for-each loop (which uses an iterator) visits each node once. Similarly, calling remove(0) repeatedly on an ArrayList as a queue is O(n²) overall.
Common Mistakes
Mistake 1: Choosing LinkedList "because insertion is fast"
Insertion is fast only at a position you already hold. Choosing it by default makes get(i) slow and memory use higher.
Mistake 2: Using ArrayList as a queue
remove(0) shifts everything. Use ArrayDeque.
Mistake 3: Using get(i) loops on a LinkedList
Use a for-each loop or an iterator instead, which is O(n) in total.
Mistake 4: Making speed claims without measuring
"This is faster" needs a benchmark on real data. Use Big O to choose, and measure before optimizing further.
Exercises
Try each exercise yourself before opening the solutions.
Level A: Foundation
A1. Which of the three classes has no get(int index) method?
أظهر الحل A1
A1. ArrayDeque. It offers no index operations (calling get on a Deque does not compile).
A2. Which structure would you use by default for (a) a list of search results you display and index, (b) a stack of undo actions, (c) tasks waiting for a worker?
أظهر الحل A2
A2. (a) ArrayList, (b) ArrayDeque used through Deque with push and pop, (c) ArrayDeque used through Queue with offer and poll.
A3. What is the cost of get(i) on an ArrayList and why?
أظهر الحل A3
A3. O(1): the item address is computed from the start of the array and the index, as in Lesson 1.
Level B: Application
B1. A program adds 100,000 events to the front of a list one by one, and later reads them in order. Compare ArrayList and ArrayDeque for the adds, using Big O for the total.
أظهر الحل B1
B1. ArrayList.add(0, x) shifts all existing items each time, so 100,000 adds cost about n²/2 item moves in total, O(n²). ArrayDeque.addFirst(x) is O(1) amortized, so the total is O(n). The ArrayDeque is the better fit, although reading by position later would require a different structure (or copying into a list at the end).
B2. A teammate wrote for (int i = 0; i < links.size(); i++) process(links.get(i)); where links is a LinkedList. Explain the cost and give the fix.
أظهر الحل B2
B2. Each links.get(i) walks from the nearer end, so one call is O(n) and the loop is O(n²). Fix with a for-each loop (for (String link : links) process(link);), which goes through each node once, or use an ArrayList if indexing is truly needed.
B3. Fill in the decision for each: "random reads by position"; "oldest first"; "newest first"; "insert in the middle at a node you already hold".
أظهر الحل B3
B3. Random reads by position: ArrayList. Oldest first: ArrayDeque as a queue. Newest first: ArrayDeque as a stack. Insert at a node already held: LinkedList (through an iterator), if you really have that situation.
Level C: Challenge
C1. A browser keeps the last 100 visited pages and needs to (a) add a visit, (b) go back, (c) forget the oldest when it holds more than 100. Choose one structure, and say which operations you need on each end.
أظهر الحل C1
C1. An ArrayDeque, used as a deque. Add a visit with push (to the front). Go back with pop from the front. When the size exceeds 100, drop the oldest with pollLast (from the other end). Every operation touches an end, so all are O(1) amortized or better.
C2. A colleague says "I benchmarked LinkedList against ArrayList for inserting at the front, LinkedList won, so we should use it everywhere." List two things wrong with the conclusion.
أظهر الحل C2
C2. First, one benchmark of one operation does not justify "everywhere": a LinkedList is slower at get(i), uses more memory per item, and is not the best queue or stack either. Second, hand-written benchmarks are easy to get wrong because of JVM warm-up, and ArrayDeque (not ArrayList) is the fair comparison for front operations; ArrayList front insertion was always going to lose.
Mini Challenge
Make a one-page cheat sheet with the table from this lesson, from memory, for the operations: get(i), add at end, add at front, remove from front, remove from middle. For each of the three classes write the cost and a six-word reason. Then add three one-line recommendations of the form "If ..., use ... because ...", for a result list, an undo stack, and a job queue.
Check your work: you gave ArrayList as the default for lists and ArrayDeque for both stack and queue, and every cost can be explained with arrays (shifting, copying) or nodes (walking, rewiring).
Summary & Next Lesson
Key takeaways
ArrayList: instantget(i), amortized O(1) append, O(n) at the front and in the middle. The default list.LinkedList: O(1) at the ends and when relinking at a held position, O(n)get(i), extra memory per item. Rarely the best choice.ArrayDeque: O(1) at both ends, no index access. The default stack and queue.- Choose by the operation you do most; use Big O to decide and measure before making speed claims.
- Declare variables as
List,Deque, orQueueand choose the class in one place.
Next lesson
Next is "Demo: building MyArrayList and watching it resize", in which you write a small dynamic array, see a +1 growth mistake, and fix it. It prepares the module's lab.
Self-check
- Which structure is the usual choice for a list you read by index?
- Which is the usual choice for a stack or queue?
- Why is
get(i)on aLinkedListO(n)? - Why does a
get(i)loop over aLinkedListtake O(n²)? - What should you do before saying one structure is faster than another?
أظهر الإجابات
ArrayList.ArrayDeque.- The nodes are scattered, so you must follow references from an end.
- There are n calls and each walks up to n nodes.
- Measure with a proper benchmark on realistic data, and also reason with Big O.
References
- Oracle, Java SE 21 API, java.util.ArrayList. Supports:
getandsizeconstant time,addamortized constant time, other operations roughly linear. - Oracle, Java SE 21 API, java.util.LinkedList. Supports: a doubly linked list implementing
ListandDeque, index operations traverse from the nearer end, andnullelements are allowed. - Oracle, Java SE 21 API, java.util.ArrayDeque. Supports: likely faster than
Stackas a stack andLinkedListas a queue, and null elements prohibited. - Oracle, Java SE 21 API, java.util.Deque. Supports: use of a deque as a stack in preference to the legacy
Stack. - Outputs and the compiler message were produced on JDK 21. The cost table for
ArrayDeque(amortized O(1) at ends) and the claims about memory use, cache friendliness, and thread-safety come from general knowledge and the documentation summaries above; the exact costs ofArrayDequeoperations were not read from a page that states them in Big O terms, so treat them as the standard expectation. Benchmark advice (JMH) is a general recommendation, not verified here.
التطبيق
طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.
تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.