Thinking logic of computer programs (39) and thinking 39
In the previous section, we introduced the ArrayList. the random access efficiency of ArrayList is very high, but the insertion and deletion performance is relatively low. We mentioned that the List interface's parallel List is also implemented, its features are almost the opposite of that of ArrayList. This section describes the feature list in detail.
In addition to the List interface, consumer List also implements the Deque and Queue interfaces, which can be operated by Queue, stack, and dual-end Queue. This section describes these operations, the implementation principle is also introduced.
Let's first look at its usage.
Usage
Constructor
The constructor of the tranquility list is similar to that of ArrayList. One is the default constructor, and the other can accept an existing Collection, as shown below:
public LinkedList()public LinkedList(Collection<? extends E> c)
For example, you can create one as follows:
List<String> list = new LinkedList<>();List<String> list2 = new LinkedList<>( Arrays.asList(new String[]{"a","b","c"}));
List Interface
Similar to ArrayList, The listlist interface also implements the List interface. The List interface extends the Collection interface and the Collection interface extends the Iterable interface. All methods of these interfaces can be used, the usage is the same as described in the previous section.
Queue)
The Queue list also implements the Queue interface Queue, which is similar to various queues in daily life. It features FIFO, adding elements at the end, and deleting elements from the header. Its interface is defined:
public interface Queue<E> extends Collection<E> { boolean add(E e); boolean offer(E e); E remove(); E poll(); E element(); E peek();}
Queue has extended Collection and has three main operations:
- Add (add, offer) elements at the end)
- View the element (peek) of the header, and return the element of the header without changing the queue.
- Delete the Header element (remove, poll), return the Header element, and delete it from the queue.
Each operation has two forms. What is the difference? The difference is that the processing for special cases is different. In special cases, if the queue is empty or the queue is full, it is easy to understand that the queue is full and has a length limit and is full. In the implementation of Queue list, there is no limit on the Queue length, but other Queue implementations may exist.
When the queue is empty, element and remove throw NoSuchElementException, while peek and poll return special null values. When the queue is full, add throws an exception IllegalStateException, offer only returns false.
It is also easy to use the Queue list as a Queue. For example, you can do this:
Queue<String> queue = new LinkedList<>();queue.offer("a");queue.offer("b");queue.offer("c");while(queue.peek()!=null){ System.out.println(queue.poll()); }
Output:
abc
Stack
We introduced the stack when introducing the function call principle. Stack is also a common data structure, which is opposite to the queue. It features first-in-first-out and later-in-first-out, similar to a storage box, when you put a piece, you can only take it from the top.
Java has a class Stack, which is used to indicate the Stack. But this class is outdated and we will not introduce it any more. Java does not have a separate Stack interface, stack-related methods are included in the Deque interface that represents the double-end queue. There are three main methods:
void push(E e);E pop();E peek();
Explanation:
- Push indicates that an element is added to the stack. The stack space may be limited. If the stack is full, push throws an IllegalStateException.
- Pop indicates that the stack is output, the Header element is returned, and deleted from the stack. If the stack is empty, NoSuchElementException is thrown.
- Peek checks the stack Header element without modifying the stack. If the stack is empty, null is returned.
It is easy to use the consumer list as a stack. For example, you can do this:
Deque<String> stack = new LinkedList<>();stack.push("a");stack.push("b");stack.push("c");while(stack.peek()!=null){ System.out.println(stack.pop()); }
Output:
cba
Deque)
Both stack and queue are operated on both ends. Stack only operates on the header and both ends of the queue, but only adds at the end and only views and deletes the header, there is a more common Deque interface at both ends of the operation. Deque extends Queue, including the stack operation method. In addition, it also has the following more explicit methods at both ends of the operation:
void addFirst(E e);void addLast(E e);E getFirst();E getLast();boolean offerFirst(E e);boolean offerLast(E e);E peekFirst();E peekLast();E pollFirst();E pollLast();E removeFirst();E removeLast();
The xxxFirst operation header and the end of the xxxLast operation. Similar to a queue, each operation has two forms. The difference is that when the queue is empty or full, the processing is different. If it is null, getXXX/removeXXX throws an exception, while peekXXX/pollXXX returns null. When the queue is full, addXXX throws an exception. offerXXX only returns false.
Stack and queue are only special cases of dual-end queues. Their methods can be replaced by the dual-end queue method. However, different names and methods are used to make the concept clearer.
The Deque interface also has an iterator method that can be traversed from the back to the front.
Iterator<E> descendingIterator();
For example, read the following code:
Deque<String> deque = new LinkedList<>( Arrays.asList(new String[]{"a","b","c"}));Iterator<String> it = deque.descendingIterator();while(it.hasNext()){ System.out.print(it.next()+" ");}
The output is
c b a
Usage Summary
The usage of consumer List is relatively simple. Similar to the usage of ArrayList, it supports the List interface. However, consumer List adds an interface Deque, which can be viewed as a queue, stack, and dual-end queue, it is convenient to operate on both ends.
If it is only used as a List, should it be used as an ArrayList or an aggregate List? We need to understand the implementation principle of consumer list.
Implementation Principle
Internal components
We know that the ArrayList contains arrays and elements are stored continuously in the memory, but the explain list is not. The literal translation of a linked list is a linked list. Specifically, its internal implementation is a two-way linked list. Each element is stored separately in the memory, and the elements are linked together, similar to hand in hand between children.
In order to represent the link relationship, a node Concept is required. A node includes actual elements, but there are two links at the same time, which respectively refer to the forward node (precursor) and the next node (successor ), A node is an internal class defined:
private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; }}
The Node class indicates the Node, item points to the actual element, next points to the next Node, and prev refers to the previous Node.
The component list contains the following instance variables:
transient int size = 0;transient Node<E> first;transient Node<E> last;
We temporarily ignore the transient keyword. size indicates the length of the linked list. The default value is 0. first points to the header node, last points to the end node, and the initial value is null.
All the public methods in the shortlist perform internal operations on these three instance variables. How do I perform this operation? How is the link maintained? Let's look at some of the main methods. First, let's look at the add method.
Add Method
The code for the add method is:
public boolean add(E e) { linkLast(e); return true;}
The Code of linkLast is as follows:
void linkLast(E e) { final Node<E> l = last; final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++;}
The basic steps of the Code are as follows:
1. Create a new node newNode. Prev points to the original End Node. If the original linked list is empty, it is null. Code:
Node<E> newNode = new Node<>(l, e, null);
2. Modify the last node to point to the new last node newNode. Code:
last = newNode;
3. Modify the backward link of the previous node. If the original linked list is empty, set the header node to the new node. Otherwise, set the next of the previous node to the new node. Code:
if (l == null) first = newNode;else l.next = newNode;
4. Increase the linked list size. Code:
size++
The purpose of modCount ++ is the same as that of ArrayList. It records the number of modifications to facilitate structural change detection during iteration.
Let's take a clearer look at some diagrams. For example, the Code is:
List<String> list = new ArrayList<String>();list.add("a");list.add("b");
After the first line is executed, the internal structure is as follows:
After "a" is added, the internal structure is as follows:
After adding "B", the internal structure is as follows:
It can be seen that, unlike the ArrayList, the memory of the sort list is allocated on demand and no extra memory needs to be allocated in advance. To add elements, you only need to allocate space for new elements, and then adjust several links.
Get Based on the index access element
Element Added. What if the element is accessed Based on the index? Let's take a look at the get method code:
public E get(int index) { checkElementIndex(index); return node(index).item;}
CheckElementIndex checks the validity of the index location. If it is invalid, an exception is thrown. The code is:
private void checkElementIndex(int index) { if (!isElementIndex(index)) throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}private boolean isElementIndex(int index) { return index >= 0 && index < size;}
If the index is valid, call the node method to find the corresponding node, and its item attribute points to the actual element content. The code of the node method is:
Node<E> node(int index) { if (index < (size >> 1)) { Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; }}
Size> 1 is equal to size/2. If the index is located in the first half (index <(size> 1), search from the first node. Otherwise, search from the last node.
It can be seen that, unlike the ArrayList, the array elements in the ArrayList are stored consecutively and can be accessed randomly. In the ArrayList, the elements must be searched from the beginning or end and followed by the link, which is less efficient.
Search for elements based on content
Let's take a look at the code of indexOf:
public int indexOf(Object o) { int index = 0; if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) return index; index++; } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) return index; index++; } } return -1;}
The Code is also very simple. You can find the source node following the link. If you are looking for null, you can find the first node whose item is null. Otherwise, you can use the equals Method for comparison.
Insert element
Add adds an element at the end. What if an element is inserted in the header or in the middle? You can use the following method:
public void add(int index, E element)
Its code is:
public void add(int index, E element) { checkPositionIndex(index); if (index == size) linkLast(element); else linkBefore(element, node(index));}
If the index is size, it is added to the end. Generally, it is inserted before the corresponding node of the index. The call method is linkBefore, and its code is:
void linkBefore(E e, Node<E> succ) { final Node<E> pred = succ.prev; final Node<E> newNode = new Node<>(pred, e, succ); succ.prev = newNode; if (pred == null) first = newNode; else pred.next = newNode; size++; modCount++;}
The succ parameter indicates the successor node. The pred variable indicates the precursor node. The target is to insert a node between pred and succ. The insert steps are as follows:
1. Create a new node newNode with The pred as the precursor and succ as the successor. Code:
Node<E> newNode = new Node<>(pred, e, succ);
2. Point the successor to the new node. Code:
succ.prev = newNode;
3. Point the successor to the new node. If the precursor is empty, modify the header node to point to the new node. Code:
if (pred == null) first = newNode;else pred.next = newNode;
4. Increase the length.
Let's take a clearer look at the figure above, for example, adding an element:
list.add(1, "c");
The graph structure changes:
It can be seen that when an element is inserted in the middle, the memory only needs to be allocated on demand to modify the links of the frontend and successor nodes, while the ArrayList may need to allocate a lot of extra space and move all subsequent elements.
Delete Element
Let's take a look at the deletion element. The code is:
public E remove(int index) { checkElementIndex(index); return unlink(node(index));}
After finding the node through the node method, the unlink method is called. The code is:
E unlink(Node<E> x) { final E element = x.item; final Node<E> next = x.next; final Node<E> prev = x.prev; if (prev == null) { first = next; } else { prev.next = next; x.prev = null; } if (next == null) { last = prev; } else { next.prev = prev; x.next = null; } x.item = null; size--; modCount++; return element;}
The basic idea of deleting an x node is to directly link the x precursor to the successor. next is the successor of x, and prev is the precursor of x. There are two steps:
As shown in the figure above, if you delete an element:
list.remove(1);
The graph structure changes:
Principles
As mentioned above, we have introduced the internal composition of the listing list and the implementation code of several main methods. The principles of other methods are similar, so we will not go into details.
As mentioned above, the queue, stack, and double-end queue interfaces may have limited length. The queue List implements these interfaces, but the length of the queue list is not limited.
Feature Analysis
A two-way linked list is implemented internally. It maintains the length, header node, and tail node, which determines that it has the following features:
- Allocate space on demand, no need to allocate a lot of space in advance
- Random Access is not allowed. The access efficiency by index location is relatively low. You must search the link from the beginning or end, and the efficiency is O (N/2 ).
- No matter whether the list is sorted or not, as long as the elements are searched by content, the efficiency is relatively low and must be compared one by one, and the efficiency is O (N ).
- It is very efficient to add or delete elements at both ends. It is O (1 ).
- To insert or delete an element in the middle, you must first locate it. The efficiency is relatively low. The value is O (N), but the modification efficiency is very high. The efficiency is O (1 ).
After understanding the features of the sequence list and ArrayList, we can easily select the list. If the list length is unknown, there are many addition and deletion operations, especially from the two ends, if access is relatively small Based on the index location, the shortlist is an ideal choice.
Summary
This section describes the rule list in detail, first introduces its usage, and then introduces its implementation principles. Finally, we analyze the features of the rule list and compare it with ArrayList.
In terms of usage, consumer List is a List, but also implements the Deque interface, which can be used as a queue, stack, and double-end queue. In principle, it is a two-way linked list and maintains the length, header node, and tail node.
Whether it's an ArrayList or a content list, the efficiency of searching for elements by content is very low. You need to compare them one by one. Is there a more effective way?
----------------
For more information, see the latest article. Please pay attention to the Public Account "lauma says programming" (scan the QR code below), from entry to advanced, ma and you explore the essence of Java programming and computer technology. Retain All copyrights with original intent.