Java集合--Queue(Java中实现2)

1.1 Deque源码(基于JDK1.7.0_45)

本票中,我们来看看Deque源码,在Queue基础上,又增加了哪些功能?

Deque接口,是一个实现了双端队列数据结构的队列,即在头尾都可进行删除和新增操作;

//接口Deuque:
public interface Deque<E> extends Queue<E> {

    //将指定元素添加到双端队列的头部(如果队列满了,则抛出异常)
    void addFirst(E e);

    //将指定元素添加到双端队列的头部(如果队列满了,则返回fasle)
    boolean offerFirst(E e);

    //将指定元素添加到双端队列的尾部(如果队列满了,则抛出异常)
    void addLast(E e);

    //将指定元素添加到双端队列的尾部(如果队列满了,则返回fasle)
    boolean offerLast(E e);

    //获取并删除该双端队列的第一个元素(如果双端队列为空,则抛出异常)
    E removeFirst();

    //获取并删除双端队列的第一个元素(如果双端队列为空,则返回null)
    E pollFirst();

    //获取并删除该双端队列的最后一个元素(如果双端队列为空,则抛出异常)
    E removeLast();

    //获取并删除该双端队列的最后一个元素(如果双端队列为空,则返回null)
    E pollLast();

    //获取但不删除双端队列的第一个元素(如果双端队列为空,则抛出异常)
    E getFirst();

    //获取但不删除双端队列的第一个元素(如果双端队列为空,则返回null)
    E peekFirst();

    //获取但不删除双端队列的最后一个元素(如果双端队列为空,则抛出异常)
    E getLast();

    //获取但不删除双端队列的最后一个元素(如果双端队列为空,则返回null)
    E peekLast();

    //删除该双端队列的第一次出现的元素o
    boolean removeFirstOccurrence(Object o);

    //删除该双端队列的最后一次出现的元素o
    boolean removeLastOccurrence(Object o);

    //与Queue同理:
    boolean add(E e);
    //与Queue同理:
    boolean offer(E e);
    //与Queue同理:
    E remove();
    //与Queue同理:
    E poll();
    //与Queue同理:
    E element();
    //与Queue同理:
    E peek();
    //与Queue同理:
    boolean remove(Object o);

    模拟数据结构--栈-将元素压入栈顶(向栈顶添加元素)
    void push(E e);

    模拟数据结构--栈-将元素移出栈顶(从栈顶删除元素)
    E pop();

    boolean contains(Object o);

    public int size();

    Iterator<E> iterator();

    Iterator<E> descendingIterator();
}

Deque(Double ended queue),双端队列的缩写。一般队列是能在队头获取(删除)元素,在队尾添加元素,即只有一个进入端,一个输出端。而在Deque中,实现了两个进入端、两个输出端--即可在头部输出也可输入,即可在尾部输出也可在尾部输入。

Deque继承了Queue的所有方法,自然可以当做队列来使用,依旧保持着“先进先出”的本质,在队头移除元素,在队尾添加元素;

但是,Deque还可以被当做“栈”来使用,即“后进先出”,添加元素、删除元素都在队头进行;主要通过push()、pop()两个方法来实现;

通过上面的框架图,可以清楚的看出,Deque接口主要有2个实现类,分别为ArrayDeque、LinkedList;

不过,还有一个并发实现类LinkedBlockingDeque,待我们下一篇幅在做介绍;

LinkedList不用多说,在之前List讲解中已做过介绍,感兴趣的朋友可以去学习下。

本篇主要讲解ArrayDeque,通过名字可以看出,它底层数据结构由数组实现,既然是数组,想必也会实现自动扩容的机制!

1.2 ArrayDeque源码(基于JDK1.7.0_45)

值得一提的是,Java中有一个类叫做Stack,该类实现了栈结构。但是,Java已经不提倡Stack来实现栈结构,而是建议使用ArrayDeque类;

来自Stack类描述:

A more complete and consistent set of LIFO stack operations is 
provided by the {@link Deque} interface and its implementations, which 
should be used in preference to this class.  For example: 
Deque<Integer> stack = new ArrayDeque<Integer>();}</pre>    
@author  Jonathan Payne 
@since   JDK1.0

在看源码前,先给大家介绍下ArrayDeque到底如何添加元素、删除元素的?这样更有利于各位的理解!

