LinkedList 源码分析

来源:互联网 发布:南京网博java培训费用 编辑:程序博客网 时间:2024/05/17 20:32

LinkedList

LinkedList是List接口的一个实现类,内部是基于一个双向链表实现的。支持添加,移除,替换三种操作。
同时,LinkedList中的元素可以是任意类型的,包括null。当需要队列一样的数据操作的时候使用LinkedList是很有用的。

先来看看LinkedList的继承结构:

AbstractCollection

AbstractList

AbstractSequentialList

LinkedList

此外LinkedList还直接实现了一下的接口:

List, Deque, Queue, Cloneable, Serializable

List接口在之前额ArrayList分析中已近讲过了。这里就不赘述了。下面主要看看Deque, Queue这两个接口。

Queue

Queue被设计用来做预先处理的收集器。它除了有继承子Collection的功能实现外,还有插入,删除,查找三个操作。
并且这三个操作都有对应的两种类型的实现方法。一种会抛出异常,另一种则是返回一个指定的值。在插入操作还会有容量限制,没有容量限制的话能保证插入不会失败。

方法 说明 boolean add(E e) 添加一个元素到队列的末尾,如果队列使用容量限制的话,插入成功,返回true,有容量限制且容量已满则否则抛出异常 boolean offer(E e) 无容量限制则直接插入队列末尾,返回true,否则调用add()进行插入,有异常返回false E remove() 从队列的首部移除一个元素,并返回该元素。队列已空则抛出异常。 E poll() 从队列的首部移除一个元素,并返回该元素。队列已空则返回null。 E element() 返回队列的末尾的元素,不执行删除操作。队列已空则抛出异常。 E peek() 返回队列的末尾的元素,不执行删除操作。队列已空则返回null。

Deque(其实是双向队列的缩写)

继承自Queue。

Deque是一个线性收集器,支持从开头或者结尾添加或者删除元素。Deque的实现类基本都不加入容量限制,但是保留有这个功能。Deque支持的操作有添加,移除,检索元素。每种操作都有两种类型,一种是操作失败直接抛出异常,另一种是返回一个指定的值。

方法 说明 void addFirst(E e) 队列没有容量限制的话,将元素加入到队列首部。否则抛出异常 void addLast(E e) 队列没有容量限制的话,将元素加入到队列末尾。否则抛出异常 boolean offerFirst(E e) 队列没有容量限制的话,将元素加入到队列首部,返回true。否则则去调用addFirst(),如抛出异常则返回false boolean offferLast(E e) 队列没有容量限制的话,将元素加入到队列末尾,返回true。否则则去调用addLast(),如抛出异常则返回false E removeFirst() 移除队列第一个元素,如果队列为空则抛出异常 E removeLast() 移除队列最后一个元素,如果队列为空则抛出异常 E pollFirst() 移除队列第一个元素,如果队列为空则返回null E pollLast() 移除队列最后一个元素,如果队列为经为空则返回null E getFirst() 获取但是不删除第一个元素,队列为空则抛出异常 E getLast() 获取但是不删除最后一个元素,队列为空则抛出异常 E peekFirst() 获取但是不删除第一个元素,队列为空则返回null E peekLast() 获取但是不删除最后一个元素,队列为空则返回null

在Deque和Queue中可能抛出的异常有:
IllegalStateException,ClassCastException,NullPointerException,IllegalArgumentException

LinkedList

先来看看它的一个内部静态类。
实际上是对数据的封装,以及加上它的前后元素的引用。其实和C语言中的链表的指针的意思是一样的。

private static final class Link<ET> {    ET data; //封装的数据元素    Link<ET> previous, next;//该元素指向它的前后元素的引用。    Link(ET o, Link<ET> p, Link<ET> n) {        data = o;//我们使用LinkedList的时候装载的数据对象本身。        previous = p;        next = n;    }}

内部还有两个迭代器内部类:

