一、对于LinkedList需要掌握的八点内容

  • LinkedList的创建:即构造器
  • 往LinkedList中添加对象:即add(E)方法
  • 获取LinkedList中的单个对象:即get(int index)方法
  • 修改LinkedList中的指定索引的节点的数据set(int index, E element)
  • 删除LinkedList中的对象:即remove(E),remove(int index)方法
  • 遍历LinkedList中的对象:即iterator,在实际中更常用的是增强型的for循环去做遍历
  • 判断对象是否存在于LinkedList中:contain(E)
  • LinkedList中对象的排序:主要取决于所采取的排序算法(以后讲)

二、源码分析

2.1、LinkedList的创建

实现方式:

List<String> strList0 = new LinkedList<String>();

源代码:在读源代码之前,首先要知道什么是环形双向链表,参考《算法导论(第二版)》P207

    private transient Entry<E> header = new Entry<E>(null, null, null);//底层是双向链表,这时先初始化一个空的header节点
private transient int size = 0;//链表中的所存储的元素个数 /**
* 构造环形双向链表
*/
public LinkedList() {
header.next = header.previous = header;//形成环形双向链表
}

Entry是LinkedList的一个内部类:

    /**
* 链表节点
*/
private static class Entry<E> {
E element; //链表节点所存储的数据
Entry<E> next; //当前链表节点的下一节点
Entry<E> previous; //当前链表节点的前一个节点 Entry(E element, Entry<E> next, Entry<E> previous) {
this.element = element;
this.next = next;
this.previous = previous;
}
}

执行完上述的无参构造器后:形成的空环形双向链表如下:

其中,左上角为previous,右下角为next

2.2、往LinkedList中添加对象(add(E e))

实现方式:

strList0.add("hello");

源代码:

    /**
* 在链表尾部增加新节点,新节点封装的数据为e
*/
public boolean add(E e) {
addBefore(e, header);//在链表尾部增加新节点,新节点封装的数据为e
return true;
}
    /*
* 在链表指定节点entry后增加新节点,新节点封装的数据为e
*/
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++; //链表中元素个数+1
modCount++; //与ArrayList相同,用于在遍历时查看是否发生了add和remove操作
return newEntry;
}

在添加一个元素后的新环形双向链表如下:

在上述的基础上,再调用一次add(E)后,新的环形双向链表如下:

这里,结合着代码注释与图片去看add(E)的源代码就好。

注意:在添加元素方面LinkedList不需要考虑数组扩容和数组复制,只需要新建一个对象,但是需要修改前后两个对象的属性。

2.3、获取LinkedList中的单个对象(get(int index))

实现方式:

strList.get(0);//注意:下标从0开始

源代码:

    /**
* 返回索引值为index节点的数据,index从0开始计算
*/
public E get(int index) {
return entry(index).element;
}
    /**
* 获取指定index索引位置的节点(需要遍历链表)
*/
private Entry<E> entry(int index) {
//index:0~size-1
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index:"+index+", Size:"+size);
Entry<E> e = header;//头节点:既作为头节点也作为尾节点
if (index < (size >> 1)) {//index<size/2,则说明index在前半个链表中,从前往后找
for (int i = 0; i <= index; i++)
e = e.next;
} else {//index>=size/2,则说明index在后半个链表中,从后往前找
for (int i = size; i > index; i--)
e = e.previous;
}
return e;
}

注意:

  • 链表节点的按索引查找,需要遍历链表;而数组不需要。
  • header节点既是头节点也是尾节点
  • 双向链表的查找,先去判断索引值index是否小于size/2,若小于,从header节点开始,从前往后找;若大于等于,从header节点开始,从后往前找
  • size>>1,右移一位等于除以2;左移一位等于乘以2

2.4、修改LinkedList中指定索引的节点的数据:set(int index, E element)

使用方式:

strList.set(0, "world");

源代码:

    /**
* 修改指定索引位置index上的节点的数据为element
*/
public E set(int index, E element) {
Entry<E> e = entry(index);//查找index位置的节点
E oldVal = e.element;//获取该节点的旧值
e.element = element;//将新值赋给该节点的element属性
return oldVal;//返回旧值
}

注意:entry(int index)查看上边

2.5、删除LinkedList中的对象

2.5.1、remove(Object o)

使用方式:

strList.remove("world")