在ArrayDeque中主要分为几类方法:

添加:双端队列(头尾都可以增加元素)

插入到队列头部:addFirst--offerFirst--将新增元素插入到数组的最大角标处,依次递减;

插入到队列尾部:addLast--offerLast--将新增元素插入到数组的0角标处,依次递增;

删除:

删除队列头部第一个元素:removeFirst--pollFirst--将元素移除,移除此时head处的元素;

删除队列尾部最后一个元素:removeLast--pollLast--将元素移除,移除此时tail处的元素;

获取:

获取队列头部第一个元素:getFirst--peekFirst     

获取队列尾部最后一个元素:getLast--peekLast

以上方法,在理解时候,切不可将队列的头尾理解成数组的头、尾。队列的头尾值得是head、tail指针;

ArrayDeque主要源码:(进行了删减)

ArrayDeque成员变量和构造方法:

public class ArrayDeque<E> extends AbstractCollection<E>
        implements Deque<E>, Cloneable, Serializable {

    //底层数据结构实现---数组[]
    private transient E[] elements;

    //队列头指针:默认为0
    private transient int head;

    //队列尾指针:默认为0
    private transient int tail;

    //最小初始化容量值:
    private static final int MIN_INITIAL_CAPACITY = 8;

    //扩大数组的长度:扩大为原有长度的2倍;
    private void doubleCapacity() {
        //assert断言修饰符--只有当head==tail时候才会进行扩容操作;
        //(前提是assert开启,否则assert修饰符无效)
        assert head == tail;
        int p = head;
        int n = elements.length;
        int r = n - p; // number of elements to the right of p
        int newCapacity = n << 1;
        if (newCapacity < 0)
            throw new IllegalStateException("Sorry, deque too big");
        Object[] a = new Object[newCapacity];
        //连续调用2次的目的,是为了把原数组中所有的元素全部复制到新数组中(看图说话)
        System.arraycopy(elements, p, a, 0, r);
        System.arraycopy(elements, 0, a, r, p);
        elements = (E[])a;
        head = 0;
        tail = n;
    }

    //默认构造函数:队列的长度默认为16
    public ArrayDeque() {
        elements = (E[]) new Object[16];
    }

    //可设置队列大小的构造函数:
    public ArrayDeque(int numElements) {
        allocateElements(numElements);
    }

    //带集合的构造函数:
    public ArrayDeque(Collection<? extends E> c) {
        allocateElements(c.size());
        addAll(c);
    }

}

ArrayDeque添加元素:

//向队列的头部插入元素:初始时从数组的最大角标处插入;
    public void addFirst(E e) {
        //插入元素不能为null:
        if (e == null)
            throw new NullPointerException();
        //计算插入的角标:  head = (head-1 与运算 数组长度-1)
        elements[head = (head - 1) & (elements.length - 1)] = e;
        //如果头尾指针相同,则进行扩容操作:
        if (head == tail)
            doubleCapacity();
    }

    //向队列的头部插入元素:底层调用addFirst(E e)
    public boolean offerFirst(E e) {
        addFirst(e);
        return true;
    }

    //向队列的末端插入元素:初始时从数组角标为0处插入;
    public void addLast(E e) {
        //插入元素不能为null:
        if (e == null)
            throw new NullPointerException();
        //插入数组中,位置就是尾指针的值;
        elements[tail] = e;
        //判断是否需要进行扩容操作:(tail+1 位运算 数组长度-1)是否与head的值相等
        if ( (tail = (tail + 1) & (elements.length - 1)) == head)
            //扩容操作:
            doubleCapacity();
    }

    //向队列的末端插入元素:底层调用addLast(E e)
    public boolean offerLast(E e) {
        addLast(e);
        return true;
    }

ArrayDeque删除元素:

    //移除队列中第一个元素:移除数组中head指针所指向的元素;
    public E removeFirst() {
        E x = pollFirst();
        //如果队列中没有元素,则抛出异常
        if (x == null)
            throw new NoSuchElementException();
        return x;
    }

    //移除队列头中的元素,实际就是移除数组中head指针所指向的元素;
    public E pollFirst() {
        //获取头指针:
        int h = head;
        //获取头指针所处的数组角标元素:
        E result = elements[h]; // Element is null if deque empty
        //如果头指针为null,说明队列中没有元素存在,直接返回;
        if (result == null)
            return null;
        //将头指针所指向数组角标置为null:
        elements[h] = null;     // Must null out slot
        //修改头指针大小 = 头指针+1 & 数组长度-1
        //实际上是将现有头指针+1。
        head = (h + 1) & (elements.length - 1);
        //返回被删除的对象:
        return result;
    }

    //移除队列中最后一个元素:移除数组中tail指针所指向的元素;
    public E removeLast() {
        E x = pollLast();
        //如果队列中没有元素,则抛出异常
        if (x == null)
            throw new NoSuchElementException();
        return x;
    }

    //移除队列尾中的元素,实际就是移除数组中tail指针所指向的元素;
    public E pollLast() {
        //获取要移除元素的数组角标:如果tail为0,则计算出的t值为数组的最大角标(首尾相连了)
        int t = (tail - 1) & (elements.length - 1);
        //获取要移除的数组元素:
        E result = elements[t];
        //如果为null,则直接返回;
        if (result == null)
            return null;
        //将对应数组角标的元素置为null:
        elements[t] = null;
        //修改tail指针的值:
        tail = t;
        return result;
    }

ArrayDeque获取元素:

    //得到队列中的第一个元素,也就是头指针所指向的元素;
    public E getFirst() {
        E x = elements[head];
        //为空的话,抛出异常;
        if (x == null)
            throw new NoSuchElementException();
        return x;
    }

    //得到队列中的第一个元素,队列空的话返回null
    public E peekFirst() {
        return elements[head]; // elements[head] is null if deque empty
    }

    //得到队列中的最后一个元素,也就是尾指针所指向的元素;
    public E getLast() {
        E x = elements[(tail - 1) & (elements.length - 1)];
        if (x == null)
            throw new NoSuchElementException();
        return x;
    }

    //得到队列中的最后一个元素,队列空的话返回null
    public E peekLast() {
        return elements[(tail - 1) & (elements.length - 1)];
    }

ArrayDeque实现队列的方法:

    //队列方法:
    //向队列中添加元素:添加到队列头部
    public boolean add(E e) {
        addLast(e);
        return true;
    }

    //向队列中添加元素:添加到队列末尾
    public boolean offer(E e) {
        return offerLast(e);
    }

    //移除队列中元素,移除队列头部元素,为null抛出异常;
    public E remove() {
        return removeFirst();
    }

    //移除队列中元素,移除队列头部元素,可以为null;
    public E poll() {
        return pollFirst();
    }

    //获取队列头部的元素:如果为null抛出异常;
    public E element() {
        return getFirst();
    }

    //获取队列头部的元素:如果为null,就返回;
    public E peek() {
        return peekFirst();
    }

ArrayDeque实现栈的方法:

    //栈方法:
    //向栈顶压入对象:向数组最大角标处插入对象;
    public void push(E e) {
        addFirst(e);
    }

    //将栈顶对象出栈,移除head指针所属的元素;
    public E pop() {
        return removeFirst();
    }

此外,ArrayDeque利用了push/pop方法,实现了栈结构,栈是一种“后进先出”的数据模型。

在ArrayDeque中,底层主要利用addFirst/removeFirst实现元素的出栈和入栈操作;

1.3 LinkedList源码(基于JDK1.7.0_45)

LinkedList即有List集合方法,又有Deque集合方法。

具体解析,请参考笔者之前的文章。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • Queue Queue继承自 Collection,我们先来看看类结构吧,代码量比较少,我直接贴代码了 从方法名上...
    Anonymous___阅读 845评论 0 1
  • 今天我们来介绍下集合Queue中的几个重要的实现类。关于集合Queue中的内容就比较少了。主要是针对队列这种数据结...
    Ruheng阅读 8,472评论 4 27
  • 栈 栈的英文单词是Stack,它代表一种特殊的线性表,这种线性表只能在固定一端(通常认为是线性表的尾端)进行插入,...
    Jack921阅读 1,438评论 0 5
  • 以下是《疯狂Java讲义》中的一些知识,如有错误,烦请指正。 集合概述 Java集合可以分为Set、List、Ma...
    hainingwyx阅读 521评论 0 1
  • 很多事,可以借力。比如: 1.留言入选,可以在猫群里求赞,可以在朋友圈求赞。 2.以前会觉得让别人点赞会觉得不好意...
    映月黑珍珠阅读 146评论 0 1