06-java集合框架之LinkedList详解

LinkedList详解

1.LinkedList是什么?

在这里插入图片描述

从图中可以看出来,LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作,同时它也实现 List 接口,所以能对它进行队列操作,并且它也实现了 Deque 接口,为 add、poll 提供先进先出队列操作,从而能将LinkedList当作双端队列使用。它还实现了Cloneable接口,Serializable接口,所以它支持克隆和序列化,但是LinkedList是一个非线程安全的List.

重要参数

transient Node first:
双向链表的表头,它是双向链表节点所对应的类Node的实例。Node中包含成员变量:prev, next, item。
transient Node last:
双向链表的表尾。
transient int size:
双向链表中节点的个数。

Node

Node节点一共有三个属性:item代表节点值,prev代表节点的前一个节点,next代表节点的后一个节点。每个结点都有一个前驱和后继结点,并且在 LinkedList中也定义了两个变量分别指向链表中的第一个和最后一个结点。

1.LinkedList怎么实现的?

老样子,还是先从构造方法开始看:

public LinkedList() {
}

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

前一个构造方法为空,里面不含任何元素。后者构造一个包含指定 collection 中的元素的列表。构造函数首先会调用LinkedList(),构造一个空列表,然后调用了addAll()方法将Collection中的所有元素添加到列表中,再来看addAll()和一张数据结构图:

在这里插入图片描述

public boolean addAll(int index, Collection<? extends E> c) {
	checkPositionIndex(index);//检查下标,若插入的位置小于0或者大于链表长度,则抛出IndexOutOfBoundsException异常
	
	Object[] a = c.toArray();
	int numNew = a.length;//插入元素个数
	if (numNew == 0)
		return false;
	Node<E> pred, succ;     //定义perv与next
	if (index == size) {    //如果在队尾插入
		succ = null;    //next置空
		pred = last;    //perv指向队尾元素last
	} else {            //在指定位置插入
		succ = node(index); //next指向该位置
		pred = succ.prev;   //perv指向前一个元素
	}
	for (Object o : a) {
		@SuppressWarnings("unchecked") E e = (E) o;
		Node<E> newNode = new Node<>(pred, e, null);//创建一个新节点,指定perv,next置空
		if (pred == null)//如果perv不存在
			first = newNode;//表头first指向此节点
		else
			pred.next = newNode;//perv存在,则将其next指向新节点
		pred = newNode;//perv移动,继续创建新节点
	}
	if (succ == null) {
		last = pred;
	} else {
		pred.next = succ;
		succ.prev = pred;
	}
	size += numNew;
	modCount++;
	return true;
}

LinkedList是通过双向链表去实现的。既然是双向链表,那么它的顺序访问会非常高效,而随机访问效率比较低;它也实现了List接口{也就是说,它实现了get(int location)、remove(int location)等根据索引值来获取、删除节点的函数.那么既然它实现了List接口,又是如何将双向链表和索引值联系起来的呢?其实它就是通过一个计数索引值来实现的。例如,当我们调用get(int index),set、add、remove时,首先会比较index和双向链表长度的1/2;若前者大,则从链表头开始往后查找,直到index位置;否则,从链表末尾开始先前查找,直到index位置,从而节省一半的查找时间.当然它还提供了一些辅助方法主要是用来添加和删除元素用的.这里要说一个遍历问题,简直采用迭代器或者for循环去遍历LinkedList,不建议采用随机访问的方式,不然效率太低下.

方法作用
linkFirs插入头部
linkLast插入尾部
linkBefore插入到某个节点前
unlinkFirst删除头部
unlinkLast删除尾部
unlink删除某节点
size获取长度
isEmpty是否为空
contains是否包含

说到LinkedList的遍历就要说一件值得关注的事,大家都知道,java集合(Collection)中有一种错误机制是fail-fast 机制,也就是用来错误检查的机制,简单来说就是当某一个线程遍历list的过程中,list的内容被另外一个线程所改变了;就会抛出ConcurrentModificationException异常,产生fail-fast事件.所以对于我们经常要考虑线程安全的问题,还是必要的时候采用java.util.concurrent包下对应的类来代替它比较好,或者采用Collections工具类中的同步方法去解决,当然ArrayList也是一样的.
那fail-fast是怎么来的?其实原理很简单,在AbstractList里定义了一个叫modCount的变量

protected transient int modCount = 0;

它存在的意义就是在有其他操作对List进行修改时,自动加1;例如在ArrayList里的 ensureExplicitCapacity 方法,remove方法,clear方法等等。产生fail-fast事件,是通过抛出ConcurrentModificationException异常来触发的。而ConcurrentModificationException是在操作Iterator时抛出的异常。如果去查看Iterator的源码你会发现,Iterator里定义了一个叫expectedModCount的变量,初始化等于modCount的值。所以每次遍历List中的元素的时候,都会比较 expectedModCount 和 modCount 是否相等。如果不相等则抛出异常。
那么什么时候 modCount 不等于 expectedModCount呢?查看ArrayList的源码,如上面所说,无论是 ensureExplicitCapacity()、add()、remove(),还是clear(),只要涉及到修改集合中的元素个数时,都会改变modCount的值。总结一下就是当多个线程对同一个集合进行操作的时候,某线程访问集合的过程中,该集合的内容被其他线程所改变(即其它线程通过add、remove、clear等方法,改变了modCount的值);这时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

解决原理
查看和ArrayList对应的CopyOnWriteArrayList的源码。举个最简单的例子add方法:

public boolean add(E e) {
   final ReentrantLock lock = this.lock;
   lock.lock();
   try {
	   Object[] elements = getArray(); //copy一份原来的array
	   int len = elements.length;
	   Object[] newElements = Arrays.copyOf(elements, len + 1); 
	   newElements[len] = e; //在copy的数组上add
	   setArray(newElements); //原有引用指向修改后的数据
	   return true;
   } finally {
	   lock.unlock();
   }
}

CopyOnWriteArrayList的add、set、remove等会改变原数组的方法中,都是先copy一份原来的array,再在copy数组上进行add、set、remove操作。然后把原有数据的引用改成指向修改后的数据,这就才不影响COWIterator那份数组。

1.LinkedList的优缺点

ArrayList的数据结构为线性表,而LinkedList数据结构是双向链表。链表数据结构的特点是每个元素分配的空间不必连续、插入和删除元素时速度非常快、但访问元素的速度较慢,当数据量很大或者操作很频繁的情况下,添加和删除元素时具有比ArrayList更好的性能,但在元素的查询和修改方面要弱于ArrayList,LinkedList类每个结点用内部类Node表示,LinkedList通过first和last引用分别指向链表的第一个和最后一个元素,当链表为空时,first和last都为NULL值.

总结一下:

优点:

插入和删除元素效率高

缺点:

访问元素的速度和修改元素的速度比较慢,顺序访问速度搞,随机访问效率低

是非线程安全的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值