jdk1.6集合源码阅读之LinkedList

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

      如果说ArrayList是基于数组实现的List,那么LinkedList是基于链表实现的List。

1.定义

public class LinkedList
    extends AbstractSequentialList
    implements List, Deque, Cloneable, java.io.Serializable

        可以看得到LinkedList继承了AbstractSequentialList。实现了List,Deque. 后面两个和ArrayList一样,说明可以被克隆和序列化。

          而AbstractSequentialList基础自AbstractList,而且还重新实现了get,set,add,remove,等等方法。

AbstractSequentialList的代码如下:

package java.util;


public abstract class AbstractSequentialList extends AbstractList {
   
    protected AbstractSequentialList() {
    }

   
    public E get(int index) {
        try {
            return listIterator(index).next();
        } catch (NoSuchElementException exc) {
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }

 
    public E set(int index, E element) {
	try {
	    ListIterator e = listIterator(index);
	    E oldVal = e.next();
	    e.set(element);
	    return oldVal;
	} catch (NoSuchElementException exc) {
	    throw new IndexOutOfBoundsException("Index: "+index);
	}
    }

   
    public void add(int index, E element) {
	try {
	    listIterator(index).add(element);
	} catch (NoSuchElementException exc) {
	    throw new IndexOutOfBoundsException("Index: "+index);
	}
    }

    public E remove(int index) {
	try {
	    ListIterator e = listIterator(index);
	    E outCast = e.next();
	    e.remove();
	    return outCast;
	} catch (NoSuchElementException exc) {
	    throw new IndexOutOfBoundsException("Index: "+index);
	}
    }


   
    public boolean addAll(int index, Collection c) {
	try {
	    boolean modified = false;
	    ListIterator e1 = listIterator(index);
	    Iterator e2 = c.iterator();
	    while (e2.hasNext()) {
		e1.add(e2.next());
		modified = true;
	    }
	    return modified;
	} catch (NoSuchElementException exc) {
	    throw new IndexOutOfBoundsException("Index: "+index);
	}
    }


 
    public Iterator iterator() {
        return listIterator();
    }

    
    public abstract ListIterator listIterator(int index);
}

而Dqueue接口 是一个双向队列,也就是既可以先入先出,又可以先入后出,再直白一点就是既可以在头部添加元素又在尾部添加元素,既可以在头部获取元素又可以在尾部获取元素。看下Deque的定义

public interface Deque extends Queue

jdk1.6集合源码阅读之LinkedList_第1张图片

 

 

2.底层存储   

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

size和ArrayList里面的size一样,记录容器元素的个数。那这个Entry类型的变量header是个什么鬼。

Entry是个内部类,来描述链表的节点的信息,代码如下:

//描述链表节点的类
private static class Entry {
	E element; //存储的对象
	Entry next;//链表的下一个节点元素
	Entry previous;//链表的上一个节点元素

	Entry(E element, Entry next, Entry previous) {
	    this.element = element;
	    this.next = next;
	    this.previous = previous;
	}
}

可以看得出ArrayList底层是采用双向链表来实现的。

数据结构双向链表是包含两个指针的,pre指向前一个节点,next指向后一个节点,但是第一个节点head的pre指向null,最后一个节点的tail指向null。

3.构造函数

//默认构造
public LinkedList() {
    链表的头和尾都指向了自己
    header.next = header.previous = header;
}

//将一个集合的元素来构造自己,这些元素按其 collection 的迭代器返回的顺序排列
public LinkedList(Collection c) {
	this();
	addAll(c);
}

从默认构造函数可以看得出这是一个双向循环链表,如果是双向不循环链表的话,应该是:

header.next=header.previous=null。

4.增加

        有8个增加,5个增加是实现Dqueue里面的添加函数,其余3个是实现List接口里面的添加函数,然后实现Dqueue基本是调用实现List里面的添加函数,所以我们使用的时候直接调用List里面的添加函数即可。可以少压一次栈。


//这5个add是LinkedList实现Dqueue里面的添加函数,其实都是调用实现List接口里面的函数

//将元素加到第一个,可以看得到,是加到header的后面,因为header就是一个空头,里面没有存储元素
public void addFirst(E e) {
	addBefore(e, header.next);
}
//将元素加列表的尾部
public void addLast(E e) {
	addBefore(e, header);
}

//封装链表插入操作,分两步走
//1.其实就是在构造的时候,自己的netx和previous指好
//2.自己的前节点的next指向自己,自己后结点的pre指向自己。
private Entry addBefore(E e, Entry entry) {
	Entry newEntry = new Entry(e, entry, entry.previous);

    //自己的前节点的next指向自己
	newEntry.previous.next = newEntry;
    //自己后结点的pre指向自己
	newEntry.next.previous = newEntry;
    //记数加1
	size++;
	modCount++;
	return newEntry;
}

