LinkedList 和 ArrayDeque 中队列与栈的实现 对比。

Author : lss 路漫漫其修远兮,不至于代码

LinkedList 的 队列 和 栈

简介

​ 上次我们就提到了过了,LinkedList 即是一个顺序容器,也是一个队列 (queue) 和 栈 (Stack), 本节将对 LinkedList 的队列和栈, 进行刨析。

​ 上个章节已经说过,LinkedList 不仅实现了 List 同时 实现了 Deque 。而 Deque 又实现了 Queue。Queue这个接口不仅继承了来自 Collection 接口的方法之外,同时还提供了一些额外方法。这里分为了两组。一组以抛异常的形式(无数据会抛出异常),另一组以返回值实现(无数据返回 null )

Queue 方法

Throws exception Return value
insert (添加) add ( e ) offer ( e )
Remove(移除) remove poll()
Examine(获取) element() peek()

说完这个 Queue 后 再来看下 Deque 这个接口。 Deque( double ended queue)表示双向队列。由于是双向队列,所以需要对列队的头部和尾部都进行操作。它同时也支持了两种实现方式 一种以抛异常形式,另一种为返回值形式。

Deque 方法

Deque Method Description
addLast (e) 队尾添加
offerLast (e) / offreFirst 队尾添加
removeFirst() / removeLast 获取并删除首元素
pollFirst() / pollLast 获取并删除首元素
getFirst() / getLast 获取但不删除栈顶元素
peekFirst() / peekLast 获取但不删除栈顶元素

LinkedList 中的 Queue

//  因为是队列,所以我们的重点只关心头节点数据。

// 获取方法  
public E peek() {
        final Node<E> f = first;
    	// 头节点为null 返回 null : 返回该节点数据
        return (f == null) ? null : f.item;
}

// 都是获取头节点的数据方法  唯一区别:这个方法会抛出异常
 public E element() {
        return getFirst();
  }

 public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            // 没有数据会抛出异常 
            throw new NoSuchElementException();
        return f.item;
   }

// 删除方法

public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
}

// 若头节点存在数据,将头节点数据,指针置null,将 first 改为后驱节点。  如果后驱节点为null,则都设置为 null。 
private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
    	// 获取当前节点数据
        final E element = f.item;
    	// 获取当前节点的后驱节点
        final Node<E> next = f.next;
    	// 将当前节点数据 置 null
        f.item = null;
    	// 将当前节点的后驱节点 置 null
        f.next = null; // help GC
   		// 头节点指向 当前节点的后驱节点
        first = next;
    	// 后驱节点为 null
        if (next == null)
            last = null;
        else
            // 后驱节点的前驱节点为 null
            next.prev = null;
        size--;
        modCount++;
        return element;
  }



public E remove() {
        return removeFirst();
}

public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
    	// 上面已有解析
        return unlinkFirst(f);
}


// 添加

public boolean offer(E e) {
    // 其实就是一个正常插入方法 上期已经讲过
        return add(e);
}

LinkedList 的 Dueue

    //  队尾加入元素
    public void addLast(E e) {
            // 上期已经解析,不再多说
            linkLast(e);
     }

	// 队尾添加
   public boolean offerLast(E e) {
       	// 也就是正常插入 不在多说。
        addLast(e);
        return true;
    }
	
	//  头部添加 
    public boolean offerFirst(E e) {
            addFirst(e);
            return true;
     }

	// 头部添加 和 尾部添加差不多 。
     private void linkFirst(E e) {
         	// 头节点
            final Node<E> f = first;
         	// 将插入的值包装成 Node
            final Node<E> newNode = new Node<>(null, e, f);
         	// 头节点为 当前插入的节点
            first = newNode;
            if (f == null)
                // 此时只有一个数据 ,既是头节点,也是尾节点
                last = newNode;
            else
                // 将之前的头节的前驱改为 当前插入的节点。
                f.prev = newNode;
            size++;
            modCount++;
    }
//  删除头节点    
//  这里调用的方法 之前都有讲解。所以没有做过多解析
    public E removeFirst() {
            final Node<E> f = first;
            if (f == null)
                throw new NoSuchElementException();
            return unlinkFirst(f);
    }

  
     public E removeLast() {
            final Node<E> l = last;
            if (l == null)
                throw new NoSuchElementException();
            return unlinkLast(l);
     }


     public E pollFirst() {
            final Node<E> f = first;
            return (f == null) ? null : unlinkFirst(f);
     }

	
    public E pollLast() {
            final Node<E> l = last;
            return (l == null) ? null : unlinkLast(l);
    }

// 获取

      public E getFirst() {
            final Node<E> f = first;
            if (f == null)
                throw new NoSuchElementException();
            return f.item;
    }


    public E getLast() {
            final Node<E> l = last;
            if (l == null)
                throw new NoSuchElementException();
            return l.item;
    }


    public E peek() {
            final Node<E> f = first;
            return (f == null) ? null : f.item;
    }	


     public E element() {
            return getFirst();
    }

