java集合二之LinkedList

来源:互联网 发布:加好友的软件 编辑:程序博客网 时间:2024/04/28 04:00

一:LinkedList简介

LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。

LinkedList 实现 List 接口,能对它进行队列操作。LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。LinkedList 实现java.io.Serializable接口,即LinkedList支持序列化,能通过序列化去传输。LinkedList 是非同步的。

LinkedList也和ArrayList一样实现了List接口,但是它执行插入和删除操作时比ArrayList更加高效,因为它是基于链表的。基于链表也决定了它在随机访问方面要比ArrayList逊色一点。

除此之外,LinkedList还提供了一些可以使其作为栈、队列、双端队列的方法。这些方法中有些彼此之间只是名称的区别,以使得这些名字在特定的上下文中显得更加的合适。

二:LinkedList源码解析

对于LinkedList而言,它实现List接口、底层使用Entry保存节点对象。其操作基本上是对双链节点的操作。下面我们来分析LinkedList的源代码:

  1. 私有属性
    LinkedList只定义了两个私有属性:

        private transient Entry<E> header = new Entry<E>(null, null, null);    private transient int size = 0;

    size肯定就是LinkedList对象里面存储的元素个数了。LinkedList既然是基于链表实现的,那么这个header肯定就是链表的头结点了,Entry就是节点对象了。以下是Entry类的代码。

    private static class Entry<E> {    E element;    Entry<E> next;    Entry<E> previous;    Entry(E element, Entry<E> next, Entry<E> previous) {        this.element = element;        this.next = next;        this.previous = previous;    }}

    只定义了存储的元素、前一个元素、后一个元素,很明显就是双向链表的节点的定义,每个节点只知道自己的上一个节点和下一个节点。

  2. 构造方法
    LinkedList提供了两个构造方法。
    第一个构造方法不接受参数,只是将header节点的前一节点和后一节点都设置为自身,这样整个链表其实就只有header一个节点,用于表示一个空的链表。下面是其源码:

        /**     * Constructs an empty list.     */    public LinkedList() {        header.next = header.previous = header;    }

    (注意,这个是一个双向循环链表,如果不是循环链表,空链表的情况应该是header节点的前一节点和后一节点均为null)

    第二个构造方法接收一个Collection参数c,调用第一个构造方法构造一个空的链表,之后通过addAll将c中的元素全部添加到链表中。

    public LinkedList(Collection<? extends E> c) {    this();    addAll(c);}

    接下来我们来看看addAll的内容:

    public boolean addAll(Collection<? extends E> c) {    return addAll(size, c);}// index参数指定collection中插入的第一个元素的位置public boolean addAll(int index, Collection<? extends E> c) {    if (index < 0 || index > size)        throw new IndexOutOfBoundsException("Index: "+index+                                                ", Size: "+size);    Object[] a = c.toArray();    int numNew = a.length;    if (numNew==0)        return false;    modCount++;    Entry<E> successor = (index==size ? header : entry(index));    Entry<E> predecessor = successor.previous;    for (int i=0; i<numNew; i++) {        Entry<E> e = new Entry<E>((E)a[i], successor, predecessor);        predecessor.next = e;        predecessor = e;    }    successor.previous = predecessor;    size += numNew;    return true;}

    整个添加过程,其实就是将Collection转换成toArray数组之后进行遍历添加;与C语言中的单双链别无二致。

