线性表ArrayList、LinkedList源码分析

学数据结构目的是?

  1. 说个金庸小说的故事,《倚天屠龙记》中的张无忌在明教禁地修习“乾坤大罗移”只用了一天练到六层!而资质极佳者七年可成,次者14年,超过21年将不能再练,原因是张无忌之前修炼过九阳神功,悟性高,内功深厚。倘若我们程序员内功生深厚,再学其他相关技术会不会也一天六层?或者对现有技术有不同角度的理解呢? 答案是肯定的! 读者可以搜索BAT等大厂的面试题,其中数据结构与算法、框架原理及设计思想等是必考项,而不会问如何使用三脚猫功夫。

  2. 还有一部分原因是最近看Java集合的源码发现一脸懵逼状态... 在某宝买了本《数据结构与算法分析》 看了之后再去看集合源码感觉 so easy 妈妈再也不用担心我的学习。

什么是数据结构

定义:是指数据元素的集合,数据元素之间存在某种特定的关系结构。

数据结构有两种方式,逻辑和实际内存物理结构。

逻辑结构
逻辑结构-逻辑结构-
集合结构
线性结构
树形结构
图形结构
物理结构
物理结构-物理结构-
顺序存储结构
链式存储结构
抽象数据类型(Abstract data type,ADT)
  1. 数据类型:指的是一个值的集合和定义在该值集上的一组操作总称。
  2. 抽象数据类型:是带有一组操作的一些对象的集合。举个例子:抽象类跟子类关系,不管子类怎么实现,它的抽象类型还是父类。对于集合抽象数据类型,可以有add、remove、contains、find等操作。

接下来看看基本数据结构,线性表。

线性表

定义:由N个具有相同特性的数据元素的有限序列组成。

这里有一些定义,用N代表这个表的长度,如果N==0,那么这个表是“空表”。还有一种情况N>0,A1的前驱是A0(Ai-1),后继是A2(Ai+1),最前面没有前驱,最后面没有后继。

注意:n指的是长度,i指的是索引。

线性表又分顺序存储跟链式存储结构。

顺序存储结构

顺序存储结构:一组内存地址连续的数据元素。

上面图表示顺序存储结构,第一个元素“1”称之为表头,最后一个表未。Java中顺序存储可以用数组来实现。

arr{1,2,3,4}数组分别代表四个人排队,突然第二个人肚子痛,老师就安排他休息,不排队了,那么3向前排、接着4也向前排,第二人去趟洗手间回来发现自己不痛了,要回原来位置,那么3、4全都向后腾出位置。 由此可见顺序存储结构,插入、删除除效率低。如果老师问第二个人叫什么名字?那就是指定第二个位置,也就是说顺序存储结构根据位置找数据,效率高。

Java顺序存储结构集合有ArrayList类,那么接下来分析ArrayList源码实现。

ArrayList源码 JDk1.8 (分析核心部分)

