集合之LinkedList

ArrayList和LinkedList以及Vector都是基础容器List中的重要成员,List集合与map,set的区别是,List是以线性方式存储的,有序的(先进先出),可重复的容器,可以设置多个null值。而set是无序不重复的容器,Map没有继承Collection,是有序不重复的。今天我们来了解一下LinkedList。
LinkedList继承关系图

LinkedList

一. LinkedList的继承关系

我们知道ArrayList的父类是AbstractList,并且实现了List接口。根据上图可以看到,LinkedList继承自AbstractSequentialList,而AbstractSequentialList则继承了AbstractList。除此之外,LinkedList还实现了List接口和Qeque接口。我们接下来进入解析源码过程。

1.AbstractSequentialList

查看源码可以得知,该抽象类包含一个无参构造方法,以及以下几个方法:

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

该方法通过调用内部listIterator方法创建了一个ListIterator迭代器,再获取,根据index值获取对应角标的数据。而该方法需要子类实现。需要注意的是,ListIterator是一个接口,与Iterator不同,它只能用于List中,每个List需要实现自己的ListIterator接口,作为一个私有的内部类存在List中,如:ArrayList和LinkedList均实现了这个内部类。

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

get方法调用内部方法获取迭代器,将迭代器中指定角标的位置修改,再返回原数据

剩下的方法不再赘述,都是围绕迭代器进行的,包括添加数据,移除数据等等。
总结:AbstractSequentialList抽象类定义了链表结构list集合的基础方法,同时其子类需要实现listIterator和iterator方法。

2.Queue

队列是一种特殊的线性表,它定义了队列的基本操作方法,它只允许在表的前端进行删除操作,而在表的后端进行插入操作。
LinkedList类实现了Queue接口,因此我们可以把LinkedList当成Queue来用,LinkedList之所以比ArrayList插入和新增效率高,是因为ArrayList每次插入数据,需要进行大量的移动操作,甚至需要进行扩容。而linkedList则避免了这些问题。我们后面会聊到。

3.Deque

继承自Queue,在队列的基础上扩展,新增了较多方法定义,例如:poll()、peek()等等。

4.LinkedList

//无参构造函数
public LinkedList() {
    }

//有参构造函数
 public LinkedList(Collection<? extends E> c) {
      this();
      addAll(c);
 }

LinkedList有两个构造函数,当没有传入参数时,只创建LinkedList对象,当我们将一个Collection当作参数传入时,先调用无参构造函数,再将集合中数据插入。

public boolean add(E e) {
        linkLast(e);
        return true;
    }

创建LinkedList集合后,要往链表中添加数据,我们需要用到add方法,add方法在内部调用了linkLast方法,从方法名可以看出,LinkedList的add方法默认是添加到链表末尾。我们跟踪进去看看。

   void linkLast(E e) {
        final Node<E> l = last;
        //新建一个节点
        final Node<E> newNode = new Node<>(l, e, null);
        //最后一个节点指向新的节点
        last = newNode;
        if (l == null)//如果当前链表是空的,则第一个和最后一个节点都指向新的节点
            first = newNode;
        else
            l.next = newNode;//正常的在最后一个节点后追加,那么原先的最后一个节点的next就要指向现在真正的最后一个节点,原先的最后一个节点就变成了倒数第二个节点
        size++;
        modCount++;
    }
//创建节点
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;
        }
    }