  • LinkIterator
  • ReverseLinkIterator

我们看看LinkIterator的内部实现:

Link<ET> link, lastLink;//link当前元素,lastLink最后一次访问到元素//从指定位置开始迭代LinkIterator(LinkedList<ET> object, int location) {    list = object;//队列本身    //得到原list中的操作记数。后续对list的操作都会使用到,判别是否存在并发操作    if (location >= 0 && location <= list.size) {    expectedModCount = list.modCount;        link = list.voidLink;        //查找的优化,根据索引位置与list的size比较,从头向尾,还是从末尾向开头开始寻找指定位置的元素        if (location < list.size / 2) {            for (pos = -1; pos + 1 < location; pos++) {                link = link.next;            }        } else {            for (pos = list.size; pos >= location; pos--) {                link = link.previous;            }        }    } else {        throw new IndexOutOfBoundsException();    }}

设置元素

public void set(ET object) {    ...      //lastLink就是最后一个访问到的元素          lastLink.data = object;    ...  }

添加元素

public void add(ET object) {    if (expectedModCount == list.modCount) {        Link<ET> next = link.next;//当前元素的下一个元素        Link<ET> newLink = new Link<ET>(object, link, next);//将新添加的数据封装        link.next = newLink;//link.next指向新元素        next.previous = newLink;//当前元素的下一个元素的previous指向新元素        link = newLink;//当前的节点更新为新加入的节点        lastLink = null;//最后访问的节点置为空        pos++;//这实际是新添加元素的节点位置索引值        //同时自加LinkedList中的操作次数和迭代器中的操作次数,不然肯定会报异常,因为每次        //对list的操作都会检查这两个值是否相等。        expectedModCount++;        list.modCount++;        list.size++;    } else {        throw new ConcurrentModificationException();    }
public boolean hasNext() {    return link.next != list.voidLink;//判断一个节点的next域是否指向null,null就说明到末尾了}//link为空值说明没有前继几点啦,用link.previous != null,可能link==null时,link.previous会报空指针异常。public boolean hasPrevious() {    return link != list.voidLink;}public ET next() {    if (expectedModCount == list.modCount) {        LinkedList.Link<ET> next = link.next;//当前元素的下一个元素        if (next != list.voidLink) {            lastLink = link = next;//同时将保存当前节点和最后访问的节点更新为刚得到的下一个元素            pos++;            return link.data;        }        throw new NoSuchElementException();//已到达末尾是调用next,会抛异常    }    throw new ConcurrentModificationException();}public ET previous() {      ...        lastLink = link;        link = link.previous;//直接从保存当前节点信息的link的previous域获得前继节点        pos--;        return lastLink.data;      ....}//其实就是add的逆操作,需要注意的是remove操作是将最后访问到的节点删除的,也就是lastLinkpublic void remove() {    ...        Link<ET> next = lastLink.next;        Link<ET> previous = lastLink.previous;        next.previous = previous;        previous.next = next;        //当前节点与最后访问的节点指向同意元素,next()或者previous()后,该条件为true        if (lastLink == link) {            pos--;        }        link = previous;//更新当前节点为删除节点的前一个元素        lastLink = null;        expectedModCount++;        list.size--;        list.modCount++;      ...}

add remove next previous set 这五个操作都会有以下异常判断(并发操作异常)

if (expectedModCount == list.modCount) {    ...} else {    throw new ConcurrentModificationException();}

ReverseLinkIterator只是LinkIterator的反序迭代而已,理解好LinkIterator就很好理解ReverseLinkIterator了

迭代器都说了这么多,下面我们看看LinkedList的主体部分:

//默认构造方法会创建一个空节点public LinkedList() {    voidLink = new Link<E>(null, null, null);//此时voidLink.previous==voidLink.next==null    voidLink.previous = voidLink;//指向对象本身,保证使用voidLink.previous不会空指针异常    voidLink.next = voidLink;}public LinkedList(Collection<? extends E> collection) {    this();//new一个LinkedList的时候,总会调用到默认构造方法    addAll(collection);}

voidLink是维持整个list的关键所在,基本每个操作都离不开它 看后面的分析就知道了。

添加元素的时候逻辑基本和上面迭代器中的逻辑一样,只是add(E e),offerFirst(E e)内部是直接调用addLastImpl(E e)执行添加操作而已,而指定位置的添加注意一点上面提及到根据插入点的索引与list的size的中点比较,从而选择从头开始还是从尾开始寻找该位置而已。其实涉及查找的操作都会使用这个策略的。

//public void add(int location, E object)的片段而已if (location < (size / 2)) {    for (int i = 0; i <= location; i++) {        link = link.next;    }} else {    for (int i = size; i > location; i--) {        link = link.previous;    }}----------------------------------------------public boolean add(E object) {    return addLastImpl(object);}private boolean addLastImpl(E object) {    Link<E> oldLast = voidLink.previous;    Link<E> newLink = new Link<E>(object, oldLast, voidLink);    voidLink.previous = newLink;    oldLast.next = newLink;    size++;    modCount++;    return true;}

在addAll方法中国,插入逻辑是不变的,只是参数检查而已

public boolean addAll(Collection<? extends E> collection) {    int adding = collection.size();    if (adding == 0) {        return false;//collection没有元素直接返回    }    //判断是不是LinkedList的自己的相同对象。是的话会拷贝它所有的元素到ArrayList中。    Collection<? extends E> elements = (collection == this) ?            new ArrayList<E>(collection) : collection;    //由LinkedList的构造方法可知,voidLink == voidLink.previous(初次调用的时候)    //不是第一次调用时,voidLink.previous已经指向了最后添加的那个元素了。详见一下几行代码    Link<E> previous = voidLink.previous;    //循环插入collection中的元素    for (E e : elements) {        Link<E> newLink = new Link<E>(e, previous, null);        previous.next = newLink;        previous = newLink;    }    previous.next = voidLink;    voidLink.previous = previous;    //至此,会使得voidLink.next和voidLink.previous都指向最后一个添加的元素,    size += adding;    modCount++;    return true;}

我们再来看看addFirst的实现:

public void addFirst(E object) {    addFirstImpl(object);}private boolean addFirstImpl(E object) {    //第一次addFirstImpl的时候voidLink.next==voidLink,否则则是上次添加在头部的节点    Link<E> oldFirst = voidLink.next;    //此时newLink.previous = voidLink,头结点的前继肯定指向空节点嘛(不同于c语言指向的是NULL)    //newLink.next == voidLink.next    Link<E> newLink = new Link<E>(object, voidLink, oldFirst);    voidLink.next = newLink;//看到没,voidLink.next保存着每次点在在头部的节点信息    oldFirst.previous = newLink;//oldFirst则是上一次添加在头部的节点啦,它的previous指向newLink就顺理成章啦    size++;    modCount++;    return true;}

到这里就能搞明白voidLink的previous和next的作用啦,那么其他的操作都是基于这两个东西操作的。
以下举一些例子:

@Overridepublic boolean contains(Object object) {    Link<E> link = voidLink.next;//头节点    //注意分两种情况查找即可    if (object != null) {        while (link != voidLink) {            if (object.equals(link.data)) {                return true;            }            link = link.next;        }    } else {        while (link != voidLink) {            if (link.data == null) {                return true;            }            link = link.next;        }    }    return false;}@Overridepublic E get(int location) {    if (location >= 0 && location < size) {        Link<E> link = voidLink;//这里,下一步就用到next 和 previous了        if (location < (size / 2)) {            for (int i = 0; i <= location; i++) {                link = link.next;            }        } else {            for (int i = size; i > location; i--) {                link = link.previous;            }        }        return link.data;    }    throw new IndexOutOfBoundsException();}@Overridepublic int indexOf(Object object) {    int pos = 0;    Link<E> link = voidLink.next;    ....

再看看几个重要的方法:

//会抛异常//注意是从头部移除public E pop() {    return removeFirstImpl();}//从头部添加public void push(E e) {    addFirstImpl(e);}//从头部移除元素public E remove() {    return removeFirstImpl();}//没有元素返回nullpublic E poll() {    return size == 0 ? null : removeFirst();}public E peek() {    return peekFirstImpl();}

只有poll peek操作遇到LinkedList无元素返回null,其他做此类型会抛出异常,其实现的接口已有说明

我们需要主要一下clear():仅是将size置零,voidLink的next,previous指向空元素而已,可见voidLink的作用很关键

public void clear() {  if (size > 0) {      size = 0;      voidLink.next = voidLink;      voidLink.previous = voidLink;      modCount++;  }

通过以上的分析,基本能理解好LinkedList了。其他具体的方法就不一一贴出来了,操作原理和上面的分析大同小异。
需要对每个方法详细的理解,可以直接去看LinkedList的源码。

ArrayList LinkedList 基于数组实现 基于链表实现 实现RandomAccess接口,可以直接访问指定索引的元素 没现RandomAccess接口,需要遍历到指定索引的位置 查找访问快,插入删除慢 查找访问慢,插入删除快 存在扩容拷贝 没有容量限制,无需扩容,内部会使用到ArrayList(addAll的时候)

均不是线程安全的,并发操作会报异常

you KO LinkedList !!!

0 0
原创粉丝点击