​ 至此,已经把两种实现方式都展示出来了。这也就是LinkedList 作为队列的一种操作。那接下来看看栈的一些操作吧。

LinkedList 的 Stack

    // 添加。 也就是进栈 
	public void push(E e) {
            addFirst(e);
    }

	// 上面有讲过。所以不写了哦。
    private void linkFirst(E e) {
            final Node<E> f = first;
            final Node<E> newNode = new Node<>(null, e, f);
            first = newNode;
            if (f == null)
                last = newNode;
            else
                f.prev = newNode;
            size++;
            modCount++;
    }



	// 获取并删除
    public E pop() {
            return removeFirst();
    }	
	// 这里也就是我们所说的出栈。 将头节点数据取出,再将头节点改为取出节点的后驱节点
    public E removeFirst() {
            final Node<E> f = first;
            if (f == null)
                throw new NoSuchElementException();
        	// 上面都有哦。。
            return unlinkFirst(f);
    }



	//获取但不删除元素
	public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

​ 至此,LinkedList 中的东西多多少少都已经阐述完了。

总结: LinkedList 既是一个顺序容器 也是一个队列 和 栈。底层是由双向链表组成。不仅实现了 List 还实现了 Deque。 同时Deque 实现了 Queue 。 这两个接口都分别提供了添加,获取,删除。 等操作。分别以两种方式实现。一种以抛异常,另一种为返回值类型。 再作为栈的时候,只有一种方式实现,即抛异常的形式。

​ 上期我们说到过一个东西。不知你还是否记得。 在使用 队列和栈的时候不推荐使用 LinkeList 而是推荐使用ArrayDeque。 从直意上看,是一个双向数组,也就是说数组的两端都可以进行添加和删除元素的操作。即循环数组 Java 中并没有这样的数组。因为Java中的数组是线性的,这里的循环数组表面看是一个数组,但是在逻辑上。他没有数组的固定开头和结尾。即可以在头部添加数据,也可以在尾部添加数据,不需要大面积的挪动数据。

ArrayDeque的实现

在这里插入图片描述

​ 先来看一副图吧,从中发现这个循环数组中多了两个指针 head, tail head指针指向了第一个有效的元素,tail指向的是尾端可插入的元素位置。 因为这个数组是循环的所以。 head 不一定是 0 tail 也不一定比 head 大。ArrayDeque 是不允许 null 存储。

添加

在这里插入图片描述
​从图中观察,在添加元素的时候应该考虑以下几点问题

  • 数组容量是否足够大。
  • head 是否存在下标越界。
//添加

public void addFirst(E e) {
    if (e == null)//不允许放入null
        throw new NullPointerException();
    // 下标是否越界   
    // 假设当前在插入一个元素后 head 为0 在调用一次这个方法, 这个时候head 就为 -1 了。 
    // 这里代表是如果是 -1 的话就对这个数组长度取补数  这里计算head 越界只会是 -1,
    // 若有不懂请自行研究二进制
    elements[head = (head - 1) & (elements.length - 1)] = e;
    // 当两个指针相撞 代表容量已满
    if (head == tail)
        //扩容   这里是进行的 2 倍的扩容
        doubleCapacity();
}

public void addLast(E e) {
    if (e == null)
        throw new NullPointerException();
    // 赋值
    elements[tail] = e;
    // 下标是否越界。   这里和上面的 其实差不多。 
    if ( (tail = (tail + 1) & (elements.length - 1)) == head)
        //扩容
        doubleCapacity();
}

获取并删除 poll


public E pollFirst() {
    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;
}


public E pollLast() {
    //计算tail 的上一个位置
    int t = (tail - 1) & (elements.length - 1);
    E result = elements[t];
    if (result == null)
        return null;
    elements[t] = null;
    tail = t;
    return result;
}
  

返回并不删除 peek

public E peekFirst() {
    return elements[head]; // elements[head] is null if deque empty
}
 
public E peekLast() {
    return elements[(tail - 1) & (elements.length - 1)];
}

总结: ArrayDeque 和 LinkedList 性能差距 ? 为什么选择 ArrayDeque ?

  • ArrayDeque 底层循环数组实现,不容置疑,查询效率肯定高于 LinkedList。

  • 循环数组不会存在删除数据后需要挪动后面的数据。所以摒弃了 数组 删除效率慢的问题

public E peekFirst() {
    return elements[head]; // elements[head] is null if deque empty
}
 
public E peekLast() {
    return elements[(tail - 1) & (elements.length - 1)];
}

总结: ArrayDeque 和 LinkedList 性能差距 ? 为什么选择 ArrayDeque ?

  • ArrayDeque 底层循环数组实现,不容置疑,查询效率肯定高于 LinkedList。

  • 循环数组不会存在删除数据后需要挪动后面的数据。所以摒弃了 数组 删除效率慢的问题

猜你喜欢

转载自blog.csdn.net/weixin_45465895/article/details/106834528
今日推荐