三:LinkedList的方法解析

  1. 数据存储

    LinkedList提供了add(E e)、add(int index, E element)、addAll(Collection<?extends E> c)、addAll(int index, Collection< ? extends E> c)、addBefore(E e, Entry entry)、addFirst(E e)、addLast(E e)这些添加元素的方法。下面我们一一讲解:

        //添加一个元素,在头之前    public boolean add(E e) {        addBefore(e, header);        return true;    }    //在特点位置添加一元素    public void add(int index, E element) {        addBefore(element, (index==size ? header : entry(index)));    }    //该方法在构造方法中已使用,即批量添加    public boolean addAll(int index, Collection<? extends E> c) {        if (index < 0 || index > size)            throw new IndexOutOfBoundsException("Index: "+index+                                                ", Size: "+size);        Object[] a = c.toArray();        int numNew = a.length;        if (numNew==0)            return false;        modCount++;        Entry<E> successor = (index==size ? header : entry(index));        Entry<E> predecessor = successor.previous;        for (int i=0; i<numNew; i++) {            Entry<E> e = new Entry<E>((E)a[i], successor, predecessor);            predecessor.next = e;            predecessor = e;        }        successor.previous = predecessor;        size += numNew;        return true;    }    //调用上一个方法    public boolean addAll(Collection<? extends E> c) {        return addAll(size, c);    }    //在某节点对象之后添加一对象e,私有的    private Entry<E> addBefore(E e, Entry<E> entry) {        Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);        newEntry.previous.next = newEntry;        newEntry.next.previous = newEntry;        size++;        modCount++;        return newEntry;    }    //在首位添加    public void addFirst(E e) {        addBefore(e, header.next);    }    //在末尾添加    public void addLast(E e) {        addBefore(e, header);    }

    整个数据添加过程,重点在于 addAll和addBefore两个方法的实现上,也就是对节点之间的操作上;

  2. 数据读取
    读取的话主要有 get(int index)、getFirst()、getLast();即获取index索引处数据和首末尾数据,下面看看具体实现:

        public E get(int index) {        return entry(index).element;    }    public E getFirst() {        if (size==0)            throw new NoSuchElementException();        return header.next.element;    }    public E getLast()  {        if (size==0)            throw new NoSuchElementException();        return header.previous.element;    }
  3. 数据修改
    数据修改就一个方式,即set(int index, E element),对特定index索引处数据进行覆盖操作

    public E set(int index, E element) {        Entry<E> e = entry(index);        E oldVal = e.element;        e.element = element;        return oldVal;    }
  4. 数据删除

        //删除第一个    public E removeFirst() {        return remove(header.next);    }    //删除最后一个    public E removeLast() {        return remove(header.previous);    }    //删除某特定对象    public boolean remove(Object o) {        if (o==null) {            for (Entry<E> e = header.next; e != header; e = e.next) {                if (e.element==null) {                    remove(e);                    return true;                }            }        } else {            for (Entry<E> e = header.next; e != header; e = e.next) {                if (o.equals(e.element)) {                    remove(e);                    return true;                }            }        }        return false;    }    //清除所有    public void clear() {        Entry<E> e = header.next;        while (e != header) {            Entry<E> next = e.next;            e.next = e.previous = null;            e.element = null;            e = next;        }        header.next = header.previous = header;        size = 0;    modCount++;    }    //删除特定index处    public E remove(int index) {        return remove(entry(index));    }
  5. 转出数组

        //将本身转换为数组    public Object[] toArray() {        Object[] result = new Object[size];        int i = 0;        for (Entry<E> e = header.next; e != header; e = e.next)            result[i++] = e.element;        return result;    }    //将特定数进行转换    public <T> T[] toArray(T[] a) {        if (a.length < size)            a = (T[])java.lang.reflect.Array.newInstance(                                a.getClass().getComponentType(), size);        int i = 0;        Object[] result = a;        for (Entry<E> e = header.next; e != header; e = e.next)            result[i++] = e.element;        if (a.length > size)            a[size] = null;        return a;    }

    先判断出入的数组a的大小是否足够,若大小不够则拓展。这里用到了发射的方法,重新实例化了一个大小为size的数组。之后将数组a赋值给数组result,遍历链表向result中添加的元素。最后判断数组a的长度是否大于size,若大于则将size位置的内容设置为null。返回a。

    从代码中可以看出,数组a的length小于等于size时,a中所有元素被覆盖,被拓展来的空间存储的内容都是null;若数组a的length的length大于size,则0至size-1位置的内容被覆盖,size位置的元素被设置为null,size之后的元素不变。

  6. 其他方法

    clone():调用父类的clone()方法初始化对象链表clone,将clone构造成一个空的双向循环链表,之后将header的下一个节点开始将逐个节点添加到clone中。最后返回克隆的clone对象

    public Object clone() {        LinkedList<E> clone = null;    try {        clone = (LinkedList<E>) super.clone();    } catch (CloneNotSupportedException e) {        throw new InternalError();    }        // Put clone into "virgin" state        clone.header = new Entry<E>(null, null, null);        clone.header.next = clone.header.previous = clone.header;        clone.size = 0;        clone.modCount = 0;        // Initialize clone with our elements        for (Entry<E> e = header.next; e != header; e = e.next)            clone.add(e.element);        return clone;    }

    clear():将整个集合容器进行清空操作;

    public void clear() {        Entry<E> e = header.next;        while (e != header) {            Entry<E> next = e.next;            e.next = e.previous = null;            e.element = null;            e = next;        }        header.next = header.previous = header;        size = 0;    modCount++;    }

四:总结

  1. LinkedList 实际上是通过双向链表去实现的。
    它包含一个非常重要的内部类:Entry。Entry是双向链表节点所对应的数据结构,它包括的属性有:当前节点所包含的值,上一个节点,下一个节点。
  2. 从LinkedList的实现方式中可以发现,它不存在LinkedList容量不足的问题。
  3. LinkedList的克隆函数,即是将全部元素克隆到一个新的LinkedList对象中。
  4. LinkedList实现java.io.Serializable。当写入到输出流时,先写入“容量”,再依次写入“每一个节点保护的值”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。
  5. 由于LinkedList实现了Deque,而Deque接口定义了在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。
  6. LinkedList可以作为FIFO(先进先出)的队列,作为FIFO的队列时,该对象提供了一些等价,如下表;

    队列方法       等效方法add(e)        addLast(e)offer(e)      offerLast(e)remove()      removeFirst()poll()        pollFirst()element()     getFirst()peek()        peekFirst()
  7. LinkedList可以作为LIFO(后进先出)的栈,作为LIFO的栈时,该对象提供了一些等价方法,如下表:

    栈方法        等效方法push(e)      addFirst(e)pop()        removeFirst()peek()       peekFirst()
原创粉丝点击