Java集合(2):LinkedList

一.LinkedList介绍

  LinkedList也和ArrayList一样实现了List接口,但是它执行插入和删除操作时比ArrayList更加高效,因为它是基于链表的。基于链表也决定了它在随机访问方面要比ArrayList逊色一点。

  除此之外,LinkedList还提供了一些可以使其作为栈、队列、双端队列的方法。这些方法中有些彼此之间只是名称的区别,以使得这些名字在特定的上下文中显得更加的合适。

1.LinkedList的继承关系

public class LinkedList<E>
     extends AbstractSequentialList<E>
     implements List<E>, Deque<E>, Cloneable, java.io.Serializable

LinkedList的类图关系如下:

  

  • LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
  • LinkedList 实现 List 接口,能对它进行队列操作。
  • LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
  • LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
  • LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
  • LinkedList 是非同步的。

2.LinkedList数据结构原理

LinkedList底层的数据结构是基于双向循环链表的,且头结点中不存放数据

二.LinkedList源码解析

1.私有属性

LinkedList中之定义了两个属性:

private transient Entry<E> header = new Entry<E>(null, null, null);
private transient int size = 0;

  header是双向链表的头节点,不包含数据,它是双向链表节点所对应的类Entry的实例。Entry中包含成员变量: previous, next, element。其中,previous是该节点的上一个节点,next是该节点的下一个节点,element是该节点所包含的值。
  size是双向链表中节点实例的个数。

2.节点类即Entry类

 1 private static class Entry<E> {
 2     E element;
 3     Entry<E> next;
 4     Entry<E> previous;
 5
 6     Entry(E element, Entry<E> next, Entry<E> previous) {
 7         this.element = element;
 8         this.next = next;
 9         this.previous = previous;
10    }
11 }

定义了存储的元素、前一个元素、后一个元素,这就是双向链表的节点的定义,每个节点只知道自己的前一个节点和后一个节点。

3.LinkedList的构造方法

1 public LinkedList() {
2     header.next = header.previous = header;
3 }
4
5
6 public LinkedList(Collection<? extends E> c) {
7     this();
8    addAll(c);
9 }

LinkedList提供了两个构造方法。

  第一个构造方法不接受参数,将header实例的previous和next全部指向header实例(注意,这个是一个双向循环链表,如果不是循环链表,空链表的情况应该是header节点的前一节点和后一节点均为null),这样整个链表其实就只有header一个节点,用于表示一个空的链表。执行完构造函数后,header实例自身形成一个闭环。

  第二个构造方法接收一个Collection参数c,调用第一个构造方法构造一个空的链表,之后通过addAll将c中的元素全部添加到链表中。

4.元素添加

 1 // 将元素(E)添加到LinkedList中
 2      public boolean add(E e) {
 3          // 将节点(节点数据是e)添加到表头(header)之前。
 4          // 即,将节点添加到双向链表的末端。
 5          addBefore(e, header);
 6          return true;
 7      }
 8
 9      public void add(int index, E element) {
10          addBefore(element, (index==size ? header : entry(index)));
11      }
12
13     private Entry<E> addBefore(E e, Entry<E> entry) {
14          Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
15          newEntry.previous.next = newEntry;
16          newEntry.next.previous = newEntry;
17          size++;
18          modCount++;
19          return newEntry;
20     }

  addBefore(E e,Entry<E> entry)方法是个私有方法,所以无法在外部程序中调用(当然,这是一般情况,你可以通过反射上面的还是能调用到的)。

  addBefore(E e,Entry<E> entry)先通过Entry的构造方法创建e的节点newEntry(包含了将其下一个节点设置为entry,上一个节点设置为entry.previous的操作,相当于修改newEntry的“指针”),之后修改插入位置后newEntry的前一节点的next引用和后一节点的previous引用,使链表节点间的引用关系保持正确。之后修改和size大小和记录modCount,然后返回新插入的节点。

  总结,addBefore(E e,Entry<E> entry)实现在entry之前插入由e构造的新节点。而add(E e)实现在header节点之前插入由e构造的新节点。

另外,还有addFirst和addLast方法

