查看: 22264|回复: 70090
打印 上一主题 下一主题

JAVA提高十一:LinkedList深入分析

[复制链接]
天尊娱乐最高负责人
跳转到指定楼层
楼主
发表于 2019-04-21 01:58:54 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

王妙想见许飞琼脸色金白,连忙掀开她的衣袖,见她那玉藕般的手臂上已溢出脓血,赶紧用剑破开皮肉,小心翼翼地将嵌入骨中的那枚太阳神针取出,又让她服下随身携带的仙汁。这太阳神针能破人玄功,伤人体魄,幸好许飞琼已证仙体,又在中针之时立即用真气逼住,否则它早已逆经脉而上,直接攻入心脏。

龙虎和开奖数字怎么算

“你们一个个的眼睛都瞎了?”守将从地上跳起来,这个时候屁股也不疼了,脑袋都要没了,谁还管屁股。
没有料到这么秘密的消息都会泄露的娜塔尔巴基露露等人遭遇到袭击的时候所有机师等死亡了,只有娜塔尔巴基露露一个幸存,高达也被夺取了四架,不过幸好最后娜塔尔巴基露露和及时赶到的玛琉联手将最后一架高达保住,但是他们两人都负伤了。

“那我便来试试!”钱诺冷哼一声,手中的太清神符放出一道清光,中央的那座山峰之上立刻便飞出一道如同长桥一般的清光将钱诺接引到了镇压孔雀明王的地方。

上一节,我们学习了ArrayList 类,本节我们来学习一下LinkedList,LinkedList相对ArrayList而言其使用频率并不是很高,因为其访问元素的性能相对于ArrayList而言比较慢,至于原因我们下面讲开始讲解,本节重点是了解其内部的结构,会简单实现一个简单的LinkedList 即可。

一、LinkedList的简单使用

任何代码在深入分析前,首先需要会使用,因此我们先看下基本的使用列子:

package study.collection;

import java.util.LinkedList;


public class TestLinkedList {
    
    public static void main(String[] args)
    {
        // 测试LinkedList的API
        testLinkedListAPIs() ;

        // 将LinkedList当作 LIFO(后进先出)的堆栈
        useLinkedListAsLIFO();

        // 将LinkedList当作 FIFO(先进先出)的队列
        useLinkedListAsFIFO();
    }
    