linkLast方法执行流程是:
1.创建一个名1为l的final的Node指向链表最后一个节点,然后创建一个新节点,将需要添加的数据传入Node的构造函数中。
2.newNode 变量为新创建的Node节点,此时该节点的prvious为last的值,next的值为空。
3.让最后一个节点指向新的节点
4.判断l是否,链表当前为空,则第一个节点和最后一个节点都是newNode,第一个和最后一个节点都指向newNode ,也就是说newNode 是第一个节点。
5.链表长度+1,修改次数+1
这里我们可以分析看出LinkedList的存储方式如下图:
数据结构
我们再分析Linked的特点:
1.ArrayList是有序的,LinkedList是无序的,这个怎么理解呢?
ArrayList的底层数据结构是数组,我们知道数组在创建时长度就是固定的,JVM分配的空间地址也是确定的、连续的,因此数组每个元素之间是有序的。而linkedList,我们在上面的示例中可以看出:链表中的每一个节点之间都是系统单独分配的内存空间,每一个Node对象它们的内存地址也是没有规律的,它们通过保存前后节点相关联。
2.性能方面的优劣
ArrayList由于内存地址是连续的,我们可以推出,当需要对List进行查询,筛选时,用ArrayList合适,而如果需要进行大量的修改,用linkedList比较合适,因为ArrayList因为内存空间是连续的,每次进行修改,需要移动的数据非常大,若空间不足,还需要进行扩容,复制数据。操作尤为繁琐。相反LinkedList,由于每个元素空间不连续,进行插入或修改时,只需要将前后节点的指向进行修改,所以效率较高。相比较修改插入数据,查询数据则是LinkedList的弱势了,由于空间不连续,所以只能从链表开头或结尾查找,直到匹配上。

4.LinkedList的addAll()方法
//在集合末尾添加一个集合
public boolean addAll(Collection<? extends E> c) {
		//当前链表长度为size
        return addAll(size, c);
    }
//将集合添加到指定角标之后
public boolean addAll(int index, Collection<? extends E> c) {
		//检查是否存在index,这是通过size判断的,如果index<0或index>size,则抛出角标越界异常。
        checkPositionIndex(index);
		//将集合转换成数组进行查找
        Object[] a = c.toArray();
        int numNew = a.length;
        //若需要添加的集合为空,添加失败
        if (numNew == 0)
            return false;
		
        Node<E> pred, succ;
        //index==size满足,则说明是加到链表末尾
        if (index == size) {//加到链表末尾
            succ = null;
            //当前节点的前一个节点是原本最后一个节点
            pred = last;
        } else {
        	//不是加到链表结尾
            succ = node(index);
            //如果不是链表结尾,则设置当前节点的前一个节点为指定角标的后面一个节点
            pred = succ.prev;
        }
		//开始遍历集合,一个一个添加
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            //新建一个节点,该节点的值为o,该节点的前一个值为pred
            Node<E> newNode = new Node<>(pred, e, null);
            if (pred == null)
            	//若当前链表为空,则最后一个节点与第一个节点都为newNode
                first = newNode;
            else
            	//设置原本最后一个节点的后一个节点为newNode 
                pred.next = newNode;
            pred = newNode;
        }
        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }
5.LinkedList的indexOf方法
public int indexOf(Object o) {
        int index = 0;
        //若当前对象为空
        if (o == null) {
        	//从第一个节点开始找,通过x.next()遍历整个List,进行对比
            for (Node<E> x = first; x != null; x = x.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;
    }
6.lastIndexOf()
public int lastIndexOf(Object o) {
        int index = size;
        if (o == null) {
        	//从最后一个查找,与indexOf相似。
            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;
    }
7.poll()从链表最前面删除一个节点
public E poll() {
        final Node<E> f = first;
        //判断如果第一个元素不为空,则说明链表中包含元素,调用unlinkFirst删除
        return (f == null) ? null : unlinkFirst(f);
    }

private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        //获取第一个元素
        final E element = f.item;
        //取出第一个节点的后一个元素
        final Node<E> next = f.next;
        //将第一个节点置空
        f.item = null;
        f.next = null; // help GC
        //first节点指向后一个节点
        first = next;
        //链表为空
        if (next == null)
        	//当前节点的后一个节点置空
            last = null;
        else
        	//否则将第一个节点置空
            next.prev = null;
        size--;
        modCount++;
        return element;
    }


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值