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. 对包含HttpContext.Current.Cache的代码进行单元测试

    假设我们如下代码调用了HttpContext.Current.Cache 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class CacheManager { ...

  2. JAVA_WEB--jsp语法

    JSP声明 一个声明语句可以声明一个或多个变量.方法,供后面的Java代码使用.在JSP文件中,必须先声明这些变量和方法然后才能使用它们. JSP声明的语法格式: <%! declaration ...

  3. Codeforces Round #618 (Div. 2)-Non-zero

    Guy-Manuel and Thomas have an array a of n integers [a1,a2,-,an]. In one step they can add 1 to any ...

  4. 在IIS服务器上本地部署 ArcGIS API for js 4.15

    作为一名刚入门的小白,还没开始一个helloworld就在软件安装,环境部署时遇到了一大堆问题,简直太让人头秃了,脑壳疼.话不多说,这篇主要想分享一下自己部署ArcGIS API for js 4.1 ...

  5. MODIS系列之NDVI(MOD13Q1)五:NDVI处理流程

    前言:(个人建议) 1.进行数据处理工作,由于通常数据量比较大.所以在个人电脑中,要将基础数据.不断增长的过程数据.结果数据等分门别类,使得简单易找. 2.将重要数据备份,因为在数据处理过程中,由于出 ...

  6. LeetCode--LinkedList--83.Remove Duplicates from Sorted List(Easy)

    题目地址https://leetcode.com/problems/remove-duplicates-from-sorted-list/ 83. Remove Duplicates from Sor ...

  7. 【HBase】通过Java代码实现HBase数据库中数据的增删改查

    目录 创建maven工程,导入jar包 java代码实现创建hbase表 java代码实现向hbase表中插入数据 java代码查询hbase数据 使用rowKey查询指定列族指定列的值 通过star ...

  8. Day_13【IO流】扩展案例2_统计指定项目文件中字符出现的次数

    需求分析 统计当前项目下info2.txt文件中, 每个字符出现的个数 文件内容如下: welcome to itheima!!! 最终效果如下: w(1) (2)!(3)t(2)e(3)c(1)a( ...

  9. 自定义IDOC

    目录 1需求场景    4 2配置发送端IDOC    4 2.1定义段(WE31)    4 2.2定义基本类型(WE30)    4 2.3定义消息类型(WE81)    5 2.4定义传输结构  ...

  10. STM32 串口USART DMA方式发送接收数据

    硬件:stm32f103cbt6 软件:STM32F10x_StdPeriph_Lib_V3.5.0 文章目录 头文件 USART3_DR的地址 DMA的通道 DMA的中断 USART接收回调函数 头 ...