/**
AbstractList:包含一些公共的方法,以及一些未实现的。
List:列表集合的规范。
RandomAccess:具有随机访问功能
Cloneable:克隆(浅拷贝)
Serializable:序列化
*/
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable

    //首先这部分可以看到是使用elementData数组存储数据的,我们add remov其实都是操作这个elementData数组。

    private static final int DEFAULT_CAPACITY = 10;//集合默认扩容长度
    private static final Object[] EMPTY_ELEMENTDATA = {}; //长度为0数组
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //默认长度为0数组数组
    transient Object[] elementData;//transient关键字忽略序列化,我们平时调用add,数据就存到elementData这个数组。
    private int size; //集合长度
    
    
    
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {//长度大于>0?
            this.elementData = new Object[initialCapacity];//创建长度
        } else if (initialCapacity == 0) {//刚好为0
            this.elementData = EMPTY_ELEMENTDATA;//赋值长度为0的数组。
        } else {//这种情况是错误的,数组长度不能是负数
            throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
        }  
    }
    
    public ArrayList() {
        //如果是new ArrarList();没有传长度,elementData长度等于0
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

	//通过其它集合创建Arraylist
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();//保存数组
		
		//将数组长度赋值到size,判断不等于0
        if ((size = elementData.length) != 0) { 
			//集合c传递进来的有可能是int[],这里转成object[]
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

	//空间对齐  意思是去掉多余的空间,在扩展内存的时候会预留空间,内存紧张情况下可以调用这个方法去掉预留空间。
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {//集合长度假如是3,数组扩容后的长度可能是10,实际上有个空位置。
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }
	
	//扩容,minCapacity意向长度 
    public void ensureCapacity(int minCapacity) {
		//最小扩容长度,如果elementData长度等于0,扩容长度DEFAULT_CAPACITY(10)
		int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)? 0 : DEFAULT_CAPACITY;
 
        // 如果elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 就扩容10,否则minCapacity
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

	/**扩容方法
	oldCapacity:当前elementData长度
	newCapacity:扩容后的长度
	*/
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);//>>右移运算 oldCapacity+(oldCapacity/2) 大概1.5倍
        if (newCapacity - minCapacity < 0)//新长度小于目标扩容长度,将目标长度赋给新长度。
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)//不能超过int最大值
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);//扩容后数组
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
	
	//集合长度、不是数组长度
    public int size() {
        return size;
    }

	//是否空集合
    public boolean isEmpty() {
        return size == 0;
    }

	//判断对象是否存在集合
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

	//如果存在返回索引 否则返回-1
    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

	//从表尾开始找
    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            //这里可以看到 查找效率不高
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }



	//获取元素
    public E get(int index) {
        rangeCheck(index);//越界检查

        return elementData(index);
    }

	//修改元素,返回修改前元素
    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);//判断是否足够空间
        elementData[size++] = e;
        return true;
    }

	//将元素插入到指定元素
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

	//移除指定索引元素
    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

	//根据对象找到索引再删除,成功返回true
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

	//System.arraycopy JNI调用底层删除,快速删除一个元素
    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }
	
	//清空
    public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

	//添加整个集合
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

	//从index后面插入
    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,numMoved);

        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }
    
复制代码

以上是主要核心代码部分

总结

  1. Arraylist非线程安全(Vector是线程安全的)
  2. 顺序存储结构基于数组,由于插入、删除需要重新调整其它元素位置导致效率低,优点高效随机访问。
链式存储结构
  1. 单链表
  2. 循环链表
  3. 双向链表

上面提到Arraylist插入、删除效率低原因是删除一个元素,其它元素都要移动,那么链式存储结构就没有这缺点这是为什么? 看图中链式存储是以节点方式存储,当前节点保存下一个节点形成线性表,这些节点可以是连续的,也可以是不连续的。假设我们有p1 p2 p3节点按照存储应该是p1指向p2,p指向p3,如果要删除p2 只需要将p1指向到p3 p2赋null即可,由此可见链式存储并未没有出现元素向前移动这种情况,仅仅改变指针即可,这就是新增、删除效率高的原因。

链式存储结构主要有以下几种

1. 单链表

单向链表是当前节点指向下一个节点,如果A4节点删除,A0指向A2即可。

2. 双向链表

上面的单向链表A1节点,如果拿到上一个节点A0的信息是比较麻烦的,所以双向链表就很好解决这个问题,在当前节点保存前置、后继节点。

双向链表结构实现 LinkedList类

AbstractSequentialList: 可以当作队列、堆栈、双端队列使用。 List:列表集合的规范。 Deque:双端队列。 Cloneable:克隆 Serializable:可以序列化

public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
复制代码

//节点
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;
    }
}    



transient int size = 0;//集合大小(节点个数)
transient Node<E> first;//表头
transient Node<E> last;//表尾

//空链表
public LinkedList() {
}

//包含Collection元素的
public LinkedList(Collection<? extends E> c) {
    this();
    addAll(c);
}


//插入到表头,参数e作为表头,原表头
private void linkFirst(E e) {
    final Node<E> f = first;//当前链表的表头
    final Node<E> newNode = new Node<>(null, e, f);//创建新节点,当前节点为参数e,next节点为当前表头,前驱null
    first = newNode;//新节点作为当前链表的表头
    if (f == null)//如果表头是空的,说明是空表。
        last = newNode;//表尾指向新节点
    else
        f.prev = newNode;//非空表,原表头的指向新节点
    size++;
    modCount++;
}

