List家族中共两个常用的对象ArrayList和LinkedList,具有以下基本特征。
  • ArrayList:长于随机访问元素,中间插入和移除元素比较慢,在插入时,必须创建空间并将它的所有引用向前移动,这会随着ArrayList的尺寸增加而产生高昂的代价,底层由数组支持。
  • LinkedList:通过代价较低的在List中间进行插入和删除操作,只需要链接新的元素,而不必修改列表中剩余的元素,无论列表尺寸如何变化,其代价大致相同,提供了优化的顺序访问,随机访问相对较慢,特性较ArrayList更大,而且还添加了可以使其作为栈、队列或双端队列的方法,底层由双向链表实现。
 
上面的特性接触了Java开发的人都知道,初级Java工程师面试更是必问的点,至于更加深入的知识点:为什么会有这样的特性? 底层是怎么实现的?往往到这里,大部分程序员都答不出来,笔者当初面试那会也是被这里吊打,被面试官无情的嘲讽,当时的情形是这样的
当时那个臊的啊,拿回简历出了公司坐在深圳某天桥思考了许久的人生。。。。。。。。
 
抽空看了下LinkedList的实现,整理成博文的形式,能给自己增加理解,也希望能帮助广大码农。
LinkedList本质是一个双向链表,由一个个的Node对象组成,如下图

LinkedList由一个个Node组成,每一个Node持有前后节点的引用,也可以称之为指针,看下Node的结构,Node有当前元素对象、前一个节点信息、后一个节点信息三个属性,构造函数也是由这三个属性去组成,在LinkedList的添加方法中,会创建一个Node对象,持有前后节点的信息,添加到最后节点之后。

//关于Node构造函数
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;
}
}
 
先看下LinkedList中共的几个属性,下面三个属性分别描述LinkedList的尺寸、第一个元素、最后一个元素,这三个元素始终贯穿着在LinkedList的使用当中

//transient 保证以下几个属性不被序列化
/**
* The number of times this list has been <i>structurally modified</i>.
* 该字段表示list结构上被修改的次数。结构上的修改指的是那些改变了list的长度
* 大小或者使得遍历过程中产生不正确的结果的其它方式。
*
*/
protected transient int modCount = 0; transient int size = 0;
/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;
/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;
 
再看LinkedList的添加实现,可以添加为null的对象

public boolean add(E e) {
linkLast(e);
return true;
}
/**
* Links e as last element.
*/
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添加一个元素时,会默认的往LinkedList最后一个节点后添加,具体步骤为
  • 获得最后一个节点last作为当前节点l
  • 用当前节点l、添加参数e、null创建一个新的Node对象
  • 将新创建的Node对象链接到最后节点,也就是last
  • 如果当前的LinkedList为空,那么添加的node就是first,也是last
  • 当前LinkedList的size+1,表示结构改变次数的对象modCount+1
整个添加过程中,系统只做了两件事情,添加一个新的节点,然后保存该节点和前节点的引用关系。

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;
}

LinkedList的删除中,会根据传递进来的参数进行null判断,因为在LinkedList的元素移除中,null和非null的处理不一样,对于nul使用==去判断是否匹配,对于非null使用.equals(Object o)去判断,至于==和equals的区别,读者自行百度。

null判断之后,传入当前节点调用unlink(E e)方法,我们可以从方法名中看出点东西,可能设计者也是为了方便读者阅读源码,可以理解为“放开链接”,也可以反映出LinkedList保存数据的方式,一个个元素相互链接而成。
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; if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
} if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
} x.item = null;
size--;
modCount++;
return element;
}
通过阅读上面的源码,LinkedList的删除中,也是改变节点之间的引用关系去实现的,具体逻辑整理如下:
  • 如果前一个节点prev为null,即第一个节点元素,则链表first = x下一个节点;
  • 如果前一个节点prev不为null,即不是第一个节点元素,则将当前节点的next赋值给prev.next,x.prev置为null,也就是当前节点x的prev和next和当前链表中的其他元素不存在任何联系;
  • 如果下一个节点next为null,即为最后一个元素,则链表last = x前一个节点;
  • 如果下一个节点next不为null,即不为最后一个元素,则将当前节点的prev赋值给next.prev,同样当前节点x的prev和next和当前链表中的其他元素不存在任何联系;
 
LinkedList的查询实现源码如下,

public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
Node<E> node(int index) {
// assert isElementIndex(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;
}
}
通过阅读源码,我们也可以解读出LinkedList的查询逻辑
  • 根据传入的index去判断是否为LinkedList中的元素,判断逻辑为index是否在0和size之间,如果在则调用node(index)方法,否则抛出IndexOutOfBoundsException;
  • 调用node(index)方法,将size右移1位,即size/2,判断传入的size在LinkedList的前半部分还是后半部分
    • 如果在前半部分,即index < size/2,则从fisrt节点开始遍历匹配
    • 如果在后半部分,即index > size/2,则从last节点开始遍历匹配
可以看出,如果LinkedList链表size越大,则遍历的时间越长,查询所需的时间也越长。
 