1 public void addFirst(E e) {
2      addBefore(e, header.next);
3  }
4
5  public void addLast(E e) {
6      addBefore(e, header);
7  }

  addFrist(E e)只需实现在header元素的下一个元素之前插入,即示意图中的一号之前。addLast(E e)只需在实现在header节点前(因为是循环链表,所以header的前一个节点就是链表的最后一个节点)插入节点(插入后在2号节点之后)。

5.删除数据remove()

  LinkedList中删除数据的方法有很多。remove(),remove(int index),remove(Object o), removeFirst(),removeLast(),removeFirstOccurrence(),removeLastOccurence()等。几个remove方法最终都是调用了一个私有方法:remove(Entry<E> e),只是其他简单逻辑上的区别。下面分析remove(Entry<E> e)方法。

 1 private E remove(Entry<E> e) {
 2     if (e == header)
 3         throw new NoSuchElementException();
 4     // 保留将被移除的节点e的内容
 5     E result = e.element;
 6     // 将前一节点的next引用赋值为e的下一节点
 7     e.previous.next = e.next;
 8     // 将e的下一节点的previous赋值为e的上一节点
 9     e.next.previous = e.previous;
10     // 上面两条语句的执行已经导致了无法在链表中访问到e节点,而下面解除了e节点对前后节点的引用
11     e.next = e.previous = null;
12     // 将被移除的节点的内容设为null
13     e.element = null;
14     // 修改size大小
15     size--;
16     modCount++;
17     // 返回移除节点e的内容
18     return result;
19 }

清空预删除节点:

e.next = e.previous = null;

e.element = null;

交给gc完成资源回收,删除操作结束。

与ArrayList比较而言,LinkedList的删除动作不需要“移动”很多数据,从而效率更高。

6.clear()和clone()方法

(1).clear()

 1 public void clear() {
 2 Entry<E> e = header.next;
 3 // e可以理解为一个移动的“指针”,因为是循环链表,所以回到header的时候说明已经没有节点了
 4 while (e != header) {
 5     // 保留e的下一个节点的引用
 6         Entry<E> next = e.next;
 7         // 接触节点e对前后节点的引用
 8         e.next = e.previous = null;
 9         // 将节点e的内容置空
10         e.element = null;
11         // 将e移动到下一个节点
12         e = next;
13 }
14 // 将header构造成一个循环链表,同构造方法构造一个空的LinkedList
15 header.next = header.previous = header;
16 // 修改size
17     size = 0;
18     modCount++;
19 }

(2).clone()

 1 public Object clone() {
 2     LinkedList<E> clone = null;
 3     try {
 4         clone = (LinkedList<E>) super.clone();
 5     } catch (CloneNotSupportedException e) {
 6         throw new InternalError();
 7    }
 8     clone.header = new Entry<E>(null, null, null);
 9     clone.header.next = clone.header.previous = clone.header;
10     clone.size = 0;
11     clone.modCount = 0;
12     for (Entry<E> e = header.next; e != header; e = e.next)
13        clone.add(e.element);
14     return clone;
15 }

  调用父类的clone()方法初始化对象链表clone,将clone构造成一个空的双向循环链表,之后将header的下一个节点开始将逐个节点添加到clone中。最后返回克隆的clone对象。

7.toArray()方法

(1).toArray()

1 public Object[] toArray() {
2     Object[] result = new Object[size];
3     int i = 0;
4     for (Entry<E> e = header.next; e != header; e = e.next)
5         result[i++] = e.element;
6     return result;
7 }

创建大小和LinkedList相等的数组result,遍历链表,将每个节点的元素element复制到数组中,返回数组。

(2).toArray(T[] a)

 1 public <T> T[] toArray(T[] a) {
 2     if (a.length < size)
 3         a = (T[])java.lang.reflect.Array.newInstance(
 4                                a.getClass().getComponentType(), size);
 5     int i = 0;
 6     Object[] result = a;
 7     for (Entry<E> e = header.next; e != header; e = e.next)
 8         result[i++] = e.element;
 9     if (a.length > size)
10         a[size] = null;
11     return a;
12 }

  先判断出入的数组a的大小是否足够,若大小不够则拓展。这里用到了发射的方法,重新实例化了一个大小为size的数组。之后将数组a赋值给数组result,遍历链表向result中添加的元素。最后判断数组a的长度是否大于size,若大于则将size位置的内容设置为null。返回a。

  从代码中可以看出,数组a的length小于等于size时,a中所有元素被覆盖,被拓展来的空间存储的内容都是null;若数组a的length的length大于size,则0至size-1位置的内容被覆盖,size位置的元素被设置为null,size之后的元素不变。

8.遍历数据:Iterator()

除了Entry,LinkedList还有一个内部类:ListItr。

ListItr实现了ListIterator接口,可知它是一个迭代器,通过它可以遍历修改LinkedList。

在LinkedList中提供了获取ListItr对象的方法:listIterator(int index)。

下面详细分析ListItr。

  1 private class ListItr implements ListIterator<E> {
  2 // 最近一次返回的节点,也是当前持有的节点
  3     private Entry<E> lastReturned = header;
  4     // 对下一个元素的引用
  5     private Entry<E> next;
  6     // 下一个节点的index
  7     private int nextIndex;
  8     private int expectedModCount = modCount;
  9     // 构造方法,接收一个index参数,返回一个ListItr对象
 10     ListItr(int index) {
 11         // 如果index小于0或大于size,抛出IndexOutOfBoundsException异常
 12         if (index < 0 || index > size)
 13         throw new IndexOutOfBoundsException("Index: "+index+
 14                             ", Size: "+size);
 15         // 判断遍历方向
 16         if (index < (size >> 1)) {
 17         // next赋值为第一个节点
 18         next = header.next;
 19         // 获取指定位置的节点
 20         for (nextIndex=0; nextIndex<index; nextIndex++)
 21             next = next.next;
 22         } else {
 23 // else中的处理和if块中的处理一致,只是遍历方向不同
 24         next = header;
 25         for (nextIndex=size; nextIndex>index; nextIndex--)
 26             next = next.previous;
 27        }
 28    }
 29     // 根据nextIndex是否等于size判断时候还有下一个节点(也可以理解为是否遍历完了LinkedList)
 30     public boolean hasNext() {
 31         return nextIndex != size;
 32    }
 33     // 获取下一个元素
 34     public E next() {
 35        checkForComodification();
 36         // 如果nextIndex==size,则已经遍历完链表,即没有下一个节点了(实际上是有的,因为是循环链表,任何一个节点都会有上一个和下一个节点,这里的没有下一个节点只是说所有节点都已经遍历完了)
 37         if (nextIndex == size)
 38         throw new NoSuchElementException();
 39         // 设置最近一次返回的节点为next节点
 40         lastReturned = next;
 41         // 将next“向后移动一位”
 42         next = next.next;
 43         // index计数加1
 44         nextIndex++;
 45         // 返回lastReturned的元素
 46         return lastReturned.element;
 47    }
 48
 49     public boolean hasPrevious() {
 50         return nextIndex != 0;
 51    }
 52     // 返回上一个节点,和next()方法相似
 53     public E previous() {
 54         if (nextIndex == 0)
 55         throw new NoSuchElementException();
 56
 57         lastReturned = next = next.previous;
 58         nextIndex--;
 59        checkForComodification();
 60         return lastReturned.element;
 61    }
 62
 63     public int nextIndex() {
 64         return nextIndex;
 65    }
 66
 67     public int previousIndex() {
 68         return nextIndex-1;
 69    }
 70     // 移除当前Iterator持有的节点
 71     public void remove() {
 72            checkForComodification();
 73             Entry<E> lastNext = lastReturned.next;
 74             try {
 75                 LinkedList.this.remove(lastReturned);
 76             } catch (NoSuchElementException e) {
 77                 throw new IllegalStateException();
 78            }
 79         if (next==lastReturned)
 80                 next = lastNext;
 81             else
 82         nextIndex--;
 83         lastReturned = header;
 84         expectedModCount++;
 85    }
 86     // 修改当前节点的内容
 87     public void set(E e) {
 88         if (lastReturned == header)
 89         throw new IllegalStateException();
 90        checkForComodification();
 91         lastReturned.element = e;
 92    }
 93     // 在当前持有节点后面插入新节点
 94     public void add(E e) {
 95        checkForComodification();
 96         // 将最近一次返回节点修改为header
 97         lastReturned = header;
 98        addBefore(e, next);
 99         nextIndex++;
100         expectedModCount++;
101    }
102     // 判断expectedModCount和modCount是否一致,以确保通过ListItr的修改操作正确的反映在LinkedList中
103     final void checkForComodification() {
104         if (modCount != expectedModCount)
105         throw new ConcurrentModificationException();
106    }
107 }

