[Collection - Stack & Queue source code analysis]

This article mainly analyzes the source code of Collection - Stack & Queue.

 Stack & Queue Overview

There is a class called Stack in Java, but there is no class called Queue< a i=4> class (it is an interface name). When you need to use the stack, Java no longer recommends using Stack, but recommends using the more efficient ArrayDeque). LinkedList (the second choice is ArrayDeque is just an interface, when you need to use a queue, it is the first choiceQueue; Since

 Queue

The Queue interface inherits from the Collection interface. In addition to the most basic Collection methods, it also supports additionalinsertion , extraction and inspection operations. There are two sets of formats, a total of 6 methods, one is the implementation of throwing exceptions; the other is the implementation of return values ​​(if not, return null).

Throws exception Returns special value
Insert add(e) offer(e)
Remove remove() poll()
Examine element() peek()

 Therefore

Deque is a "double ended queue", which means a two-way queue, and is pronounced "deck" in English. Deque inherits from the Queue interface. In addition to supporting Queue methods, it also supports insert, remove and examine operations. Since Deque is bidirectional, it can operate on both the head and tail of the queue. It also supports two sets of formats, one One group is the implementation that throws exceptions; the other group is the implementation that returns values ​​(if not, null is returned). A total of 12 methods are as follows:

First Element - Head Last Element - Tail
Throws exception Special value Throws exception Special value
Insert addFirst(e) offerFirst addLast(e) offer load(s)
Remove removeFirst() pollFirst() removeLast() pollLast()
Examine getFirst() peekFirst() getLast() peekLast()

When Deque is used as FIFO's queue, elements are added from the end of deque, from the beginning Partial deletion is performed; therefore, some methods of deque are equivalent to queue. The details are as follows:

Queue Method Equivalent Deque Method
add(e) addLast(e)
offer(e) offer load(s)
remove() removeFirst()
poll() pollFirst()
element() getFirst()
peek() peekFirst()

Deque means "double ended queue", which is a double-ended queue. It can be used as a stack or a queue. The following table lists the interfaces corresponding to Deque and Queue:< /span>

Queue Method Equivalent Deque Method illustrate
add(e) addLast(e) Insert an element to the end of the queue and throw an exception if it fails.
offer(e) offerLast(e) Insert an element to the end of the queue and return if failed.false
remove() removeFirst() Get and delete the first element of the team, and throw an exception if it fails.
poll() pollFirst() Get and delete the first element of the team, return if failednull
element() getFirst() Gets but does not delete the first element of the queue, and throws an exception if it fails.
peek() peekFirst() Get but not delete the first element of the team, return if failednull

The following table lists the correspondence between Deque and Stack Interface:

Stack Method Equivalent Deque Method illustrate
push(e) addFirst(e) Insert an element to the top of the stack, throwing an exception if it fails.
none offerFirst(e) Insert an element to the top of the stack and return if failed.false
pop() removeFirst() Get and delete the top element of the stack, throwing an exception if it fails.
none pollFirst() Get and delete the top element of the stack, return if failednull
peek() getFirst() Gets but does not delete the top element of the stack, and throws an exception if it fails.
none peekFirst() Get but do not delete the top element of the stack, return if failednull

The above two tables define a total of 12 interfaces ofDeque. There are two sets of interfaces for adding, deleting, and getting values. They have the same functions. The difference is the handling of failure situations. One set of interfaces will throw an exception when encountering a failure, and the other set will return a special value (false or null). Unless an implementation has capacity constraints, in most cases the add operation will not fail. Although Deque has as many as 12 interfaces, they are nothing more than operating on both ends of the container, or adding , or delete, or view. Once you understand this, it will be very simple to explain.

ArrayDeque and LinkedList areDeque Two common implementations of , since the official recommendation is to use AarryDeque as a stack and queue, and it has been explained in the previous article a>LinkedList, this article will focus on the specific implementation of ArrayDeque.

It can be seen from the nameArrayDequeThe bottom layer is implemented through an array. In order to meet the requirement of inserting or deleting elements at both ends of the array at the same time, this The array must also be cyclic, that is, circular array (circular array), which means that any point of the array may be regarded as the starting point or the end point. ArrayDeque is not thread-safe. When multiple threads are used at the same time, the programmer needs to manually synchronize; in addition, the container does not null elements are allowed.

As we can see in the picture above, head points to the first valid element at the beginning, and tail points to the first empty space at the end where an element can be inserted. . Because it is a cyclic array, head may not always be equal to 0, and tail may not always be larger than head.

 Method analysis

 addFirst()

addFirst(E e)The function of is to insert elements at the beginning of Deque, that is, to insert elements in front of head , if there is enough space and the subscript does not cross the boundary, you only need to change elements[--head] = e.

Actual considerations include: 1. Whether there is enough space, and 2. Whether the subscript is out of bounds. In the above figure, if head is 0 and then calls addFirst(), although the free space is still enough, < a i=4> is , and the subscript is out of bounds. The following code solves these two problems very well. head-1

//addFirst(E e)
public void addFirst(E e) {
    if (e == null)//不允许放入null
        throw new NullPointerException();
    elements[head = (head - 1) & (elements.length - 1)] = e;//2.下标是否越界
    if (head == tail)//1.空间是否够用
        doubleCapacity();//扩容
}

We see in the above code that the space problem is solved after insertion because tail is always Points to the next insertable slot, which means that theelements array has at least one slot, so there is no need to consider space issues when inserting elements.

The solution to out-of-bounds subscripts is very simple.head = (head - 1) & (elements.length - 1) is enough.This code is equivalent to taking the remainder and solves the problem at the same time When a>head is a negative value. Because elements.length must be an exponential multiple of 2, elements - 1 is the binary low-order full 1, followed by < /span> The complement of a>. is a negative number (actually it can only be -1), it is equivalent to taking the relative valuehead - 1 plays the role of modulo after being ANDed. Ifhead - 1elements.length

Let’s talk about the expansion functiondoubleCapacity(). Its logic is to apply for a larger array (twice the original array), and then copy the original array.

We see in the picture that the copying is done in two steps, the first time copying the elements on the right side ofhead, and the second time copying the elements on the left side ofhead .

//doubleCapacity()
private void doubleCapacity() {
    assert head == tail;
    int p = head;
    int n = elements.length;
    int r = n - p; // head右边元素的个数
    int newCapacity = n << 1;//原空间的2倍
    if (newCapacity < 0)
        throw new IllegalStateException("Sorry, deque too big");
    Object[] a = new Object[newCapacity];
    System.arraycopy(elements, p, a, 0, r);//复制右半部分,对应上图中绿色部分
    System.arraycopy(elements, 0, a, r, p);//复制左半部分,对应上图中灰色部分
    elements = (E[])a;
    head = 0;
    tail = n;
}

 addLast()

addLast(E e)The function of is to insert elements at the end of Deque, that is, to insert elements at the position of tail , since tail always points to the next empty position that can be inserted, so only elements[tail] = e; is needed. After the insertion is completed, check the space again. If the space is used up, call doubleCapacity() for expansion.

public void addLast(E e) {
    if (e == null)//不允许放入null
        throw new NullPointerException();
    elements[tail] = e;//赋值
    if ( (tail = (tail + 1) & (elements.length - 1)) == head)//下标越界处理
        doubleCapacity();//扩容
}

The subscript out-of-bounds processing method has been discussed in addFirt() and will not be repeated.

 pollFirst()

pollFirst()The function of is to delete and return the head element of Deque, that is, the element at position head . If the container is not empty, just return elements[head] directly. Of course, you also need to deal with the subscript issue. Since is not allowed in ArrayDeque, when , it means that the container is empty. nullelements[head] == null

public E pollFirst() {
    int h = head;
    E result = elements[head];
    if (result == null)//null值意味着deque为空
        return null;
    elements[h] = null;//let GC work
    head = (head + 1) & (elements.length - 1);//下标越界处理
    return result;
}

 pollLast()

pollLast()The function of is to delete and return the element at the end of Deque, that is, the one before the position tail element.

public E pollLast() {
    int t = (tail - 1) & (elements.length - 1);//tail的上一个位置是最后一个元素
    E result = elements[t];
    if (result == null)//null值意味着deque为空
        return null;
    elements[t] = null;//let GC work
    tail = t;
    return result;
}

 peekFirst()

peekFirst()The function of is to return but not delete the Deque head element, that is, the element at the head position element, just return elements[head] directly.

public E peekFirst() {
    return elements[head]; // elements[head] is null if deque empty
}

 peekLast()

peekLast()The function of is to return but not delete the element at the end of Deque, that is, the element before the position tail That element.

public E peekLast() {
    return elements[(tail - 1) & (elements.length - 1)];
}

Guess you like

Origin blog.csdn.net/abclyq/article/details/134745281