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

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

  1. //关于Node构造函数
  2. private static class Node<E> {
  3. E item;
  4. Node<E> next;
  5. Node<E> prev;
  6.  
  7. Node(Node<E> prev, E element, Node<E> next) {
  8. this.item = element;
  9. this.next = next;
  10. this.prev = prev;
  11. }
  12. }
 
先看下LinkedList中共的几个属性,下面三个属性分别描述LinkedList的尺寸、第一个元素、最后一个元素,这三个元素始终贯穿着在LinkedList的使用当中

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

  1. public boolean add(E e) {
  2. linkLast(e);
  3. return true;
  4. }
  5. /**
  6. * Links e as last element.
  7. */
  8. void linkLast(E e) {
  9. final Node<E> l = last;
  10. final Node<E> newNode = new Node<>(l, e, null);
  11. last = newNode;
  12. if (l == null)
  13. first = newNode;
  14. else
  15. l.next = newNode;
  16. size++;
  17. modCount++;
  18. }
看上面的源码可以知道,当LinkedList添加一个元素时,会默认的往LinkedList最后一个节点后添加,具体步骤为
  • 获得最后一个节点last作为当前节点l
  • 用当前节点l、添加参数e、null创建一个新的Node对象
  • 将新创建的Node对象链接到最后节点,也就是last
  • 如果当前的LinkedList为空,那么添加的node就是first,也是last
  • 当前LinkedList的size+1,表示结构改变次数的对象modCount+1
整个添加过程中,系统只做了两件事情,添加一个新的节点,然后保存该节点和前节点的引用关系。

  1. public boolean remove(Object o) {
  2. if (o == null) {
  3. for (Node<E> x = first; x != null; x = x.next) {
  4. if (x.item == null) {
  5. unlink(x);
  6. return true;
  7. }
  8. }
  9. } else {
  10. for (Node<E> x = first; x != null; x = x.next) {
  11. if (o.equals(x.item)) {
  12. unlink(x);
  13. return true;
  14. }
  15. }
  16. }
  17. return false;
  18. }

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

null判断之后,传入当前节点调用unlink(E e)方法,我们可以从方法名中看出点东西,可能设计者也是为了方便读者阅读源码,可以理解为“放开链接”,也可以反映出LinkedList保存数据的方式,一个个元素相互链接而成。
  1. E unlink(Node<E> x) {
  2. // assert x != null;
  3. final E element = x.item;
  4. final Node<E> next = x.next;
  5. final Node<E> prev = x.prev;
  6.  
  7. if (prev == null) {
  8. first = next;
  9. } else {
  10. prev.next = next;
  11. x.prev = null;
  12. }
  13.  
  14. if (next == null) {
  15. last = prev;
  16. } else {
  17. next.prev = prev;
  18. x.next = null;
  19. }
  20.  
  21. x.item = null;
  22. size--;
  23. modCount++;
  24. return element;
  25. }
通过阅读上面的源码,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的查询实现源码如下,

  1. public E get(int index) {
  2. checkElementIndex(index);
  3. return node(index).item;
  4. }
  5. private void checkElementIndex(int index) {
  6. if (!isElementIndex(index))
  7. throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
  8. }
  9. private boolean isElementIndex(int index) {
  10. return index >= 0 && index < size;
  11. }
  12. Node<E> node(int index) {
  13. // assert isElementIndex(index);
  14.  
  15. if (index < (size >> 1)) {
  16. Node<E> x = first;
  17. for (int i = 0; i < index; i++)
  18. x = x.next;
  19. return x;
  20. } else {
  21. Node<E> x = last;
  22. for (int i = size - 1; i > index; i--)
  23. x = x.prev;
  24. return x;
  25. }
  26. }
通过阅读源码,我们也可以解读出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. 网络流--最大流--Dinic模板矩阵版(当前弧优化+非当前弧优化)

    //非当前弧优化版 #include <iostream> #include <cstdio> #include <math.h> #include <cst ...

  2. Spring Cloud学习 之 Spring Cloud Ribbon(负载均衡器源码分析)

    文章目录 AbstractLoadBalancer: BaseLoadBalancer: DynamicServerListLoadBalancer: ServerList: ServerListUp ...

  3. OpenCV Error: Unspecified Error(The Function is not implemented)

    Ubuntu 或者 Debian 系统显示窗口的时候遇到了这个问题 error: (-2:Unspecified error) The function is not implemented. Reb ...

  4. Centos最小安装

    网络配置 虚拟机安装使用的NAT模式 #enoXXX 为自己的网卡名称, 使用ip addr 可以查看 将/etc/sysconfig/network-scripts/ifcfg-enoXXX中的ON ...

  5. transition完成事件

    当transition事件完成时调用函数(移动端导航的动画消失效果). <!doctype html> <html> <head> <meta charset ...

  6. OpenCV开发笔记(五十六):红胖子8分钟带你深入了解多种图形拟合逼近轮廓(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  7. CCF ISBN

    题目原文 问题描述(题目链接登陆账号有问题,要从这个链接登陆,然后点击“模拟考试”,进去找本题目)   试题编号: 201312-2 试题名称: ISBN号码 时间限制: 1.0s 内存限制: 256 ...

  8. Redis的几种集群方式分析

    一 单机版 分析: 无论多少用户,都访问这一台服务器 .服务器一旦挂了,所有用户都无法访问.风险很大,一般不会有人使用. 二 主从模式(master/slaver) 分析: 主从模式中, 无论多少用户 ...

  9. docker+headless+robotframework+jenkins实现web自动化持续集成

    在Docker环境使headless实现web自动化持续集成 一.制作镜像 原则:自动化测试基于基础制作镜像 命令:docker run --privileged --name=$1 --net=ho ...

  10. MySQL++:Liunx - MySQL 主从复制

    目标:搭建两台MySQL服务器,一台作为主服务器,一台作为从服务器,实现主从复制 环境:虚拟机 主数据库:192.168.211.101 从数据库:192.168.211.102 MySQL 安装可参 ...