LinkedList其实也就是我们在数据结构中的链表,这种数据结构有这样的特性:
在Java中,LinkedList提供了丰富的方法,可以模拟链式队列,链式堆栈等数据结构,为用户带来了极大的方便,下面看看这些方法的用法:
boolean add(E e):在链表后添加一个元素,如果成功,返回true,否则返回false;
void addFirst(E e):在链表头部插入一个元素;
addLast(E e):在链表尾部添加一个元素;
void add(int index, E element):在指定位置插入一个元素。
下面是代码演示:
输出:
E remove();移除链表中第一个元素;
boolean remove(Object o):移除链表中指定的元素;
E remove(int index):移除链表中指定位置的元素;
E removeFirst():移除链表中第一个元素,与remove类似;
E removeLast():移除链表中最后一个元素;
boolean removeFirstOccurrence(Object o):移除链表中第一次出现所在位置的元素;
boolean removeLastOccurrence(Object o):移除链表中最后一次出现所在位置的元素;
输出:
E get(int index):按照下边获取元素;
E getFirst():获取第一个元素;
E getLast():获取第二个元素;
输出:
注意到了,链表前后没有产生变化。
void push(E e):与addFirst一样,实际上它就是addFirst;
E pop():与removeFirst一样,实际上它就是removeFirst;
E poll():查询并移除第一个元素;
输出:
通过代码示例可以看出:push,pop的操作已经很接近stack的操作了。
如果链表为空的时候,看看poll与pop是啥区别:
输出:
可以看出poll返回null,而pop则产生异常。
E peek():获取第一个元素,但是不移除;
E peekFirst():获取第一个元素,但是不移除;
E peekLast():获取最后一个元素,但是不移除;
输出:
如果没找到对应的元素,统统输出null:
输出:
boolean offer(E e):在链表尾部插入一个元素;
boolean offerFirst(E e):与addFirst一样,实际上它就是addFirst;
boolean offerLast(E e):与addLast一样,实际上它就是addLast;
输出:
LinkedList中常用的方法基本都列出来了,当然还有一些其他的例子,这里就一起演示了:
输出:
从输出可以看出,除了set改变原linkedlist,其他几个方法都不改变原链表。
前面,我们已经学习了ArrayList,并了解了fail-fast机制。这一章我们接着学习List的实现类——LinkedList。
和学习ArrayList一样,接下来呢,我们先对LinkedList有个整体认识,然后再学习它的源码;最后再通过实例来学会使用LinkedList。内容包括:
第1部分 LinkedList介绍
第2部分 LinkedList数据结构
第3部分 LinkedList源码解析(基于JDK1.6.0_45)
第4部分 LinkedList遍历方式
第5部分 LinkedList示例
转载请注明出处:http://www.cnblogs.com/skywang12345/p/3308807.html
LinkedList简介
LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
LinkedList 实现 List 接口,能对它进行队列操作。
LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
LinkedList 是非同步的。
LinkedList构造函数
// 默认构造函数 LinkedList() // 创建一个LinkedList,保护Collection中的全部元素。 LinkedList(Collection extends E> collection)
LinkedList的API
LinkedList的API boolean add(E object) void add(int location, E object) boolean addAll(Collection extends E> collection) boolean addAll(int location, Collection extends E> collection) void addFirst(E object) void addLast(E object) void clear() Object clone() boolean contains(Object object) IteratordescendingIterator() E element() E get(int location) E getFirst() E getLast() int indexOf(Object object) int lastIndexOf(Object object) ListIterator listIterator(int location) boolean offer(E o) boolean offerFirst(E e) boolean offerLast(E e) E peek() E peekFirst() E peekLast() E poll() E pollFirst() E pollLast() E pop() void push(E e) E remove() E remove(int location) boolean remove(Object object) E removeFirst() boolean removeFirstOccurrence(Object o) E removeLast() boolean removeLastOccurrence(Object o) E set(int location, E object) int size() T[] toArray(T[] contents) Object[] toArray()
AbstractSequentialList简介
在介绍LinkedList的源码之前,先介绍一下AbstractSequentialList。毕竟,LinkedList是AbstractSequentialList的子类。
AbstractSequentialList 实现了get(int index)、set(int index, E element)、add(int index, E element) 和 remove(int index)这些函数。这些接口都是随机访问List的,LinkedList是双向链表;既然它继承于AbstractSequentialList,就相当于已经实现了“get(int index)这些接口”。
此外,我们若需要通过AbstractSequentialList自己实现一个列表,只需要扩展此类,并提供 listIterator() 和 size() 方法的实现即可。若要实现不可修改的列表,则需要实现列表迭代器的 hasNext、next、hasPrevious、previous 和 index 方法即可。
LinkedList的继承关系
java.lang.Object ↳ java.util.AbstractCollection↳ java.util.AbstractList ↳ java.util.AbstractSequentialList ↳ java.util.LinkedList public class LinkedList extends AbstractSequentialList implements List , Deque , Cloneable, java.io.Serializable {}
LinkedList与Collection关系如下图:
LinkedList的本质是双向链表。
(01) LinkedList继承于AbstractSequentialList,并且实现了Dequeue接口。
(02) LinkedList包含两个重要的成员:header 和 size。
header是双向链表的表头,它是双向链表节点所对应的类Entry的实例。Entry中包含成员变量: previous, next, element。其中,previous是该节点的上一个节点,next是该节点的下一个节点,element是该节点所包含的值。
size是双向链表中节点的个数。
为了更了解LinkedList的原理,下面对LinkedList源码代码作出分析。
在阅读源码之前,我们先对LinkedList的整体实现进行大致说明:
LinkedList实际上是通过双向链表去实现的。既然是双向链表,那么它的顺序访问会非常高效,而随机访问效率比较低。
既然LinkedList是通过双向链表的,但是它也实现了List接口{也就是说,它实现了get(int location)、remove(int location)等“根据索引值来获取、删除节点的函数”}。LinkedList是如何实现List的这些接口的,如何将“双向链表和索引值联系起来的”?
实际原理非常简单,它就是通过一个计数索引值来实现的。例如,当我们调用get(int location)时,首先会比较“location”和“双向链表长度的1/2”;若前者大,则从链表头开始往后查找,直到location位置;否则,从链表末尾开始先前查找,直到location位置。
这就是“双线链表和索引值联系起来”的方法。
好了,接下来开始阅读源码(只要理解双向链表,那么LinkedList的源码很容易理解的)。
总结:
(01) LinkedList 实际上是通过双向链表去实现的。
它包含一个非常重要的内部类:Entry。Entry是双向链表节点所对应的数据结构,它包括的属性有:当前节点所包含的值,上一个节点,下一个节点。
(02) 从LinkedList的实现方式中可以发现,它不存在LinkedList容量不足的问题。
(03) LinkedList的克隆函数,即是将全部元素克隆到一个新的LinkedList对象中。
(04) LinkedList实现java.io.Serializable。当写入到输出流时,先写入“容量”,再依次写入“每一个节点保护的值”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。
(05) 由于LinkedList实现了Deque,而Deque接口定义了在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。
总结起来如下表格:
第一个元素(头部) 最后一个元素(尾部)
抛出异常 特殊值 抛出异常 特殊值
插入 addFirst(e) offerFirst(e) addLast(e) offerLast(e)
移除 removeFirst() pollFirst() removeLast() pollLast()
检查 getFirst() peekFirst() getLast() peekLast()
(06) LinkedList可以作为FIFO(先进先出)的队列,作为FIFO的队列时,下表的方法等价:
队列方法 等效方法
add(e) addLast(e)
offer(e) offerLast(e)
remove() removeFirst()
poll() pollFirst()
element() getFirst()
peek() peekFirst()
(07) LinkedList可以作为LIFO(后进先出)的栈,作为LIFO的栈时,下表的方法等价:
栈方法 等效方法
push(e) addFirst(e)
pop() removeFirst()
peek() peekFirst()
LinkedList遍历方式
LinkedList支持多种遍历方式。建议不要采用随机访问的方式去遍历LinkedList,而采用逐个遍历的方式。
(01) 第一种,通过迭代器遍历。即通过Iterator去遍历。
for(Iterator iter = list.iterator(); iter.hasNext();) iter.next();
(02) 通过快速随机访问遍历LinkedList
int size = list.size(); for (int i=0; i) { list.get(i); }
(03) 通过另外一种for循环来遍历LinkedList
for (Integer integ:list) ;
(04) 通过pollFirst()来遍历LinkedList
while(list.pollFirst() != null) ;
(05) 通过pollLast()来遍历LinkedList
while(list.pollLast() != null) ;
(06) 通过removeFirst()来遍历LinkedList
try { while(list.removeFirst() != null) ; } catch (NoSuchElementException e) { }
(07) 通过removeLast()来遍历LinkedList
try { while(list.removeLast() != null) ; } catch (NoSuchElementException e) { }
测试这些遍历方式效率的代码如下:
执行结果:
iteratorLinkedListThruIterator:8 ms
iteratorLinkedListThruForeach:3724 ms
iteratorThroughFor2:5 ms
iteratorThroughPollFirst:8 ms
iteratorThroughPollLast:6 ms
iteratorThroughRemoveFirst:2 ms
iteratorThroughRemoveLast:2 ms
由此可见,遍历LinkedList时,使用removeFist()或removeLast()效率最高。但用它们遍历时,会删除原始数据;若单纯只读取,而不删除,应该使用第3种遍历方式。
无论如何,千万不要通过随机访问去遍历LinkedList!
下面通过一个示例来学习如何使用LinkedList的常用API
运行结果:
Test "addFirst(), removeFirst(), getFirst()"
llist:[10, 1, 4, 2, 3]
llist.removeFirst():10
llist:[1, 4, 2, 3]
llist.getFirst():1
Test "offerFirst(), pollFirst(), peekFirst()"
llist:[10, 1, 4, 2, 3]
llist.pollFirst():10
llist:[1, 4, 2, 3]
llist.peekFirst():1
Test "addLast(), removeLast(), getLast()"
llist:[1, 4, 2, 3, 20]
llist.removeLast():20
llist:[1, 4, 2, 3]
llist.getLast():3
Test "offerLast(), pollLast(), peekLast()"
llist:[1, 4, 2, 3, 20]
llist.pollLast():20
llist:[1, 4, 2, 3]
llist.peekLast():3
get(3):300
str:1
str:4
str:300
str:3
size:4
isEmpty():true
useLinkedListAsLIFO
stack:[4, 3, 2, 1]
stack.pop():4
stack.peek():3
stack:[3, 2, 1]
useLinkedListAsFIFO
queue:[10, 20, 30, 40]
queue.remove():10
queue.element():20
queue:[20, 30, 40]
更多内容
01. Java 集合系列目录
02. Java 集合系列01之 总体框架
03. Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例
04. Java 集合系列08之 List总结(LinkedList, ArrayList等使用场景和性能分析)
05. Java 集合系列18之 Iterator和Enumeration比较
链表 —— LinkedList
ArrayList 虽然好用,但是数组和数组列表都有一个重大的缺陷:从数组的中间位置删除一个元素要付出很大的代价,其原因是数组中处于被删除元素之后的所有元素都要向数组的前端移动。但是,具有链表结构的 LinkedList 则没有这个问题。
LinkedList 的底层结构是类似数据结构里边的双向链表,可以代价比较小地实现元素的增加和删除。
LinkedList 有一个如上图的内部类,这个类就是实现了双向链表的功能。
1、构造方法
LinkedList()
Constructs an empty list. 构造方法没什么特殊的。
2、常用方法
1)add 方法
默认添加元素到链表的最后一位
1 LinkedListlist = new LinkedList<>(); 2 //----- 添加操作 ---- 3 list.add("1"); 4 list.add("2"); 5 list.add("3"); 6 7 // 将 "4" 添加到第一个位置 8 list.add(0, "4");
2)addFirst、removeFirst 和 getFirst 方法
分别为将元素添加到第一个位置,删除第一个位置的元素,获取第一个位置的元素
System.out.println("\nTest \"addFirst(), removeFirst(), getFirst()\""); // 将 "10" 添加到第一个位置 list.addFirst("10"); System.out.println("list: " + list); // 将第一个元素删除 System.out.println("list.removeFirst(): " + list.removeFirst()); System.out.println("list: " + list); // 获取第一个元素 System.out.println("list.getFirst(): " + list.getFirst());
结果为:
Test "addFirst(), removeFirst(), getFirst()" list: [10, 4, 1, 2, 3] list.removeFirst(): 10 list: [4, 1, 2, 3] list.getFirst(): 4
3)offerFirst、pollFirst 和 peekFirst 方法
比上面的上个方法多了个返回值
System.out.println("\nTest \"offerFirst(), pollFirst(), peekFirst()\""); // 将 "10" 添加到第一个位置,返回 true System.out.println(list.offerFirst("10")); System.out.println("list: " + list); // 将第一个元素删除,失败则返回 null System.out.println("list.pollFirst(): " + list.pollFirst()); System.out.println("list: " + list); // 获取第一个元素,失败则返回 null System.out.println("list.peekFirst(): " + list.peekFirst());
结果为:
Test "offerFirst(), pollFirst(), peekFirst()" true list: [10, 4, 1, 2, 3] list.pollFirst(): 10 list: [4, 1, 2, 3] list.peekFirst(): 4
4)addLast、removeLast 和 getLast 方法
分别为添加一个元素到最后一个位置,删除最后一个位置的元素以及获取最后一个位置的元素
System.out.println("\nTest \"addLast(), removeLast(), getLast()\""); // 将 "20" 添加到最后一个位置 list.addLast("20"); System.out.println("list: " + list); // 将最后一个元素删除 System.out.println("list.removeLast(): " + list.removeLast()); System.out.println("list: " + list); // 获取最后一个元素 System.out.println("list.getLast(): " + list.getLast());
结果为:
Test "addLast(), removeLast(), getLast()" list: [4, 1, 2, 3, 20] list.removeLast(): 20 list: [4, 1, 2, 3] list.getLast(): 3
5)offerLast、pollLast 和 peekLast 方法
同理,比上面的方法多个返回值
System.out.println("\nTest \"offerLast(), pollLast(), peekLast()\""); // 将 "20" 添加到第一个位置,返回 true list.offerLast("20"); System.out.println("list: " + list); // 将第一个元素删除,失败则返回 null System.out.println("list.pollLast(): " + list.pollLast()); System.out.println("list: " + list); // 获取第一个元素,失败则返回 null System.out.println("list.peekLast(): " + list.peekLast());
结果为:
Test "offerLast(), pollLast(), peekLast()" list: [4, 1, 2, 3, 20] list.pollLast(): 20 list: [4, 1, 2, 3] list.peekLast(): 3
6)set 和 get 方法
// 将第 3 个元素设置为 300,不建议在 LinkedList 中使用此操作,因为效率低 list.set(2, "300"); // 获取第 3 个元素,不建议在 LinkedList 中使用此操作,因为效率低 System.out.println("\nget(2): " + list.get(2)); System.out.println("list: " + list);
结果为:
get(2): 300
list: [4, 1, 300, 3]
7)toArray 方法
有两个实现:toArray(), toArray(T[] a)
第二个不需要进行强制类型转换,而且转换后的数组长度和 a 的长度有关,小于等于 list 的 size,则返回 list 的元素,大于则补 null
// 将 LinkedList 转换为数组 String[] arr = list.toArray(new String[list.size()]); for (String str : arr) { System.out.println("str: " + str); }
结果为:
str: 4 str: 1 str: 300 str: 3
如果参数数组长度大于 list 的 size,则返回 null
// 将 LinkedList 转换为数组 String[] arr = list.toArray(new String[list.size() + 5]); for (String str : arr) { System.out.println("str: " + str); }
结果为:
str: 4 str: 1 str: 300 str: 3 str: null str: null str: null str: null str: null
8)size、clear 和 isEmpty 方法
// 输出大小 System.out.println("size: " + list.size()); // 清空 LinkedList list.clear(); // 判断 LinkedList 是否为空 System.out.println("isEmpty(): " + list.isEmpty() + "\n");
结果为:
size: 4 isEmpty(): true
二、数组列表 —— ArrayList
1、构造方法
ArrayList 是 Java 中的动态数组,底层实现就是对象数组,只不过数组的容量会根据情况来改变。
它有个带 int 类型参数的构造方法,根据传入的参数,扩展初始化的数组容量,这个方法是推荐使用的,因为如果预先知道数组的容量,可以设置好初始值,而不用等每次容量不够而扩容,减少 Arrays.copyOf 的次数:
它的很多方法的实现都是依靠 Arrays 这个工具类完成的,足以体现它与数组之间的密切关系。
比如有个带 Collection 类型的构造方法,实现如下:
2、常用方法
1) trimToSize 方法
Trims the capacity of this ArrayList instance to be the list's current size.
An application can use this operation to minimize the storage of an ArrayList instance.
该方法可以去掉 ArrayList 占用的多余的空间或内存,因为 ArrayList 每次扩容后总会有所剩余,如果数组很大,占用的多余的空间会比较大,内存不够时可以使用此方法。
2)ensureCapacity 方法
public void ensureCapacity(int minCapacity)
Increases the capacity of this ArrayList instance, if necessary, to ensure that it can hold at least the number of elements specified by the minimum capacity argument.
除了在初始化 ArrayList 的时候可以事先定义一个给定的容量之外,还可以用此方法提高 ArrayList 的初始化速度。看下面的例子:
1 public static void main(String[] args) { 2 int n = 100000; 3 String str = "hello google"; 4 5 // 没有调用 ensureCapacity() 方法初始化 ArrayList 对象 6 ArrayListlist = new ArrayList<>(); 7 long startTime = System.currentTimeMillis(); 8 for (int i = 0; i <= n; i++) { 9 list.add(str); 10 } 11 long endTime = System.currentTimeMillis(); 12 System.out.println("time: " + (endTime - startTime) + " ms"); 13 14 // 调用 ensureCapacity() 方法初始化 ArrayList 对象 15 list = new ArrayList<>(); 16 startTime = System.currentTimeMillis(); 17 list.ensureCapacity(n); 18 for (int i = 0; i < n; i++) { 19 list.add(str); 20 } 21 endTime = System.currentTimeMillis(); 22 System.out.println("time: " + (endTime - startTime) + " ms"); 23 }
结果为:
3)isEmpty 方法
注意此方法是判断是否为空,不是是否为 null
public boolean isEmpty() { return size == 0; }
4)indexOf 、lastIndexOf 和 contain 方法
indexOf 方法返回 list 中首次出现给定对象的索引值(从 0 开始),如果不存在则返回 -1。
lastIndexOf 方法返回 list 中最后一次出现给定对象的索引值(从 size - 1 开始),如果不存在则返回 -1。
contain 方法 参数为 Object o,判断 list 中是否包含给定的对象,存在则返回 true,源码如下:
5)add,get 和 set 方法
三个很简单的方法,区别在于:add 方法是数组长度 +1,将给定对象放在最后的位置,set 方法是替换指定索引位置的元素,get 方法则是获取指定索引位置的元素。
6)remove 方法
删除指定索引位置的元素或者指定元素,不推荐使用,对数组操作比较复杂,如果你使用了此方法,说明你应该考虑用 LinkedList 了。
3、最佳使用建议
1)ArrayList 是 Array 的复杂版本
ArrayList 内部封装了一个 Object 类型的数组,从一般的意义来说,它和数组没有本质的差别,甚至于 ArrayList 的许多方法,如 Index、IndexOf、Contains、Sort 等都是在内部数组的基础上直接调用 Array 的对应方法。
2)内部的 Object 类型的影响
对于一般引用类型来说,这部分的影响不大,但是对于值类型,往 ArrayList 里面添加和修改元素,都会引起装箱和拆箱操作,频繁的操作可能会影响一部分效率。
3)数组扩容
这是对 ArrayList 效率影响比较大的一个因素。
每当执行 Add、AddRange、Insert、InsertRange 等添加元素的方法,都会检查内部数组的容量是否不够了,如果是,它就会以当前容量的两倍来重新构建一个数组,将旧元素 Copy 到新数组中,然后丢弃旧数组,在这个临界点的扩容操作,应该来说是比较影响效率的。
例 1:比如,一个可能有 200 个元素的数据动态添加到一个以默认 16 个元素大小创建的 ArrayList 中,将会经过:
16*2*2*2*2 = 256
四次的扩容才会满足最终的要求,那么如果一开始就以 ArrayList list = new ArrayList(210) 的方式创建 ArrayList,不仅会减少 4 次数组创建和 Copy 的操作,还会减少内存使用。
例 2:预计有 30 个元素而创建了一个 ArrayList:
ArrayList List = new ArrayList(30);
在执行过程中,加入了 31 个元素,那么数组会扩充到 60 个元素的大小,而这时候不会有新的元素再增加进来,而且有没有调用 TrimSize 方法,那么就有 1 次扩容的操作,并且浪费了 29 个元素大小的空间。如果这时候,用 ArrayList list = new ArrayList(40) 那么一切都解决了。
所以说,正确的预估可能的元素,并且在适当的时候调用 TrimSize 方法是提高 ArrayList 使用效率的重要途径。
前言:凡是使用 Java 编程的,几乎肯定会用到集合框架,比如 ArrayList、LinkedList、HashSet、HashMap 等,集合框架的代码绝对是大师级的实现,所以为了更好地使用集合框架,我们有必要系统地学习下集合框架的内容。
一、接口综述
集合框架的整体结构图如下(摘自《Thinking In Java》):
上图也许有些复杂,因为包含了很多我们很少用到的抽象类,其实,只需要记住下面两张图即可(摘自张龙的 collection 集合教程):
Collection 接口里定义了如下操作集合元素的方法:
Iterator 接口也是 Java 集合框架的成员,但它与 Collection 系列、Map 系列的集合不一样:Collection 系列集合、Map 系列集合主要用于盛装其他对象,而 Iterator 则主要用于遍历(即迭代访问)Collection 集合中的元素,Iterator 对象也被称为迭代器。
Iterator 接口包含 3 个方法:
1 public interface Iterator{ 2 E next(); // 返回集合里的下一个元素 3 boolean hasNext(); // 如果被迭代的集合元素还没有被遍历,则返回 true 4 void remove(); // 删除集合里上一次 next 方法返回的元素 5 }
通过反复调用 next 方法,可以逐个访问集合中的每个元素。但是,如果到达了集合的末尾,next 方法将抛出一个 NoSuchElementException。因此,需要在调用 next 之前调用 hasNext 方法。如果迭代器对象还有多个供访问的元素,则返回 true。如果想要查看集合中的所有元素,就请求一个迭代器,并在 hasNext 返回 true 时反复地调用 next 方法。例如:
Collectionc = ...; Iterator iter = c.iterator(); while (iter.hasNext()) { String element = iter.next(); do something element }
不过,从 Java SE 5.0 起,这个循环可以采用一种更加优雅的缩写方式。用 "for each" 循环可以更加简练地表达同样的循环操作:
for (String element : c) { do something else }
PS:编程老手会注意到:Iterator 接口的 next 和 hasNext 方法与 Enumeration 接口的 nextElement 和 hasElement 方法的作用一样,但是可能是 Java 集合类库的设计者觉得这个方法名太长,于是引入了具有较短方法的新接口。
注意:这里还有个有用的类推。可以将 Iterator.next 与 InputStream.read 看作为等效的。从数据流中读取一个字节,就会自动地 "消耗掉" 这个字节。下一个调用 read 将会消耗并返回输入的下一个字节,用同样的方式,反复地调用 next 就可以读取集合中所有元素。
Iterator 接口的 remove 方法要和 next 方法一起使用,如果调用 remove 之前没有调用 next 是不合法的,会抛出一个 IllegalStateException 异常。
如果想删除相邻的两个元素,需要这样:
it.next();
it.remove();
it.next();
it.remove();
将接口是空洞的,因为没有具体的精彩的实现,所有接下来还是具体介绍 集合框架中的几个常用的实现类: