Collection探究之LinkedList
2015-10-25 09:35
246 查看
LinkedList与ArrayList一样都是List的实现类,但是它内部结构与ArrayList有着极大的不同。LinkedList内部是由一个双向的链表组成,由链表的特征我们可以知道,LinkedList它插入元素的效率非常高而要查找元素的话因为需要遍历整个链表所以性能比较差。
**
从LinkedList的定义我们可以看到,它继承自AbstractSequentialList,实现类List、Deque、Cloneable、Serializable的接口方法。其中Deque要介绍一下,它是一个双端队列,LinkedList实现了它时之后就能被当作队列来使用。
**
size是LinkedList的容量大小,first为指向LinkedList链表头,last存入LinkedList的链表尾,其中Noed为LinkedList的节点结构,代码如下:
**
LinkedList共有两个构造函数,源代码如下所示:
**
因为LinkedList继承了AbstractSequentialList实现了Deque所以这里有多种方法,但是原理都大同小异。一般在头、尾操作的都是实现Deque,而在链表中间操作的是继承自AbstractSequentialList。我们先来看一下常用的操作方法,然后再来深入其内部原理。
LinkedList就先分析那么多了,其实其中还有很多方法没有写出来,只是针对常用的方法进行了简单的介绍,有兴趣的朋友可以自己去研究一下源码。
**
1.LinkedList类定义
**public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
从LinkedList的定义我们可以看到,它继承自AbstractSequentialList,实现类List、Deque、Cloneable、Serializable的接口方法。其中Deque要介绍一下,它是一个双端队列,LinkedList实现了它时之后就能被当作队列来使用。
**
2.LinkedList的属性
**transient int size = 0; transient Node<E> first; transient Node<E> last;
size是LinkedList的容量大小,first为指向LinkedList链表头,last存入LinkedList的链表尾,其中Noed为LinkedList的节点结构,代码如下:
private static class Node<E> { //所存入的元素 E item; //存储下一个节点位置的属性 Node<E> next; //存储上一个节点位置的属性 Node<E> prev; //Node节点的构造函数 Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; }
**
3.构造函数
**LinkedList共有两个构造函数,源代码如下所示:
//空构造函数,构造一个空的LinkedList public LinkedList() { } //参数为集合,它的意思为可以放入一个集合到LinkedList内,LinkedList会将集合的各元素转化称为自身的元素 public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
**
4.常用操作方法
**因为LinkedList继承了AbstractSequentialList实现了Deque所以这里有多种方法,但是原理都大同小异。一般在头、尾操作的都是实现Deque,而在链表中间操作的是继承自AbstractSequentialList。我们先来看一下常用的操作方法,然后再来深入其内部原理。
4.1获取元素方法
//该方法用于获取LinkedList中的第一个元素 public E getFirst() { //初始化一个节点,并将LinkedList的链表头赋值给它 final Node<E> f = first; if (f == null) throw new NoSuchElementException(); //返回链表头的元素值 return f.item; } //获取LinkedList链表尾部的值 public E getLast() { //初始化一个节点,并将LinkedList的链表尾赋值给它 final Node<E> l = last; if (l == null) throw new NoSuchElementException(); //返回链表尾的元素值 return l.item; } //返回LinkedList指定位置的元素 public E get(int index) { //先检查index是否越界 checkElementIndex(index); //返回index位置处的元素值 return node(index).item; } //检查index是否越界,我们可以看到所有的方法都是层层封装,各司其职的。 private void checkElementIndex(int index) { if(!isElementIndex(index)) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } //真正检查是否越界的方法 private boolean isElementIndex(int index) { return index >= 0 && index < size; } //node方法用于根据index来查找元素 Node<E> node(int index) { //我们看到这里会先对size折半,判断index属于前半部分还是后半部分,这样只需对一半元素进行查找,提升查找的效率 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; } }
4.2删除元素
//删除链表第一个元素 public E removeFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); } //删除链表最后的元素 public E removeLast() { final Node<E> l = last; if (l == null) throw new NoSuchElementException(); return unlinkLast(l); } //我们可以看到上面两个方法都将具体的操作让unlinkFirst与unlinkLast来完成 private E unlinkFirst(Node<E> f) { //unlinkFirst传入的是一个Node节点,先把原先的元素值取出 final E element = f.item; //获取传入节点f的下一个节点 final Node<E> next = f.next; //把需要移除的节点f删除 f.item = null; f.next = null; if (next == null) last = null; else //因为删除的是第一个元素,因此将f->next元素的->prev的值赋为null,因为在f节点没有删除时,它是指向的f,现在它变成一个元素了。 next.prev = null; //LinkedList链表的长度减1 size--; modCount++; //返回被移除节点的值 return element; } //与unlinkFirst原理差不多,只是这次是移除最后一个节点,所以是最后一个节点l的上一个节点把其next置为null private E unlinkLast(Node<E> l) { final E element = l.item; final Node<E> prev = l.prev; l.item = null; l.prev = null; if (prev == null) first = null; else prev.next = null; size--; modCount++; return element; } //该方法用于移除指定位置的节点 public E remove(int index) { checkElementIndex(index); return unlink(node(index)); } //真正执行移除节点的方法 E unlink(Node<E> x) { //先初始化一个节点,将节点的元素值赋给新节点 final E element = x.item; //分别保存要移除节点x的上一节点与下一节点 final Node<E> next = x.next; final Node<E> prev = x.prev; //对要移除节点x的上一节点进行判断,若为null,那么说明x为头节点,把x的下一节点变成头节点 if (prev == null) { first = next; } else { //否则就将要移除节点的上一节点的下一节点(原来是x)指向x的下一节点(相当于把x移走,x的上下节点相连) prev.next = next; x.prev = null; } //这是判断x是否为尾节点 if (next == null) { last = prev; } else { next.prev = prev; x.next = null; } x.item = null; size--; modCount++; return element; }
4.3 添加元素
//在LinkedList链表头添加元素 public void addFirst(E e) { linkFirst(e); } //在LinkedList链表尾添加元素 public void addLast(E e) { linkLast(e); } //上述两个方法的具体操作是由linkFirst与linkLast方法来完成 private void linkFirst(E e) { //初始化一个LinkedList的节点,将LinkedList链表头赋值给它 final Node<E> f = first; //在初始化一个新的节点,把元素e与节点f传入 final Node<E> newNode = new Node<>(null, e, f); //让新的节点newNode称为LinkedList的链表头 first = newNode; if (f == null) //若原先的链表头f为空,那么这个LinkedList只有一个节点,那就是新加入的节点 last = newNode; else //若不为空,本来的头节点变成第二个节点,它的prev指向新添加的节点newNode f.prev = newNode; //LinkedList长度加1 size++; modCount++; } //与上述方法相同,只不过是在尾部添加元素 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++; modCount++; } //在LinkedList中指定位置index处添加节点 public void add(int index, E element) { //检查是否越界 checkPositionIndex(index); //判断是否要插入的位置为尾 if (index == size) linkLast(element); else linkBefore(element, node(index)); } //该方法用于在succ节点前插入节点 void linkBefore(E e, Node<E> succ) { //获取succ节点的上一节点信息 final Node<E> pred = succ.prev; //初始化要插入的新节点 final Node<E> newNode = new Node<>(pred, e, succ); //将succ节点的上一节点的下一节点位置指向新创建的节点(有点绕口,相当于在succ节点与其上一节点中间本来连着的线切断,加入一个新的节点newNode,由newNode作为中间的桥梁连接她们) succ.prev = newNode; if (pred == null) first = newNode; else pred.next = newNode; size++; modCount++; } //set方法与add方法不同,set方法是替换index位置处的节点,并不是插入 public E set(int index, E element) { checkElementIndex(index); Node<E> x = node(index); E oldVal = x.item; x.item = element; return oldVal; }
5.一般常用的方法
//将linkedList的节点转化为数组输出 public Object[] toArray() { Object[] result = new Object[size]; int i = 0; for (Node<E> x = first; x != null; x = x.next) result[i++] = x.item; return result; } //用于查找在LinkedList中元素为o的节点 public int indexOf(Object o) { int index = 0; //LinkedList中是可以存空值的,因此有两种可能 if (o == null) { //遍历整个LinkedList,查找是否与o元素相同的节点,相同则返回位置下标index for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) return index; index++; } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) return index; index++; } } return -1; } //该方法是最末尾开始向前查找第一个与o相同的值的位置 public int lastIndexOf(Object o) { int index = size; if (o == null) { for (Node<E> x = last; x != null; x = x.prev) { index--; if (x.item == null) return index; } } else { for (Node<E> x = last; x != null; x = x.prev) { index--; if (o.equals(x.item)) return index; } } return -1; }
LinkedList就先分析那么多了,其实其中还有很多方法没有写出来,只是针对常用的方法进行了简单的介绍,有兴趣的朋友可以自己去研究一下源码。
相关文章推荐
- 注册表的组织结构
- SQLSERVER的非聚集索引结构深度理解
- 调整SQLServer2000运行中数据库结构
- C#基础语法:结构和类区别详解
- 深入c# 类和结构的区别总结详解
- c#结构和类的相关介绍
- C#中结构(struct)的部分初始化和完全初始化实例分析
- C#中类与结构的区别实例分析
- C#枚举类型与结构类型实例解析
- javascript实现表现、结构、行为分离的选项卡效果!
- 实用的js 焦点图切换效果 结构行为相分离
- asp下生成目录树结构的类
- thinkphp文件引用与分支结构用法实例
- php实现的树形结构数据存取类实例
- 解析Java中的队列和用LinkedList集合模拟队列的方法
- JAVA LinkedList和ArrayList的使用及性能分析
- LinkedList学习示例模拟堆栈与队列数据结构
- Swift教程之类与结构详解
- 第三话:关于数据结构的一些概念
- ArrayList和LinkedList的主要区别