下面是一个ListItr的使用实例。

 1 LinkedList<String> list = new LinkedList<String>();
 2         list.add("First");
 3         list.add("Second");
 4         list.add("Thrid");
 5        System.out.println(list);
 6         ListIterator<String> itr = list.listIterator();
 7         while (itr.hasNext()) {
 8            System.out.println(itr.next());
 9        }
10         try {
11             System.out.println(itr.next());// throw Exception
12         } catch (Exception e) {
13             // TODO: handle exception
14        }
15         itr = list.listIterator();
16        System.out.println(list);
17        System.out.println(itr.next());
18         itr.add("new node1");
19        System.out.println(list);
20         itr.add("new node2");
21        System.out.println(list);
22        System.out.println(itr.next());
23         itr.set("modify node");
24        System.out.println(list);
25        itr.remove();
26         System.out.println(list);
 1 结果:
 2 [First, Second, Thrid]
 3 First
 4 Second
 5 Thrid
 6 [First, Second, Thrid]
 7 First
 8 [First, new node1, Second, Thrid]
 9 [First, new node1, new node2, Second, Thrid]
10 Second
11 [First, new node1, new node2, modify node, Thrid]
12 [First, new node1, new node2, Thrid]

LinkedList还有一个提供Iterator的方法:descendingIterator()。该方法返回一个DescendingIterator对象。DescendingIterator是LinkedList的一个内部类。

9.其他方法

LinkedList中还有许多其他方法,在此不一一介绍。

contains(Object o),element(),getFirst(),get(int index),getLast(),set(int index,E element), lastIndexOf(Object o),

offer(E e)  在链表尾部插入元素,offerFirst(E e)  在链表开头插入元素,offerLast(E e)  在链表末尾插入元素    这三个方法都调用了相应的add方法。

peek(),peekFirst(),peekLast()  调用了对应的get方法

poll(),pollFirst(),pollLast()  poll相关的方法都是获取并移除某个元素。都是和remove操作相关。

pop(),push(E e)  弹出一个元素和压入一个元素,仅仅是调用了removeFirst()和addFirst()方法。

参考:http://www.cnblogs.com/ITtangtang/p/3948610.html#a9

http://www.cnblogs.com/hzmark/archive/2012/12/25/LinkedList.html

时间: 2024-10-13 06:53:57

Java集合(2):LinkedList的相关文章

死磕 java集合之LinkedList源码分析

问题 (1)LinkedList只是一个List吗? (2)LinkedList还有其它什么特性吗? (3)LinkedList为啥经常拿出来跟ArrayList比较? (4)我为什么把LinkedList放在最后一章来讲? 简介 LinkedList是一个以双向链表实现的List,它除了作为List使用,还可以作为队列或者栈来使用,它是怎么实现的呢?让我们一起来学习吧. 继承体系 通过继承体系,我们可以看到LinkedList不仅实现了List接口,还实现了Queue和Deque接口,所以它既

java集合-LinkedList

LinkedList概念与简单实例 LinkedList类是双向列表,列表中的每个节点都包含了对前一个和后一个元素的引用. 1:LinkedList提供了4个不同位置的添加数据的方法,分别为链头插入,链尾插入,节点前插入,节点后插入 2:由于LinkedList是双向链表,在查询数据方面提供了"从前往后"和"从后往前"两个查询方法 3:如果数据量大,删除频繁,只能用LinkedList. 特别注意: list.get(i),LinkedList的底层是一个链表,随机