//添加到表尾
void linkLast(E e) {
    final Node<E> l = last;//当前链表的表尾    
    final Node<E> newNode = new Node<>(l, e, null);//创建新节点,表尾作为新节点的“前驱”,e是当前节点,后继指向null    
    last = newNode;    //记录新节点为表尾
    if (l == null)    //如果表尾为空,说明空表
        first = newNode;//放到表尾
    else    
        l.next = newNode;//原表尾节点后继指向当前新节点    
    size++;    
    modCount++;    
}  

//节点e插入到节点succ之前
void linkBefore(E e, Node<E> succ) {
       final Node<E> pred = succ.prev;//succ节点的前驱
       final Node<E> newNode = new Node<>(pred, e, succ); //创建新节点,newNode前驱指向pred,当前节点e,后继节点指向succ
       succ.prev = newNode; //succ节点前驱指向newNode,也就是e在succ前面
       if (pred == null) //如果succ前面是空的,说明succ是表头
           first = newNode; //新节点作为表头
       else 
           pred.next = newNode;//e前面的节点后继  指向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)//next节点是空,说明只有一个节点。
        last = null;//表尾置空
    else
        next.prev = 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; //help gc
    last = prev;//尾节点的前面一个作为表尾
    if (prev == null)//前面没有,说明只有一个节点
        first = null;//置空表头
    else
        prev.next = null;//尾节点没有下一个节点。
    size--;
    modCount++;
    return element;
}



//删除一个节点
E unlink(Node<E> x) {
    // assert x != null;
    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节点的后继
        x.prev = null;///删除当前x节点前驱
    }

    if (next == null) {//x节点后继是空,说明是尾节点
        last = prev;/前驱成为表尾
    } else {
        next.prev = prev;//后继的前驱 指向 x节点的前驱
        x.next = null;//删除后继
    }

    x.item = null;//删除x节点
    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);
}

//判断是否包含
public boolean contains(Object o) {
    return indexOf(o) != -1;
}

//找到对象o的索引。
public int indexOf(Object o) {
    int index = 0;
    if (o == null) {//找到等于null
        for (Node<E> x = first; x != null; x = x.next) {//不停读取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;
}

//链表大小
public int size() {
    return size;
}

//添加节点到尾部
public boolean add(E e) {
    linkLast(e);
    return true;
}

//遍历找到这个元素删除,成功返回true,否则false;
public boolean remove(Object o) {
    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);
}

//从index开始批量插入
public boolean addAll(int index, Collection<? extends E> c) {
    checkPositionIndex(index);//越界检查
    Object[] a = c.toArray();//调用实现类的toArray()方法,转成数组
    int numNew = a.length;//数组长度
    if (numNew == 0)//如果参数c长度为0,说明空集合,直接返回添加失败。
        return false;
        
    Node<E> pred, succ;//pred是succ的前驱,succ是index位置的节点
    if (index == size) {//从表尾插入
        succ = null;
        pred = last;//当前链表的表尾放到前驱pred
    } else {
        succ = node(index);//折半找到index位置的元素
        pred = succ.prev;//succ的前驱
    }
    
    for (Object o : a) {//创建小链表
        E e = (E) o;//元素
        Node<E> newNode = new Node<>(pred, e, null);//创建一个节点,前驱指向succ节点前驱
        if (pred == null)//如果没有前驱,说明是表头
            first = newNode;//新节点作为表头
        else
            pred.next = newNode;//新节点指向要插入位置节点的前驱,前驱的后继再指向新节点
        pred = newNode;//前驱节点指针向后移,下一个节点的前驱就是newNode。
    }
    
    if (succ == null) {//从表尾插入
        last = pred;//指向最后一个插入的节点
    } else {
        pred.next = succ;//最后一个节点跟原来succ节点相连
        succ.prev = pred;
    }
    
    size += numNew;
    modCount++;
    return true;
}



//折半查找
Node<E> node(int index) {
    if (index < (size >> 1)) {//size右移相当于除以2的1次方,index小于size一半
        Node<E> x = first;
        for (int i = 0; i < index; i++)//一直向下找到index位置的元素
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

//清除所有节点
public void clear() {
    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;//index节点的值
    x.item = element;//重新赋值
    return oldVal;//返回之前修改的值
}

//插入节点
public void add(int index, E element) {
    checkPositionIndex(index);//越界检查

    if (index == size)//从尾部插入
        linkLast(element);
    else//插入到之前
        linkBefore(element, node(index));
}

//插入到succ之前
void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    final Node<E> pred = succ.prev;//succ前驱节点
    final Node<E> newNode = new Node<>(pred, e, succ);//新节点的前驱指向succ的前驱,新节点后继指向succ
    succ.prev = newNode;//succ的前驱指向新节点
    if (pred == null)//空表判断
        first = newNode;
    else
        pred.next = newNode;//succ的前驱节点  后继指向新节点
    size++;
    modCount++;
}

//移除节点
public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));//折半找到后删除
}

