title: java集合之LinkedList
tags: java集合
author: 辰砂
一. LinkedList概述:
List 接口的链接列表实现。实现所有可选的列表操作,并且允许所有元素(包括 null)。除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。
注意,此实现不是同步的。如果不存在这样的对象,则应该使用 Collections.synchronizedList 方法来“包装”该列表。最好在创建时完成这一操作,以防止对列表进行意外的不同步访问,如下所示:
List list = Collections.synchronizedList(new LinkedList(...));
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
LinkedList 实现 List 接口,能对它进行队列操作。
LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
LinkedList 是非同步的
二.LinkedList的用法 (参考优秀博文)
public class LinkedListTest { public static void main(String[] args) { // 测试LinkedList的API testLinkedListAPIs() ; // 将LinkedList当作 LIFO(后进先出)的堆栈 useLinkedListAsLIFO(); // 将LinkedList当作 FIFO(先进先出)的队列 useLinkedListAsFIFO(); } /* * 测试LinkedList中部分API */ private static void testLinkedListAPIs() { String val = null; //LinkedList llist; //llist.offer("10"); // 新建一个LinkedList LinkedList llist = new LinkedList(); //---- 添加操作 ---- // 依次添加1,2,3 llist.add("1"); llist.add("2"); llist.add("3"); // 将“4”添加到第一个位置 llist.add(1, "4"); System.out.println("\nTest \"addFirst(), removeFirst(), getFirst()\""); // (01) 将“10”添加到第一个位置。 失败的话,抛出异常! llist.addFirst("10"); System.out.println("llist:"+llist); // (02) 将第一个元素删除。 失败的话,抛出异常! System.out.println("llist.removeFirst():"+llist.removeFirst()); System.out.println("llist:"+llist); // (03) 获取第一个元素。 失败的话,抛出异常! System.out.println("llist.getFirst():"+llist.getFirst()); System.out.println("\nTest \"offerFirst(), pollFirst(), peekFirst()\""); // (01) 将“10”添加到第一个位置。 返回true。 llist.offerFirst("10"); System.out.println("llist:"+llist); // (02) 将第一个元素删除。 失败的话,返回null。 System.out.println("llist.pollFirst():"+llist.pollFirst()); System.out.println("llist:"+llist); // (03) 获取第一个元素。 失败的话,返回null。 System.out.println("llist.peekFirst():"+llist.peekFirst()); System.out.println("\nTest \"addLast(), removeLast(), getLast()\""); // (01) 将“20”添加到最后一个位置。 失败的话,抛出异常! llist.addLast("20"); System.out.println("llist:"+llist); // (02) 将最后一个元素删除。 失败的话,抛出异常! System.out.println("llist.removeLast():"+llist.removeLast()); System.out.println("llist:"+llist); // (03) 获取最后一个元素。 失败的话,抛出异常! System.out.println("llist.getLast():"+llist.getLast()); System.out.println("\nTest \"offerLast(), pollLast(), peekLast()\""); // (01) 将“20”添加到第一个位置。 返回true。 llist.offerLast("20"); System.out.println("llist:"+llist); // (02) 将第一个元素删除。 失败的话,返回null。 System.out.println("llist.pollLast():"+llist.pollLast()); System.out.println("llist:"+llist); // (03) 获取第一个元素。 失败的话,返回null。 System.out.println("llist.peekLast():"+llist.peekLast()); // 将第3个元素设置300。不建议在LinkedList中使用此操作,因为效率低! llist.set(2, "300"); // 获取第3个元素。不建议在LinkedList中使用此操作,因为效率低! System.out.println("\nget(3):"+llist.get(2)); // ---- toArray(T[] a) ---- // 将LinkedList转行为数组 String[] arr = (String[])llist.toArray(new String[0]); for(String str:arr) { System.out.println("str:"+str); } // 输出大小 System.out.println("size:"+llist.size()); // 清空LinkedList llist.clear(); // 判断LinkedList是否为空 System.out.println("isEmpty():"+llist.isEmpty()+"\n"); } /** * 将LinkedList当作 LIFO(后进先出)的堆栈 */ private static void useLinkedListAsLIFO() { System.out.println("\nuseLinkedListAsLIFO"); // 新建一个LinkedList LinkedList stack = new LinkedList(); // 将1,2,3,4添加到堆栈中 stack.push("1"); stack.push("2"); stack.push("3"); stack.push("4"); // 打印“栈” System.out.println("stack:"+stack); // 删除“栈顶元素” System.out.println("stack.pop():"+stack.pop()); // 取出“栈顶元素” System.out.println("stack.peek():"+stack.peek()); // 打印“栈” System.out.println("stack:"+stack); } /** * 将LinkedList当作 FIFO(先进先出)的队列 */ private static void useLinkedListAsFIFO() { System.out.println("\nuseLinkedListAsFIFO"); // 新建一个LinkedList LinkedList queue = new LinkedList(); // 将10,20,30,40添加到队列。每次都是插入到末尾 queue.add("10"); queue.add("20"); queue.add("30"); queue.add("40"); // 打印“队列” System.out.println("queue:"+queue); // 删除(队列的第一个元素) System.out.println("queue.remove():"+queue.remove()); // 读取(队列的第一个元素) System.out.println("queue.element():"+queue.element()); // 打印“队列” System.out.println("queue:"+queue); }
三.源码解读
1.数据结构
LinkedList 是一个双向链表。内部类 Node 是 LinkedList 中的基本数据结构,包含当前节点值,上一个节点得引用,和下个节点的引用。
// 链表中有多少个节点,默认为 0 transient int size = 0;// 头节点transient Node<E> first;// 尾节点transient Node<E> last; 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; } }
2.构造方法
比较简单,默认无参构造,和一个 Collection 参数的构造( 将里面元素按顺序前后连接,修改节点个数,并且操作次数 + 1 )。
public LinkedList() { } /** * Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @param c the collection whose elements are to be placed into this list * @throws NullPointerException if the specified collection is null */ public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
3.添加方法ADD
// 尾部插入public boolean add(E e) { // 去为节点加 linkLast(e); return true; }// 将指定的元素防止在链表的尾节点,以前的尾节点变成它前面的节点,如果上个尾节点为null,说明以前是的空链表。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; // 添加一个,我们就需要把size增加 size++; modCount++; } add(int index, E element) public void add(int index, E element) { // 边界校验 checkPositionIndex(index); if (index == size) linkLast(element); else linkBefore(element, node(index)); } /** * Returns the (non-null) Node at the specified element index. */ Node<E> node(int index) { // assert isElementIndex(index);// 双链表可以分别从 头节点 或者尾节点开始遍历,计算它是在前面一半,还是在后面的位置,决定遍历方式。// 这也是LinkedList 为什么要使用双向链表,提升了使用游标操作链表的效率。 if (index < (size >> 1)) { Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } } /** * Inserts element e before non-null Node succ. */ void linkBefore(E e, Node<E> succ) { // assert succ != null; final Node<E> pred = succ.prev; final Node<E> newNode = new Node<>(pred, e, succ); succ.prev = newNode; if (pred == null) first = newNode; else pred.next = newNode; size++; modCount++; }
检查索引是否越界,虽然 ListedList 中没有索引概念;
如果 index 和 size 相同,则在尾节点上加上元素;
不相同的话,先去遍历链表查找到索引位置的节点,然后在它的前面插入节点。
1.s->prior=p->prior; 2. p->prior->next=s; 3. s->next=p; 4. p->prior=s;
4.获取元素Get
public E get(int index) { // 检查索引越界;// 跟上面的一样,查找该索引位置的节点,然后获取它的元素。 checkElementIndex(index); return node(index).item; }
5.删除元素Remove
public E remove() { return removeFirst(); } // 移除头节点 public E removeFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); } // 参数 f 为头节点// 将头节点指向 next 节点,如果 next节点 为 null 则链表 为 null ,链表大小减 1 ,修改次数记录加 1. 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 = next; if (next == null) last = null; else next.prev = null; size--; modCount++; return element; }public E remove(int index) { checkElementIndex(index); return unlink(node(index)); } /** * Unlinks non-null node x. */ E unlink(Node<E> x) { // assert x != null; final E element = x.item; final Node<E> next = x.next; final Node<E> prev = x.prev; // 如果本节点为头节点,头节点指向next if (prev == null) { first = next; } else { // 不是头节点,则将前节点和后节点连接起来,然后删掉本节点的引用 GC prev.next = next; x.prev = null; } // 如果是尾节点,则将尾节点指向前节点 if (next == null) { last = prev; } else { // 连接,双向链表,双方都有引用,删除自身的引用GC next.prev = prev; x.next = null; } // 删除自身 GC x.item = null; size--; modCount++; return element; }// 遍历 equals 找出 node,然后调用 unlink(Node<E> x) public boolean remove(Object o) { if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) { unlink(x); return true; } } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) { unlink(x); return true; } } } return false; }
6.更新元素Set
/** *有索引,第一件事去检查索引是否越界;根据索引找出 node; *替换 node 的元素,返回 该索引位置 Node 的旧元素的值。 *注意,Set 方法不增加LinkedList 的修改次数 */public E set(int index, E element) { checkElementIndex(index); Node<E> x = node(index); E oldVal = x.item; x.item = element; return oldVal; }
7.清空clear()
//释放所有的元素,让他们直接无引用,垃圾回收器发现这些 node 元素是不可达的时候,释放内存。// 数据恢复默认;修改次数记录加一。public void clear() { // Clearing all of the links between nodes is "unnecessary", but: // - helps a generational GC if the discarded nodes inhabit // more than one generation // - is sure to free memory even if there is a reachable Iterator for (Node<E> x = first; x != null; ) { Node<E> next = x.next; x.item = null; x.next = null; x.prev = null; x = next; } first = last = null; size = 0; modCount++; }
四、ArrayList和LinkedList比较
ArrayList的实现是基于数组,LinkedList的实现是基于双向链表。
对于随机访问,ArrayList优于LinkedList
对于插入和删除操作,LinkedList优于ArrayList
LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
参考
https://blog.wuwii.com/java-linkedlist.html#more
https://www.cnblogs.com/skywang12345/p/3308807.html