四个关注点在LinkedList上的答案c++
关 注 点 | 结 论 |
---|---|
LinkedList是否容许空 | 容许 |
LinkedList是否容许重复数据 | 容许 |
LinkedList是否有序 | 有序 |
LinkedList是否线程安全 | 非线程安全 |
LinkedList是基于链表实现的,那什么是链表呢?链表原先是c/c++的一个概念,是一种线性的存储结构,意思是将存储的数据存储在单元格里面,与数组不一样的是,它还带一个存储地址的单元格,值得注意的是 LinkedList是 双向链表,那什么是双向链表呢?和单向链表不一样的是,双向链表在存储数据的单元格的两端各带有一个存储地址的单元格,为何叫链表呢,故名思意,这个结构就像一个链子同样是首尾相接的,能够随意的拆分,如图:算法
private static class Entry<E> {
E element;
Entry<E> next;
Entry<E> previous;
...
}

中间的element真正存储数据的单元格,头和尾是用来存储地址的引用值的编程
先来一段代码数组
1 public static void main(String[] args)
2 {
3 List<String> list = new LinkedList<String>();
4 list.add("111");
5 list.add("222");
6 }
看作了什么:安全
一共五步,每一步的操做步骤都用数字表示出来了:数据结构
一、新的entry的element赋值为111;dom
二、新的entry的next是header的next,header的next是0x00000000,因此新的entry的next即0x00000000;atom
三、新的entry的previous是header的previous,header的previous是0x00000000,因此新的entry的next即0x00000000;spa
四、"newEntry.previous.next = newEntry",首先是newEntry的previous,因为newEntry的previous为0x00000000,因此newEntry.previous表示的是header,header的next为newEntry,即header的next为0x00000001;线程
五、"newEntry.next.previous = newEntry",和4同样,把header的previous设置为0x00000001;
为何要这么作?还记得双向链表的两个特色吗,一是任意节点均可以向前和向后寻址,二是整个链表头的previous表示的是链表的尾Entry,链表尾的next表示的是链表的头Entry。如今链表头就是0x00000000这个Entry,链表尾就是0x00000001,能够本身看图观察、思考一下是否符合这两个条件。
最后看一下add了一个字符串"222"作了什么,假设新new出来的Entry的地址是0x00000002,画图表示:
这彻底能够解释了问什么双向链表既能够向前查找,有能够向后查找
先查一下LinkedList是怎么写的:
public E get(int index) {
return entry(index).element;
}
1 private Entry<E> entry(int index) {
2 if (index < 0 || index >= size)
3 throw new IndexOutOfBoundsException("Index: "+index+
4 ", Size: "+size);
5 Entry<E> e = header;
//索引大于一半后往前查找
6 if (index < (size >> 1)) {
7 for (int i = 0; i <= index; i++)
8 e = e.next;
9 } else {
//不然顺序查找
10 for (int i = size; i > index; i--)
11 e = e.previous;
12 }
13 return e;
14 }
这段代码就体现出了双向链表的好处了。双向链表增长了一点点的空间消耗(每一个Entry里面还要维护它的前置Entry的引用),同时也增长了必定的编程复杂度,却大大提高了效率。
因为LinkedList是双向链表,因此LinkedList既能够向前查找,也能够向后查找,第6行~第12行的做用就是:当index小于数组大小的一半的时候(size >> 1表示size / 2,使用移位运算提高代码运行效率),向后查找;不然,向前查找。
这样,在个人数据结构里面有10000个元素,刚巧查找的又是第10000个元素的时候,就不须要从头遍历10000次了,向后遍历便可,一次就能找到我要的元素。
先看源码
1 public E remove(int index) {
2 return remove(entry(index));
3 }
1 private E remove(Entry<E> e) {
//先找到位置
2 if (e == header)
3 throw new NoSuchElementException();
4 //在将它置空,利用回收机制回收
5 E result = e.element;
6 e.previous.next = e.next;
7 e.next.previous = e.previous;
8 e.next = e.previous = null;
9 e.element = null;
10 size--;
11 modCount++;
12 return result;
13 }
画个图利于理解:
这个问题我稍微扩展深刻一点:按照Java虚拟机HotSpot采用的垃圾回收检测算法----根节点搜索算法来讲,即便previous、element、next不设置为null也是能够回收这个Entry的,由于此时这个Entry已经没有任何地方会指向它了,tail的previous与header的next都已经变掉了,因此这块Entry会被当作"垃圾"对待。之因此还要将previous、element、next设置为null,我认为多是为了兼容另一种垃圾回收检测算法----引用计数法,这种垃圾回收检测算法,只要对象之间存在相互引用,那么这块内存就不会被看成"垃圾"对待。
public void add(int index, E element) {
addBefore(element, (index==size ? header : entry(index)));
}
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;
}
理解了前面的原理,这个应该看的懂了
LinkedList和ArrayList的对比
老生常谈的问题了,这里我尝试以本身的理解尽可能说清楚这个问题,顺便在这里就把LinkedList的优缺点也给讲了。
一、顺序插入速度ArrayList会比较快,由于ArrayList是基于数组实现的,数组是事先new好的,只要往指定位置塞一个数据就行了;LinkedList则不一样,每次顺序插入的时候LinkedList将new一个对象出来,若是对象比较大,那么new的时间势必会长一点,再加上一些引用赋值的操做,因此顺序插入LinkedList必然慢于ArrayList
二、基于上一点,由于LinkedList里面不只维护了待插入的元素,还维护了Entry的前置Entry和后继Entry,若是一个LinkedList中的Entry很是多,那么LinkedList将比ArrayList更耗费一些内存
三、数据遍历的速度,看最后一部分,这里就不细讲了,结论是:使用各自遍历效率最高的方式,ArrayList的遍历效率会比LinkedList的遍历效率高一些
四、有些说法认为LinkedList作插入和删除更快,这种说法实际上是不许确的:
(1)LinkedList作插入、删除的时候,慢在寻址,快在只须要改变先后Entry的引用地址
(2)ArrayList作插入、删除的时候,慢在数组元素的批量copy,快在寻址
因此,若是待插入、删除的元素是在数据结构的前半段尤为是很是靠前的位置的时候,LinkedList的效率将大大快过ArrayList,由于ArrayList将批量copy大量的元素;越日后,对于LinkedList来讲,由于它是双向链表,因此在第2个元素后面插入一个数据和在倒数第2个元素后面插入一个元素在效率上基本没有差异,可是ArrayList因为要批量copy的元素愈来愈少,操做速度必然追上乃至超过LinkedList。
从这个分析看出,若是你十分肯定你插入、删除的元素是在前半段,那么就使用LinkedList;若是你十分肯定你删除、删除的元素在比较靠后的位置,那么能够考虑使用ArrayList。若是你不能肯定你要作的插入、删除是在哪儿呢?那仍是建议你使用LinkedList吧,由于一来LinkedList总体插入、删除的执行效率比较稳定,没有ArrayList这种越日后越快的状况;二来插入元素的时候,弄得很差ArrayList就要进行一次扩容,记住,ArrayList底层数组扩容是一个既消耗时间又消耗空间的操做
ArrayList使用最普通的for循环遍历,LinkedList使用foreach循环比较快,主要是由于ArrayList是实现了RandomAccess接口而LinkedList则没有实现这个接口