Java容器LinkedList源代码解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Kevin_zhai/article/details/72820410

写在前面的话

本文针对的是Java1.6进行的源码分析,与其他版本可能存在差异。

概述

LinkedList是基于双向链表来实现的,与ArrayList一样,它也实现了List接口。与ArrayList相比,它的插入和删除操作更加高效,但是在随机访问数据方面要逊色许多。LinkedList适用的场景:更多的执行插入和删除操作,基本上不会随机访问数据。

源代码解析

1. LinkedList底层数据结构

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

Entry定义了三个属性,element用于存放数据,next指向双向链表的后结点,previous指向链表的前结点。LinkedList是基于双向链表实现的,它定义了一个Entry类型的头结点head,head中并不存储任何数据。如下图:
这里写图片描述

2. LinkedList定义

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

LinkedList继承了AbstractSequentialList,并且实现了List和Deque接口。AbstractSequentialList实现了List接口中的get()、set()、add()和remove()方法,降低了List接口的复杂度。LinkedList实现了Deque接口,意味着我们可以将它当做双向队列来用。

3. 属性

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

LinkedList定义了两个属性,header为双向链表的头结点,它并不存储任何数据。size是LinkedList的大小,初始化为0.

4. 构造方法

    //默认构造方法,head的前后结点都指向它本身
    public LinkedList() {
        header.next = header.previous = header;
    }

    //带有collection参数的构造方法
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

LinkedList提供了两个构造方法,第二个需要调用addAll()方法,我们看一下addAll()的实现:

    public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }

    public boolean addAll(int index, Collection<? extends E> c) {
        //校验传入index的值,失败的话,直接抛出异常
        if (index < 0 || index > size)
            throw new IndexOutOfBoundsException("Index: "+index+
                                               ", Size: "+size);
        //将传入的collection转化为数据
        Object[] a = c.toArray();
        int numNew = a.length;
        //如果数组内没有数据,直接返回false
        if (numNew==0)
            return false;
        modCount++;
        //如果index和size相等的话,说明是在链表末尾添加数据,否则找到当前index处的结点
        Entry<E> successor = (index==size ? header : entry(index));
        //找到插入位置的前一个结点
        Entry<E> predecessor = successor.previous;
        //遍历数组,增加结点
        for (int i=0; i<numNew; i++) {
            //新建结点,并把结点放在successor和predecessor的中间
            Entry<E> e = new Entry<E>((E)a[i], successor, predecessor);
            predecessor.next = e;
            //predecessor向后移动
            predecessor = e;
        }
        successor.previous = predecessor;
        //更新size大小
        size += numNew;
        return true;
    }

    //查找index处的结点
    private Entry<E> entry(int index) {
         //校验传入index的值,失败的话,直接抛出异常
        if (index < 0 || index >= size)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+size);
        Entry<E> e = header;
        //判断index和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;
    }

entry(int index)方法是用来查找index处的结点的,这个方法比普通的遍历做了个小小的优化。先判断index和size的关系,如果要查找的结点在链表的前半段,则从链表头部开始查找;如果要查找的结点在链表的后半段,则从链表的尾部开始查找。

5.添加数据

LinkedList的add(E e)方法是在链表末尾添加结点,会调用addBefore方法在头结点前插入结点。

    public boolean add(E e) {
        addBefore(e, header);
        return true;
    }

    //在entry结点前添加结点
    private Entry<E> addBefore(E e, Entry<E> entry) {
        //新建结点,next指向entry,previous指向entry的前一个结点
        Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
        //让新结点的前后结点的对应引用都指向自己
        newEntry.previous.next = newEntry;
        newEntry.next.previous = newEntry;
        size++;
        modCount++;
        return newEntry;
    }

6.获取数据

LinkedList的get(int index)方法是获取在index索引处的值,会调用上面说到的entry(int index)方法,获取index处的结点,返回结点的值。

    public E get(int index) {
        return entry(index).element;
    }

7.删除数据

LinkedList的remove(Object o)方法是删除list中第一个值为o的数据,会先遍历链表,找到第一个值为o的结点,然后在链表中把该结点删除。

    public boolean remove(Object o) {
        //如果o的值为null,则单独处理
        if (o==null) {
            for (Entry<E> e = header.next; e != header; e = e.next) {
                if (e.element==null) {
                    remove(e);
                    return true;
                }
            }
        } else {
            //从头遍历链表,找到值为o的结点
            for (Entry<E> e = header.next; e != header; e = e.next) {
                if (o.equals(e.element)) {
                    remove(e);
                    return true;
                }
            }
        }
        return false;
    }

    //链表中删除给定结点方法
    private E remove(Entry<E> e) {
        //header结点不保存数据,如果是header结点,直接抛出异常
        if (e == header)
            throw new NoSuchElementException();
        //返回要删除结点的值
        E result = e.element;
        //在链表中去除该结点
        e.previous.next = e.next;
        e.next.previous = e.previous;
        e.next = e.previous = null;
        e.element = null;
        size--;
        modCount++;
        return result;
    }

8.判断值是否存在

LinkedList的contains(Object o)方法是判断值o在list中是否存在,直接调用indexOf(Object o)方法来判断。

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

    //获取o在链表中的索引,不存在就返回-1
    public int indexOf(Object o) {
        int index = 0;
        //如果o为null的话,单独处理
        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;
    }
展开阅读全文

没有更多推荐了,返回首页