    /*
     * 测试LinkedList中部分API
     */
    private static void testLinkedListAPIs() {
        String val = null;
        //LinkedList llist;
        //llist.offer("10");
        // 新建一个LinkedList
        LinkedList llist = new LinkedList();
        //---- 添加操作 ----
        // 依次添加1,2,3
        llist.add("1");
        llist.add("2");
        llist.add("3");

        // 将“4”添加到第一个位置
        llist.add(1, "4");
        

        System.out.println("
Test "addFirst(), removeFirst(), getFirst()"");
        // (01) 将“10”添加到第一个位置。  失败的话,抛出异常!
        llist.addFirst("10");
        System.out.println("llist:"+llist);
        // (02) 将第一个元素删除。        失败的话,抛出异常!
        System.out.println("llist.removeFirst():"+llist.removeFirst());
        System.out.println("llist:"+llist);
        // (03) 获取第一个元素。          失败的话,抛出异常!
        System.out.println("llist.getFirst():"+llist.getFirst());


        System.out.println("
Test "offerFirst(), pollFirst(), peekFirst()"");
        // (01) 将“10”添加到第一个位置。  返回true。
        llist.offerFirst("10");
        System.out.println("llist:"+llist);
        // (02) 将第一个元素删除。        失败的话,返回null。
        System.out.println("llist.pollFirst():"+llist.pollFirst());
        System.out.println("llist:"+llist);
        // (03) 获取第一个元素。          失败的话,返回null。
        System.out.println("llist.peekFirst():"+llist.peekFirst());
    

        System.out.println("
Test "addLast(), removeLast(), getLast()"");
        // (01) 将“20”添加到最后一个位置。  失败的话,抛出异常!
        llist.addLast("20");
        System.out.println("llist:"+llist);
        // (02) 将最后一个元素删除。        失败的话,抛出异常!
        System.out.println("llist.removeLast():"+llist.removeLast());
        System.out.println("llist:"+llist);
        // (03) 获取最后一个元素。          失败的话,抛出异常!
        System.out.println("llist.getLast():"+llist.getLast());


        System.out.println("
Test "offerLast(), pollLast(), peekLast()"");
        // (01) 将“20”添加到第一个位置。  返回true。
        llist.offerLast("20");
        System.out.println("llist:"+llist);
        // (02) 将第一个元素删除。        失败的话,返回null。
        System.out.println("llist.pollLast():"+llist.pollLast());
        System.out.println("llist:"+llist);
        // (03) 获取第一个元素。          失败的话,返回null。
        System.out.println("llist.peekLast():"+llist.peekLast());

         

        // 将第3个元素设置300。不建议在LinkedList中使用此操作,因为效率低!
        llist.set(2, "300");
        // 获取第3个元素。不建议在LinkedList中使用此操作,因为效率低!
        System.out.println("
get(3):"+llist.get(2));


        // ---- toArray(T[] a) ----
        // 将LinkedList转行为数组
        String[] arr = (String[])llist.toArray(new String[0]);
        for (String str:arr) 
            System.out.println("str:"+str);

        // 输出大小
        System.out.println("size:"+llist.size());
        // 清空LinkedList
        llist.clear();
        // 判断LinkedList是否为空
        System.out.println("isEmpty():"+llist.isEmpty()+"
");

    }

    /**
     * 将LinkedList当作 LIFO(后进先出)的堆栈
     */
    private static void useLinkedListAsLIFO() {
        System.out.println("
useLinkedListAsLIFO");
        // 新建一个LinkedList
        LinkedList stack = new LinkedList();

        // 将1,2,3,4添加到堆栈中
        stack.push("1");
        stack.push("2");
        stack.push("3");
        stack.push("4");
        // 打印“栈”
        System.out.println("stack:"+stack);

        // 删除“栈顶元素”
        System.out.println("stack.pop():"+stack.pop());
        
        // 取出“栈顶元素”
        System.out.println("stack.peek():"+stack.peek());

        // 打印“栈”
        System.out.println("stack:"+stack);
    }

    /**
     * 将LinkedList当作 FIFO(先进先出)的队列
     */
    private static void useLinkedListAsFIFO() {
        System.out.println("
useLinkedListAsFIFO");
        // 新建一个LinkedList
        LinkedList queue = new LinkedList();

        // 将10,20,30,40添加到队列。每次都是插入到末尾
        queue.add("10");
        queue.add("20");
        queue.add("30");
        queue.add("40");
        // 打印“队列”
        System.out.println("queue:"+queue);

        // 删除(队列的第一个元素)
        System.out.println("queue.remove():"+queue.remove());
    
        // 读取(队列的第一个元素)
        System.out.println("queue.element():"+queue.element());

        // 打印“队列”
        System.out.println("queue:"+queue);
    }
}

 从上面的代码可以看出,LinkedList的功能非常多,既可以用于存放元素的集合功能,还具备了堆栈的功能,还拥有队列的功能。这均源于其不仅仅实现了List接口还是实现了Queue接口。

但在介绍LinkedList接口前,从名字可以看出LinkedList 即底层采用的是链表的结构,那什么是链表,需要提前有一个认识:顾名思义,链表就和链子一样,每一环都要连接着后边的一环和前边的一环,这样,当我们需要找这根链子的某一环的时候,只要我们能找到链子的任意一环,都可以找到我们需要的那一环。我们看一个图,就能很好的理解了。

在LinkedList中,我们把链子的“环”叫做“节点”,每个节点都是同样的结构。节点与节点之间相连,构成了我们LinkedList的基本数据结构,也是LinkedList的核心。链表又分为单向链表和双向链表,而单向/双向链表又可以分为循环链表和非循环链表,下面简单就这四种链表进行图解说明。

 1.1.单向链表
          单向链表就是通过每个结点的指针指向下一个结点从而链接起来的结构,最后一个节点的next指向null。

 

1. 2.单向循环链表

  单向循环链表和单向列表的不同是,最后一个节点的next不是指向null,而是指向head节点,形成一个“环”。

 1. 3.双向链表

  从名字就可以看出,双向链表是包含两个指针的,pre指向前一个节点,next指向后一个节点,但是第一个节点head的pre指向null,最后一个节点的tail指向null。

1. 4.双向循环链表
          双向循环链表和双向链表的不同在于,第一个节点的pre指向最后一个节点,最后一个节点的next指向第一个节点,也形成一个“环”。而LinkedList就是基于双向循环链表设计的。

更形象的解释下就是:双向循环链表就像一群小孩手牵手围成一个圈,第一个小孩的右手拉着第二个小孩的左手,第二个小孩的左手拉着第一个小孩的右手。。。最后一个小孩的右手拉着第一个小孩的左手。【接下来我们进入源代码的分析,说明linkedlist 我们采用的JDK1.8进行分析,只所以不用1.6是因为,在1.6  1.7 的时候进行过变动将1.6中的环形结构优化为了直线型了链表结构,然后回到了1.8又有部分的变动,因为从上面可以看出链表的由4种不向ArrayList单一演进,所以这里直接选择用1.8来分析】

 二、LinkedList的概述

LinkedList是List和Deque接口的双向链表的实现。实现了所有可选列表操作,并允许包括null值。

LinkedList既然是通过双向链表去实现的,那么它可以被当作堆栈、队列或双端队列进行操作。并且其顺序访问非常高效,而随机访问效率比较低。

注意,此实现不是同步的。 如果多个线程同时访问一个LinkedList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。这通常是通过同步那些用来封装列表的 对象来实现的。但如果没有这样的对象存在,则该列表需要运用{@link Collections#synchronizedList Collections.synchronizedList}来进行“包装”,该方法最好是在创建列表对象时完成,为了避免对列表进行突发的非同步操作。

List list = Collections.synchronizedList(new LinkedList(...));

类中的iterator()方法和listIterator()方法返回的iterators迭代器是fail-fast的:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

1.Node节点

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;
        }
    }

2.LinkedList类结构

//通过LinkedList实现的接口可知,其支持队列操作,双向列表操作,能被克隆,支持序列化
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    // LinkedList的大小(指其所含的元素个数)
    transient int size = 0;

    /**
     * 指向第一个节点
     * 不变的: (first == null && last == null) ||
     *            (first.prev == null && first.item != null)
     */
    transient Node<E> first;

    /**
     * 指向最后一个节点
     * 不变的: (first == null && last == null) ||
     *            (last.next == null && last.item != null)
     */
    transient Node<E> last;

    ......
}

 

LinkedList包含了三个重要的对象first、last 和 size。

(1) first 是双向链表的表头,它是双向链表节点所对应的类Node的实例

(2) last 是双向链表的最后一个元素,它是双向链表节点所对应的类Node的实例

(3) size 是双向链表中节点的个数。

3.构造函数

LinkedList提供了两种种方式的构造器,构造一个空列表、以及构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回的顺序排列的。

//构建一个空列表
    public LinkedList() {
    }

    /**
     * 构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回的顺序排列的
     * @param  c 包含用于去构造LinkedList的元素的collection
     * @throws NullPointerException 如果指定的collection为空
     */
    //构建一个包含指定集合c的列表
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

三、LinkedList 方法功能源码分析

/**
 * LinkedList底层使用双向链表,实现了List和deque。实现所有的可选List操作,并可以只有所有元素(包括空值)
 * 其大小理论上仅受内存大小的限制
 *
 * 所有的操作都可以作为一个双联列表来执行(及对双向链表操作)。
 * 把对链表的操作封装起来,并对外提供看起来是对普通列表操作的方法。
 * 遍历从起点、终点、或指定位置开始
 * 内部方法,注释会描述为节点的操作(如删除第一个节点),公开的方法会描述为元素的操作(如删除第一个元素)
 *
 * LinkedList不是线程安全的,如果在多线程中使用(修改),需要在外部作同步处理。
 * 
 * 需要弄清元素(节点)的索引和位置的区别,不然有几个地方不好理解,具体在碰到的地方会解释。
 * 
 * 迭代器可以快速报错
 */
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    //容量
    transient int size = 0;
    //首节点
    transient Node<E> first;
    //尾节点
    transient Node<E> last;
    //默认构造函数
    public LinkedList() {
    }
    //通过一个集合初始化LinkedList,元素顺序有这个集合的迭代器返回顺序决定
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }
    //使用对应参数作为第一个节点,内部使用
    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;     //如果之前首节点为空(size==0),那么尾节点就是首节点
        else
            f.prev = newNode;   //如果之前首节点不为空,之前的首节点的前一个节点为当前首节点
        size++;                 //长度+1
        modCount++;             //修改次数+1
    }
    //使用对应参数作为尾节点
    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;    //如果之前尾节点为空(size==0),首节点即尾节点
        else
            l.next = newNode;   //如果之前尾节点不为空,之前的尾节点的后一个就是当前的尾节点
        size++;
        modCount++;
    }
    //在指定节点前插入节点,节点succ不能为空
    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;//如果前一个节点为null,新的节点就是首节点
        else
            pred.next = newNode;//如果存在前节点,那么前节点的向后指向新节点
        size++;
        modCount++;
    }
    //删除首节点并返回删除前首节点的值,内部使用
    private E unlinkFirst(Node<E> f) {
        final E element = f.item;//获取首节点的值
        final Node<E> next = f.next;//得到下一个节点
        f.item = null;
        f.next = null;      //便于垃圾回收期清理
        first = next;       //首节点的下一个节点成为新的首节点
        if (next == null)
            last = null;    //如果不存在下一个节点,则首尾都为null(空表)
        else
            next.prev = null;//如果存在下一个节点,那它向前指向null
        size--;
        modCount++;
        return element;
    }
    //删除尾节点并返回删除前尾节点的值,内部使用
    private E unlinkLast(Node<E> l) {
        final E element = l.item;//获取值
        final Node<E> prev = l.prev;//获取尾节点前一个节点
        l.item = null;
        l.prev = null;      //便于垃圾回收期清理
        last = prev;        //前一个节点成为新的尾节点
        if (prev == null)
            first = null;   //如果前一个节点不存在,则首尾都为null(空表)
        else
            prev.next = null;//如果前一个节点存在,先后指向null
        size--;
        modCount++;
        return element;
    }
    //删除指定节点并返回被删除的元素值
    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;  //方便gc回收
        }
        if (next == null) {
            last = prev;    //如果后一个节点为空(如当前节点为尾节点),当前节点前一个成为新的尾节点
        } else {
            next.prev = prev;//如果后一个节点不为空,后一个节点向前指向当前的前一个节点
            x.next = null;  //方便gc回收
        }
        x.item = null;      //方便gc回收
        size--;
        modCount++;
        return element;
    }
    //获取第一个元素
    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 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 void addFirst(E e) {
        linkFirst(e);
    }
    //店家元素作为最后一个元素
    public void addLast(E e) {
        linkLast(e);
    }
    //检查是否包含某个元素,返回bool
    public boolean contains(Object o) {
        return indexOf(o) != -1;//返回指定元素的索引位置,不存在就返回-1,然后比较返回bool值
    }
    //返回列表长度
    public int size() {
        return size;
    }
    //添加一个元素,默认添加到末尾作为最后一个元素
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    //删除指定元素,默认从first节点开始,删除第一次出现的那个元素
    public boolean remove(Object o) {
        //会根据是否为null分开处理。若值不是null,会用到对象的equals()方法
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }
    //添加指定集合的元素到列表,默认从最后开始添加
    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);//size表示最后一个位置,可以理解为元素的位置分别为1~size
    }
    //从指定位置(而不是下标!下标即索引从0开始,位置可以看做从1开始,其实也是0)后面添加指定集合的元素到列表中,只要有至少一次添加就会返回true
    //index换成position应该会更好理解,所以也就是从索引为index(position)的元素的前面索引为index-1的后面添加!
    //当然位置可以为0啊,为0的时候就是从位置0(虽然它不存在)后面开始添加嘛,所以理所当前就是添加到第一个位置(位置1的前面)的前面了啊!
    //比如列表:0 1 2 3,如果此处index=4(实际索引为3),就是在元素3后面添加;如果index=3(实际索引为2),就在元素2后面添加。
    //原谅我的表达水平,我已经尽力解释了...
    public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index);  //检查索引是否正确(0<=index<=size)
        Object[] a = c.toArray();   //得到元素数组
        int numNew = a.length;      //得到元素个数
        if (numNew == 0)            //若没有元素要添加,直接返回false
            return false;
        Node<E> pred, succ;
        if (index == size) {    //如果是在末尾开始添加,当前节点后一个节点初始化为null,前一个节点为尾节点
            succ = null;        //这里可以看做node(index),不过index=size了(index最大只能是size-1),所以这里的succ只能=null,也方便后面判断
            pred = last;        //这里看做noede(index-1),当然实现是不能这么写的,看做这样只是为了好理解,所以就是在node(index-1的后面开始添加元素)
        } else {                //如果不是从末尾开始添加,当前位置的节点为指定位置的节点,前一个节点为要添加的节点的前一个节点
            succ = node(index); //添加好元素后(整个新加的)的后一个节点
            pred = succ.prev;   //这里依然是node(index-1)
        }
        //遍历数组并添加到列表中
        for (Object o : a) {
            @SuppressWarnings("unchecked")
            E e = (E) o;
            Node<E> newNode = new Node<>(pred, e, null);//创建一个节点,向前指向上面得到的前节点
            if (pred == null)
                first = newNode;    //若果前节点为null,则新加的节点为首节点
            else
                pred.next = newNode;//如果存在前节点,前节点会向后指向新加的节点
            pred = newNode;         //新加的节点成为前一个节点
        }
        if (succ == null) {
            //pred.next = null  //加上这句也可以更好的理解
            last = pred;        //如果是从最后开始添加的,则最后添加的节点成为尾节点
        } else {
            pred.next = succ;   //如果不是从最后开始添加的,则最后添加的节点向后指向之前得到的后续第一个节点
            succ.prev = pred;   //当前,后续的第一个节点也应改为向前指向最后一个添加的节点
        }
        size += numNew;
        modCount++;
        return true;
    }
    //清空表
    public void clear() {
        //方便gc回收垃圾
        for (Node<E> x = first; x != null; ) {
            Node<E> next = x.next;
            x.item = null;
            x.next = null;
            x.prev = null;
            x = next;
        }
        first = last = null;
        size = 0;
        modCount++;
    }
    //获取指定索引的节点的值
    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
    //修改指定索引的值并返回之前的值
    public E set(int index, E element) {
        checkElementIndex(index);
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        return oldVal;
    }
    //指定位置后面(即索引为这个值的元素的前面)添加元素
    public void add(int index, E element) {
        checkPositionIndex(index);
        if (index == size)
            linkLast(element);  //如果指定位置为最后,则添加到链表最后
        else                    //如果指定位置不是最后,则添加到指定位置前
            linkBefore(element, node(index));
    }
    //删除指定位置的元素,
    public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }
    //检查索引是否超出范围,因为元素索引是0~size-1的,所以index必须满足0<=index<size
    private boolean isElementIndex(int index) {
        return index >= 0 && index < size;
    }
    //检查位置是否超出范围,index必须在index~size之间(含),如果超出,返回false
    private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }
    //异常详情
    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }
    //检查元素索引是否超出范围,若已超出,就抛出异常
    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    //检查位置是否超出范围,若已超出,就抛出异常
    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    //获取指定位置的节点
    Node<E> node(int index) {
        //如果位置索引小于列表长度的一半(或一半减一),从前面开始遍历;否则,从后面开始遍历
        if (index < (size >> 1)) {
            Node<E> x = first;//index==0时不会循环,直接返回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;
        }
    }
    //获取指定元素从first开始的索引位置,不存在就返回-1
    //不能按条件双向找了,所以通常根据索引获得元素的速度比通过元素获得索引的速度快
    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;
    }
    //获取指定元素从first开始最后出现的索引,不存在就返回-1
    //但实际查找是从last开始的
    public int lastIndexOf(Object o) {
        int index = size;
        if (o == null) {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (x.item == null)
                    return index;
            }
        } else {
            for (Node<E> x = last; x != null; x = x.prev) {
                index--;
                if (o.equals(x.item))
                    return index;
            }
        }
        return -1;
    }
    //提供普通队列和双向队列的功能,当然,也可以实现栈,FIFO,FILO
    //出队(从前端),获得第一个元素,不存在会返回null,不会删除元素(节点)
    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }
    //出队(从前端),不删除元素,若为null会抛出异常而不是返回null
    public E element() {
        return getFirst();
    }
    //出队(从前端),如果不存在会返回null,存在的话会返回值并移除这个元素(节点)
    public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }
    //出队(从前端),如果不存在会抛出异常而不是返回null,存在的话会返回值并移除这个元素(节点)
    public E remove() {
        return removeFirst();
    }
    //入队(从后端),始终返回true
    public 龙虎合平台 小赌怡情 牛牛游戏规则介绍 t6娱乐官方网站|t6娱乐平台登录|t6娱乐平台手机版 澳门永利集团 澳门正规博彩50大网站_澳门正规博彩_欢迎您访问 巴黎人官网_巴黎人注册_巴黎人娱乐网址 推荐澳门博彩app_澳门博彩_专业平台app 