//删除x节点
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) {//如果前驱是空的,说明x是表头
        first = next;//后继作为表头
    } else {
        prev.next = next;//x的前驱的后继 指向x的后继
        x.prev = null;//移除x前驱引用
    }
    
    if (next == null) {//表尾?
        last = prev;//x前驱作为表尾
    } else {
        next.prev = prev;//下后继的前驱 指向x的前驱
        x.next = null;
    }
    x.item = null;
    size--;
    modCount++;
    return element;
}

//从表尾找到对象(反向查找)
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;
}

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

//返回表头
public E element() {
    return getFirst();
}

//删除并返回表头
public E poll() {
    final Node<E> f = first;
    return (f == null) ? null : unlinkFirst(f);
}

复制代码

总结

  1. 以节点方式存储,所以不存在扩容机制。
  2. 插入、删除效率高、查找效率低。
  1. 栈stack又叫表LIFO先进后出,只能在表的一端插入和删除操作,这一端称之为栈顶,另一端则栈底,插入称为进栈(push),删除称之为出栈(pop)。像栈帧、后缀表达式都是栈结构。

1、链表方式实现栈结构,添加表头、删除表头两个操作比较简单。

LinkedList.java 源码中有两个方法
//进栈
public void push(E var1) {
    //linkFirst方法插入到表头,上面LinkedList源码分析有
    this.addFirst(var1);
}

//出栈
public E pop() {
    return this.removeFirst();
}
复制代码

2、数组方式 Stack类,它继承Vector,而Vector是线程安全的,跟ArrayList是一样的功能。

Stack类也是这两个方法,stack是调用Vector的方法完成进出栈的,而Vector源码跟上面ArrayList相似度很高,这里就不贴了。
public E push(E var1) {
   ...
}

public synchronized E pop() {
    ...
}

复制代码
队列

队列又叫先入先出表(FIFO),在表的一端删除,这头称之为队头,另一端添加称之为队尾。 生活中也有很多队列例子像取号排队,多电脑连打印机

同样的数组可以实现队列,有两种形式顺序数组,循环数组。

看上面图,数组长度是6,现在入队A4,出队A0,此时表头指向A1 表尾指向A4。那么问题来了这个时候再入队A5 长度就会不足 扩容嘛? 如果一直这样的话会造成前面空间浪费,这种现象称为“假溢出”。循环数组解决这个问题,将表尾指向前面可用的空间。

Java中队列实现LinkedList(当然有其它的,这里举例子)

public interface Queue<E>  { 
    //核心方法 入队
    boolean offer(E e); 
    //核心方法 出队
    E poll(); 
}

public class LinkedList{ 
    
    //入队是调用add方法
    public boolean offer(E e) {
        return add(e);
    }
    
    //出队是调用unlinkFirst方法
    private E unlinkFirst(Node<E> f) {
    }
    
}

复制代码
作者:天星技术团队-张德帅 https://juejin.im/user/5afa539751882542aa42e5c5/posts
  • 1996年,就读于德国慕尼黑特种兵学校。
  • 1998年,在美国宾夕法尼亚大学心理系进修。
  • 2000年,加入海波突击队。
  • 2003年,攻破日本情报系统,获取10份绝密文件,令其战争阴谋破产。
  • 2005年,前往叙利亚执行任务,成功解救三千人质。
  • 2006年,获得诺贝尔和平奖提名。
  • 2008年,参加美国总统选举,以一票只差落选。
  • 2011年,被奥巴马跪请回到海波突击队,同年击毙拉登。
  • 2015年,被提名为全球最有影响力人物。
  • 2018年,放弃一生荣誉加入"天星技术团队",持续更新文章。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值