应该可以将LinkedList的添加、删除、查询给说清楚,如果读者还有不清楚或者觉得有什么不对的地方,欢迎各位指正或者入群交流。
 

LinkedList为什么增删快、查询慢的更多相关文章

  1. 集合之LinkedList源码分析

    转载请注明出处:http://www.cnblogs.com/qm-article/p/8903893.html 一.介绍 在介绍该源码之前,先来了解一下链表,接触过数据结构的都知道,有种结构叫链表, ...

  2. 数据结构-List接口-LinkedList类-Set接口-HashSet类-Collection总结

    一.数据结构:4种--<需补充> 1.堆栈结构:     特点:LIFO(后进先出);栈的入口/出口都在顶端位置;压栈就是存元素/弹栈就是取元素;     代表类:Stack;     其 ...

  3. ArrayList和LinkedList的共同点和区别

    ArrayList和LinkedList的相同点和不同点 共同点:都是单列集合中List接口的实现类.存取有序,有索引,可重复 不同点: 1.底层实现不同: ArrayList底层实现是数组,Link ...

  4. List-ApI及详解

    1.API : add(Object o) remove(Object o) clear() indexOf(Object o) get(int i) size() iterator() isEmpt ...

  5. javaList容器中容易忽略的知识点

    在集合类框架中,List是使用比较多的一种 List |---Arraylist 内部维护的是一个数组,查找快增删慢 |---LinkedList 底层是链表,增删快查询慢. |---Vctor线程安 ...

  6. java_List集合及其实现类

    第一章:List集合_List接口介绍 1).特点       1).有序的: 2).可以存储重复元素: 3).可以通过索引访问: List<String> list = new Arra ...

  7. java基础-day17

    第06天 集合 今日内容介绍 u  集合&迭代器 u  增强for & 泛型 u  常见数据结构 u  List子体系 第1章   集合&迭代器 1.1  集合体系结构 1.1 ...

  8. BAT面试必备——Java 集合类

    本文首发于我的个人博客:尾尾部落 1. Iterator接口 Iterator接口,这是一个用于遍历集合中元素的接口,主要包含hashNext(),next(),remove()三种方法.它的一个子接 ...

  9. 集合之四:List接口

    查阅API,看List的介绍.有序的 collection(也称为序列).此接口的用户可以对列表中每个元素的插入位置进行精确地控制.用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的 ...

随机推荐

  1. 搭建vsftpd文件服务器并创建虚拟用户

    一.安装     1. 查看是否安装vsftpd         rpm -qa | grep vsftpd     2. 安装          yum -y install vsftpd      ...

  2. postman(全局变量设置)

    全局变量 全局变量作用于整个postman工具及所有环境 1.点击小齿轮进入到变量添加页面,点击Globals添加全局变量 2.输入变量名称和变量值 3.接口中设置变量 4.调用 Globals 变量 ...

  3. ASP.NET Core MVC 如何获取请求的参数

    一次HTTP请求,就是一次标准IO操作.请求是I,是输入:响应式O,是输出.任何web开发框架,其实都是在干这两件事: 接受请求并进行解析获取参数 根据参数进行渲染并输出响应内容 所以我们学习一个框架 ...

  4. 前端——Vue.js学习总结一

    一.什么是Vue.js 1.Vue.js 是目前最火的一个前端框架,React是最流行的一个前端框架 2.Vue.js 是前端的主流框架之一,和Angular.js.React.js 一起,并成为前端 ...

  5. bootstrap栅格系统的使用

    bootstrap栅格系统的使用 bootstrap栅格系统的使用,主要分为四种方式 1.列组合  col-md-* 2.列偏移 col-md-offset-* 3.列嵌套  大列组合包含着小组合 4 ...

  6. 这是一篇每个人都能读懂的最小生成树文章(Kruskal)

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是算法和数据结构专题的第19篇文章,我们一起来看看最小生成树. 我们先不讲算法的原理,也不讲一些七七八八的概念,因为对于初学者来说,看到 ...

  7. vue项目中使用bpmn-自定义platter

    前情提要 经过前四篇的学习,我们能够实现bpmn基本绘图.预览.为节点加事件加颜色等效果,这一篇我们来说,如何自定义左侧工具栏(platter),首先看一下自定义前后效果图对比: 我们本次要实现的目标 ...

  8. 一阶RC高通滤波器详解(仿真+matlab+C语言实现)

    文章目录 预备知识 关于电容 HPF的推导 simulink 仿真 simulink 运行结果 matlab 实现 matlab 运行结果 C语言实现 如果本文帮到了你,帮忙点个赞: 如果本文帮到了你 ...

  9. vue 如何实现 Input 输入框模糊查询方法

    原理:原生js的indexOf() 方法,该方法将从头到尾地检索数组,看它是否含有对应的元素.开始检索的位置在数组 start 处或数组的开头(没有指定 start 参数时).如果找到一个 item, ...

  10. [hdu5266]区间LCA

    题意:给一棵树,求节点L,L+1,...R的最近公共祖先 思路:先对树dfs一下,从根1出发,经过每条边时记录一下终点和到达这个点的时间截,令r[u]表示到达u这个节点的最早时间截,t[x]表示在时间 ...