Java集合类库 LinkedList 源码解析

基于JDK 1.7,和ArrayList进行比较分析 Java已经有了ArrayList,用来存放元素,对元素的操作都很方便.为什么还会有LinkedList呢?我们都知道ArrayList获取元素很快,但是插入一个元素很慢,因为ArrayList底层维护的是一个数组,往数组中的某个位置插入一个元素,是很消耗资源的. 而LinkedList插入元素很快,获取任意位置的元素却很慢.这是为什么呢?底层又是怎样实现的呢? 1.继承关系 LinkedList的继承关系图: LinkedList继承的是A

Java 集合之LinkedList源码分析

1.介绍 链表是数据结构中一种很重要的数据结构,一个链表含有一个或者多个节点,每个节点处理保存自己的信息之外还需要保存上一个节点以及下一个节点的指针信息.通过链表的表头就可以访问整个链表的信息.Java API中提供了链表的Java实现---LinkedList下.LinkedList是通过节点的连接实现链表的数据结构,向linkedList中插入或删除元素的速度是特别快,而随机访问的速度相对较慢,这个是由于链表本身的性质造成的,在链表中,每个节点都包含了前一个节点的引用,后一个节点的引用和节点

【JAVA集合】LinkedList

以下内容基于jdk1.7.0_79源码: 什么是LinkedList List接口的链表实现,并提供了一些队列,栈,双端队列操作的方法: LinkedList补充说明 与ArrayList对比,LinkedList插入和删除操作更加高效,随机访问速度慢: 可以作为栈.队列.双端队列数据结构使用: 非同步,线程不安全: 与ArrayList.Vector一样,LinkedList的内部迭代器存在“快速失败行为”: 支持null元素.有顺序.元素可以重复: LinkedList继承的类以及实现的接口

从源码分析java集合【LinkedList】

LinkedList是一个双端链表,他继承了AbstractSequentaiList顺序列表,实现了List,Deque,Cloneable,和Serializable接口.Deque是双端队列的接口,LinkedList有记录头的 first 和 尾的 last,所以我们可以对队列的两端进行操作.它还实现了Cloneable和Serializeble接口,它们分别是实现队列进行拷贝和序列化的接口. LinkedList的节点的结构是: private static class Node<E>

java集合之LinkedList

LinkedList:位于java.util包下 1 LinkedList概述 特点:底层数据结构是链表,增删快查询慢:实现非同步,线程不安全,效率高:有序(存取顺序一致):允许存储重复值:允许存储null值:易于实现堆栈.队列.双向队列结构: 常用构造: public LinkedList(): 空参构造,构建一个空列表. 常用方法:和ArrayList差不多,此处仅列出特有的功能方法. 1)添加功能: public addFirst(E e) :将指定元素插入此列表的开头. public a

Java集合四LinkedList

LinkedList简介 继承于AbstractSequentialList的双向链表,可以被当做堆栈.队列或双端队列进行操作. LinkedList本质上是一个双向链表,实现了Dequeue接口. LinkedList包含两个重要的成员:header和size. header是双向链表的表头,它是双向链表节点所对应的类Node的实例.Node中包含成员变量:prev.next.item.其中prev是该节点的上一个节点,next是该节点的下一个节点,item是该节点所包含的值. private

Java集合之LinkedList常见实例操作,实例说明

一.LinkedList常见操作 package List相关; /** * LinkedList 链接列表 * LinkedList:底层使用的链表数据结构.特点:增删速度快,查询速度稍慢: */ import java.util.Iterator; import java.util.LinkedList; class linkedlist_test { public static void main(String[] args){ LinkedList linkedlist = new Li

【Java集合】-- LinkedList源码解析

目录 继承体系 数据结构 源码解析 1.属性 2.构造方法 LinkedList() LinkedList(Collection<? extends E> c) 3.添加元素 add(E e) addFirst(E e) addLast(E e) add(int index, E element) offer(E e) offerFirst(E e) offerLast(E e) 总结 4.获取元素 get(int index) getFirst() getLast() peek() 5.删除