点击获取礼包
龙虎娱乐官网登录
沙发
发表于 2019-04-21 00:20:40 | 只看该作者
一声宛如远古凶兽一般的咆哮声从熊熊燃烧的金色火焰之中传出,一双红色的巨大眼镜若隐若现的被所有人看到,让超级17号被盯着有一种毛骨悚然的感觉。
回复 支持 反对

使用道具 举报

元宝网交易平台官网
板凳
发表于 2019-04-21 01:51:45 | 只看该作者
崔平再次一躬到地,“李将军,我上任才一个多月,便遇到了响马,你一定要救救我啊!”/unoev21a/index.html
回复 支持 反对

使用道具 举报

百乐门彩票备用网址
地板
发表于 2019-04-21 01:18:42 | 只看该作者
唐三微微一笑,道:“那就这么定了。”带白沉香一同参与这次历练的旅程,就是唐三对马红俊的承诺。感情是需要时间来培养的,至于他们能否真正产生感情,那就要看马红俊自己的本事了。白沉香在攻防两方面虽然不强,但速度奇快,绝对是一名合格的侦查魂师。凭借着速度,一般情况下自保也足够了。
回复 支持 反对

使用道具 举报

大发彩票手机版
5#
发表于 2019-04-21 02:46:12 | 只看该作者
“老子爆你的菊花!”霸刀死里逃生,一看白鹤痛苦地抱头哀嚎,恶向胆生,抽出一把匕首,向上一捅。
回复 支持 反对