 public boolean offer(E e) {
        return add(e);
}


public boolean offerFirst(E e) {
        addFirst(e);
        return true;
}

   
public boolean offerLast(E e) {
        addLast(e);
        return true;
}

//下面三个增加是实现List接口里面的添加函数
//将元素加列表的尾部
public boolean add(E e) {
	addBefore(e, header);
    return true;
}

//添加一个集合元素到list中
public boolean addAll(Collection c) {
         //其实还是调用在指定位置添加一个集合到list中
        return addAll(size, c);
}

//在指定位置添加一个集合元素到list中   
public boolean addAll(int index, Collection 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++;

        //找出要插入元素的前后节点

        //找出其后节点,如果位置和size大小相等(为什么不是size-1,因为header占了一位),则他的下一个节点是header
        //否则要查找index位置的节点,这个就是他的后一个节点
        Entry successor = (index==size ? header : entry(index));
        //他的前一个节点,就是index位置的前一个节点。
        Entry predecessor = successor.previous;
	    for (int i=0; i e = new Entry((E)a[i], successor, predecessor);
            //将它前一个节点的next指向自己
            predecessor.next = e;
            //后面的元素插入到这个元素的后面
            predecessor = e;
        }
        //将index位置的节点的前指针指向自己这样就完成了链表的操作。
        successor.previous = predecessor;

        size += numNew;
        return true;
}

双向链表的增加比起数组的增加稍微是要麻烦的理解一点,自己画图应该不难理解,总结起来,就是一句话主要就是插入会改变前节点的next和后一节点的pre,主要把前一节点的next指向自己,后一节点的pre指向自己,即可。

5.删除

//删除第一个容器元素
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 = header.next; e != header; e = e.next) {
                if (e.element==null) {
                    remove(e);
                    return true;
                }
            }
        } else {
            //循环遍历节点,然后找到节点,然后删除。时间复杂度O(n)
            for (Entry e = header.next; e != header; e = e.next) {
                if (o.equals(e.element)) {
                    remove(e);
                    return true;
                }
            }
        }
        return false;
}

//删除指定位置的容器元素
public E remove(int index) {
    //entry(index)是找到这个节点
    return remove(entry(index));
}

//删除指定的节点,供自己调用
private E remove(Entry e) {
	if (e == header)
	    throw new NoSuchElementException();

        E result = e.element;
    //将自己的前一节点的后指针执行自己的后一节点
	e.previous.next = e.next;
    //讲自己后一节点的前指针指向自己的前节点
    //讲自己置为null,给gc回收
	e.next.previous = e.previous;
        e.next = e.previous = null;
        e.element = null;
    //大小减一
	size--;
	modCount++;
        return result;
}

删除则要简单一点,删除了自己之后,主要

将自己的前一节点的后指针执行自己后一节点
讲自己后一节点的前指针指向自己的前节点

而且可以看到remove(Object o)时间负责度为O(n),而remove(int)的时间复杂度度为O(n/2),因为里面用到了二分查找的办法。所以删除的时候要注意了,要选用正确的方法删除。(其实我转载了一篇博客专门介绍这个LinkenList的局限。)所以以前所说的增删快的删有时也是很慢的。

6.修改容器元素的值

public E set(int index, E element) {
        Entry e = entry(index);
        E oldVal = e.element;
        e.element = element;
        return oldVal;
}

这个比较简单,查找到,然后修改即可

7.查找

         我们知道链表和数组相比,查找比数组要慢的非常多,数组直接定位,而链表每次我们只能拿到一个头部,所以不管找什么,我们都要从头开始遍历起,而LinkedList使用了双向循环链表,这样遍历起来就会快很多,既可以从头往后找,又可以从后往前找。直到找到index位置。


//查找指定index的链表里面元素
public E get(int index) {
        return entry(index).element;
}

//这个方法很重要,基本上查找都是使用这个方法来进行查找。
//这儿就显示双向循环链表的好处,既可以从头往后遍历,又可以从后往前遍历
//这儿使用了二分查找的方法,效率要高很多
private Entry entry(int index) {
        if (index < 0 || index >= size)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+size);
        Entry e = header;
        //如果下标小于size的一般,就从头往后遍历,找到元素
        if (index < (size >> 1)) {
            for (int i = 0; i <= index; i++)
                e = e.next;
        } else {否则从后往前遍历
            for (int i = size; i > index; i--)
                e = e.previous;
        }
        return e;
}

 到这里我们明白,基于双向循环链表实现的LinkedList,通过索引Index的操作时低效的,index所对应的元素越靠近中间所费时间越长。而向链表两端插入和删除元素则是非常高效的(如果不是两端的话,都需要对链表进行遍历查找)。

8.其余的LinkedList的操作

8.1 查找元素是否在容器中

public boolean contains(Object o) {
        return indexOf(o) != -1;
}

public int indexOf(Object o) {
        int index = 0;
        if (o==null) {
            for (Entry e = header.next; e != header; e = e.next) {
                if (e.element==null)
                    return index;
                index++;
            }
        } else {
            for (Entry e = header.next; e != header; e = e.next) {
                if (o.equals(e.element))
                    return index;
                index++;
            }
        }
        return -1;
}

public int lastIndexOf(Object o) {
        int index = size;
        if (o==null) {
            for (Entry e = header.previous; e != header; e = e.previous) {
                index--;
                if (e.element==null)
                    return index;
            }
        } else {
            for (Entry e = header.previous; e != header; e = e.previous) {
                index--;
                if (o.equals(e.element))
                    return index;
            }
        }
        return -1;
}

要遍历,低效。

打完收工。

转载于:https://my.oschina.net/u/1540325/blog/737264

你可能感兴趣的:(jdk1.6集合源码阅读之LinkedList)