源代码:

    /**
* 删除第一个出现的指定元数据为o的节点
*/
public boolean remove(Object o) {
if (o == null) {//从前往后删除第一个null
//遍历链表
for (Entry<E> e = header.next; e != header; e = e.next) {
if (e.element == null) {
remove(e);
return true;
}
}
} else {
for (Entry<E> e = header.next; e != header; e = e.next) {
if (o.equals(e.element)) {
remove(e);
return true;
}
}
}
return false;
}
    /*
* 删除节点e
*/
private E remove(Entry<E> e) {
//header节点不可删除
if (e == header)
throw new NoSuchElementException(); E result = e.element;
//调整要删除节点的前后节点的指针指向
e.previous.next = e.next;
e.next.previous = e.previous;
//将要删除元素的三个属性置空
e.next = e.previous = null;
e.element = null; size--;//size-1
modCount++;
return result;
}

注意:

  • header节点不可删除

2.5.2、remove(int index)

使用方式:

strList.remove(0);

源代码:

    /**
* 删除指定索引的节点
*/
public E remove(int index) {
return remove(entry(index));
}

注意:

  • remove(entry(index))见上边
  • remove(Object o)需要遍历链表,remove(int index)也需要

2.6、判断对象是否存在于LinkedList中(contains(E)

源代码:

    /**
* 链表中是否包含指定数据o的节点
*/
public boolean contains(Object o) {
return indexOf(o) != -1;
}
    /**
* 从header开始,查找第一个出现o的索引
*/
public int indexOf(Object o) {
int index = 0;
if (o == null) {//从header开始,查找第一个出现null的索引
for (Entry e = header.next; e != header; e = e.next) {
if (e.element == null)
return index;
index++;
}
} else {
for (Entry e = header.next; e != header; e = e.next) {
if (o.equals(e.element))
return index;
index++;
}
}
return -1;
}

注意:

  • indexOf(Object o)返回第一个出现的元素o的索引

2.7、遍历LinkedList中的对象(iterator())

使用方式:

        List<String> strList = new LinkedList<String>();
strList.add("jigang");
strList.add("nana");
strList.add("nana2"); Iterator<String> it = strList.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}

源代码:iterator()方法是在父类AbstractSequentialList中实现的,

    public Iterator<E> iterator() {
return listIterator();
}

listIterator()方法是在父类AbstractList中实现的,

    public ListIterator<E> listIterator() {
return listIterator(0);
}

listIterator(int index)方法是在父类AbstractList中实现的,

    public ListIterator<E> listIterator(final int index) {
if (index < 0 || index > size())
throw new IndexOutOfBoundsException("Index: " + index); return new ListItr(index);
}

该方法返回AbstractList的一个内部类ListItr对象

ListItr:

    private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
cursor = index;
}

上边这个类并不完整,它继承了内部类Itr,还扩展了一些其他方法(eg.向前查找方法hasPrevious()等),至于hasNext()/next()等方法还是来自于Itr的。

Itr:

    private class Itr implements Iterator<E> {

        int cursor = 0;//标记位:标记遍历到哪一个元素
int expectedModCount = modCount;//标记位:用于判断是否在遍历的过程中,是否发生了add、remove操作 //检测对象数组是否还有元素
public boolean hasNext() {
return cursor != size();//如果cursor==size,说明已经遍历完了,上一次遍历的是最后一个元素
} //获取元素
public E next() {
checkForComodification();//检测在遍历的过程中,是否发生了add、remove操作
try {
E next = get(cursor++);
return next;
} catch (IndexOutOfBoundsException e) {//捕获get(cursor++)方法的IndexOutOfBoundsException
checkForComodification();
throw new NoSuchElementException();
}
} //检测在遍历的过程中,是否发生了add、remove等操作
final void checkForComodification() {
if (modCount != expectedModCount)//发生了add、remove操作,这个我们可以查看add等的源代码,发现会出现modCount++
throw new ConcurrentModificationException();
}
}

注:

  • 上述的Itr我去掉了一个此时用不到的方法和属性。
  • 这里的get(int index)方法参照2.3所示。

三、总结

  • LinkedList基于环形双向链表方式实现,无容量的限制
  • 添加元素时不用扩容(直接创建新节点,调整插入节点的前后节点的指针属性的指向即可)
  • 线程不安全
  • get(int index):需要遍历链表
  • remove(Object o)需要遍历链表
  • remove(int index)需要遍历链表
  • contains(E)需要遍历链表