使用道具 举报

云顶国际娱网址
6#
发表于 2019-04-21 00:33:44 | 只看该作者
心里对丁宁是艳羡不已,他拍了拍丁宁的肩膀,给了丁宁一个饶有意味的眼神,风一般地又钻回了器材室。
回复 支持 反对

使用道具 举报

金沙js28com注册送钱55
7#
发表于 2019-04-21 02:38:33 | 只看该作者
如来这番话依然提到了“本来面目”,看来如来想要得到自己这造化之身,必有大用。而如来越是如此,悟空越不敢降,相比天庭的拔刀相向,西天世界的佛祖菩萨看似温文尔雅、慈悲为怀,说不准更是吃人不吐骨头的主儿。
回复 支持 反对

使用道具 举报

完美国际娱乐
8#
发表于 2019-04-21 01:52:44 | 只看该作者
他身边的副将卫伯玉看见了,在月光下几名骑兵正向这边疾奔而来,片刻,几名斥候奔至密林前,一名队正翻身下马向李嗣业禀报道:“禀报将军,敌军一分为二,一半驻扎在北城外的大营中,另一半驻扎在城中,目前他们尚无动静,没有发现我们。”
回复 支持 反对

使用道具 举报

申博app下载
9#
发表于 2019-04-21 02:52:34 | 只看该作者
“韩少校,那个女人不见了。”喻站长趁着嗓子对正在拼命射击的韩非喊道。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

乐虎国际手机平台&全球顶级娱乐平台是互联网最大的搜索引擎优化研究中心,是致力于培养学员用户体验意识和提供专业技术解答的专业培训机构, 成立于2007年,2008年第一家入驻歪歪的培训机构,2014年成为腾讯课堂战略合作机构。
© 2007-2016 乐虎国际手机平台&全球顶级娱乐平台 湘ICP备13004652号-1 Powered by Discuz!X  Template by 乐虎国际手机平台&全球顶级娱乐平台 
快速回复 返回顶部 返回列表