第三章 LinkedList源码解析的更多相关文章

  1. 第三章 CopyOnWriteArrayList源码解析

    注:在看这篇文章之前,如果对ArrayList底层不清楚的话,建议先去看看ArrayList源码解析. http://www.cnblogs.com/java-zhao/p/5102342.html ...

  2. LinkedList源码解析

    LinkedList是基于链表结构的一种List,在分析LinkedList源码前有必要对链表结构进行说明.1.链表的概念链表是由一系列非连续的节点组成的存储结构,简单分下类的话,链表又分为单向链表和 ...

  3. Java集合:LinkedList源码解析

    Java集合---LinkedList源码解析   一.源码解析1. LinkedList类定义2.LinkedList数据结构原理3.私有属性4.构造方法5.元素添加add()及原理6.删除数据re ...

  4. 第十四章 Executors源码解析

    前边两章介绍了基础线程池ThreadPoolExecutor的使用方式.工作机理.参数详细介绍以及核心源码解析. 具体的介绍请参照: 第十二章 ThreadPoolExecutor使用与工作机理 第十 ...

  5. 第九章 LinkedBlockingQueue源码解析

    1.对于LinkedBlockingQueue需要掌握以下几点 创建 入队(添加元素) 出队(删除元素) 2.创建 Node节点内部类与LinkedBlockingQueue的一些属性 static ...

  6. 第六章 ReentrantLock源码解析2--释放锁unlock()

    最常用的方式: int a = 12; //注意:通常情况下,这个会设置成一个类变量,比如说Segement中的段锁与copyOnWriteArrayList中的全局锁 final Reentrant ...

  7. Java集合---LinkedList源码解析

    一.源码解析1. LinkedList类定义2.LinkedList数据结构原理3.私有属性4.构造方法5.元素添加add()及原理6.删除数据remove()7.数据获取get()8.数据复制clo ...

  8. ava集合---LinkedList源码解析

    一.源码解析 public class LinkedList<E> extends AbstractSequentialList<E> implements List<E ...

  9. 【vuejs深入三】vue源码解析之二 htmlParse解析器的实现

    写在前面 一个好的架构需要经过血与火的历练,一个好的工程师需要经过无数项目的摧残. 昨天博主分析了一下在vue中,最为基础核心的api,parse函数,它的作用是将vue的模板字符串转换成ast,从而 ...

随机推荐

  1. 人生第一个快速幂的题(HDU - 1097--A hard puzzle )

    题意: 最简单的快速幂.给你两个数n和m,求n^m的最后一位: 解题思路: 额,快速幂就很简单了,这里只要最后一位可以一对每次运算都%10: 代码: #include<cstdio> #i ...

  2. 远程连接mysql root账号报错:2003-can't connect to MYSQL serve

    1.远程连接Linux系统,登录数据库:mysql -uroot -p(密码) 2.修改root账号的设置: GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDE ...

  3. GPL、BSD和Apache开源许可证

    参考资料 五种开源协议的比较(BSD,Apache,GPL,LGPL,MIT) 如何选择开源许可证? - 阮一峰的网络日志 开源许可证教程 - 阮一峰的网络日志 简介 自由软件许可证由FSF(Free ...

  4. 洛谷——P1023 税收与补贴问题

    P1023 税收与补贴问题 题目背景 每样商品的价格越低,其销量就会相应增大.现已知某种商品的成本及其在若干价位上的销量(产品不会低于成本销售),并假设相邻价位间销量的变化是线性的且在价格高于给定的最 ...

  5. JAVAEE——宜立方商城04:图片服务器FastDFS、富文本编辑器KindEditor、商品添加功能完成

    1. 学习计划 1.图片上传 a) 图片服务器FastDFS b) 图片上传功能实现 2.富文本编辑器的使用KindEditor 3.商品添加功能完成 2. 图片服务器的安装 1.存储空间可扩展. 2 ...

  6. Python 递归删除非空目录(包括子目录以及文件)

    Python的OS模块自带rmdir和removedirs函数用于删除目录,但是两者都不能删除非空目录,以下代码定义了一个函数 remove_dir 用于删除非空目录. #作者官网 http://ww ...

  7. FLASK开发简易blog的学习笔记

    首先,学会了如何创建一个新的包.就是在文件夹下创建__init__.py文件,就是在其他地方导入这个包了.

  8. poj 1825 Ants 水题

    Ants Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 10722   Accepted: 4752 Description ...

  9. (转,学习记录)MD5加密算法中的加盐值(SALT)

    我们知道,如果直接对密码进行散列,那么黑客可以对通过获得这个密码散列值,然后通过查散列值字典(例如MD5密码破解网站),得到某用户的密码. 加Salt可以一定程度上解决这一问题.所谓加Salt方法,就 ...

  10. DDos与CC攻击的简单个人理解

    DDos简单来说就是向指定IP发送大量无用的数据包,造成网卡堵塞. CC理解成模拟表单提交,真实模拟业务,但量大之后也会造成网络堵塞. 参考: http://www.enkj.com/idcnews/ ...