从Java源码上分析为什么LinkedList随机访问比顺序访问要慢这么多?

简介: 我们都知道LinedList的随机访问比顺序访问要慢,那你知道为什么吗?我们那从源码上就可以清晰看出缘由。

从Java源码上分析为什么LinkedList随机访问比顺序访问要慢这么多?

// 随机访问
for(int i=0;i<list.size();i++) {
    list.get(i);
}
// 顺序访问
Iterator<E> it = list.iterator();
while(it.hasNext()){
    it.next();
}

LinkedListget()方法源码

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    // 返回此列表中指定位置的元素。
    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;
            // index小于长度一半时,是从链表头部往后找
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            // index大于长度一半时,是从链表尾部往前找
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

随机访问使用list.get(i)方法,从源码中我们可以得知,每次list.get(i)都遍历找到该元素位置再返回,当我们需要遍历一次list,其实list.get(i)会遍历很多次,做了重复性工作。

list.iterator()源码

Iterator<E> it = list.iterator();

// AbstractList为LinkedList父类的父类
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
    // 返回此列表中元素的列表迭代器(以正确的顺序)。
    public Iterator<E> iterator() {
        return listIterator();
    }
    // 返回参数为0的列表迭代器
    public ListIterator<E> listIterator() {
        return listIterator(0);
    }
}

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    public ListIterator<E> listIterator(int index) {
        checkPositionIndex(index);
        return new ListItr(index);
    }
    
    // 检查index范围
    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }
    
    // LinkedList迭代器实现类
    private class ListItr implements ListIterator<E> {
        private Node<E> lastReturned;
        private Node<E> next;
        private int nextIndex;
        // 将实际修改集合次数赋值给预期修改次数
        private int expectedModCount = modCount;

        ListItr(int index) {
            // 🍀判断 0 == size,实际上就是调用 node(index)方法
            next = (index == size) ? null : node(index);
            // 将index的值赋值给 nextIndex,便于下次查找
            nextIndex = index;
        }
        
        // 判断nextIndex是否在范围内
        public boolean hasNext() {
            return nextIndex < size;
        }
        
        // 获取下一个元素
        public E next() {
            // 检查集合实际修改次数和预期次数是否一样
            checkForComodification();
            // 再次判断是否有元素
            if (!hasNext())
                throw new NoSuchElementException();
            lastReturned = next;
            next = next.next;            
            nextIndex++;
            return lastReturned.item;
        }
        // 检查集合实际修改次数和预期次数是否一样
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
    
    // 🍀在获取迭代器的时候也会进行折半判断的过程,但index=0
    Node<E> node(int index) {
        if (index < (size >> 1)) {
            // 但是在获取迭代器的时候 index 一定是0,因此 if 的条件成立
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            // index=0 直接返回第一个元素
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
}

从迭代器源码中我们得知,在进行顺序访问时,只在第一次,index=0时进行了一个折半判断,此后按照顺序依次向后传递获取元素,实际只进行了一次遍历过程。由此可见,LinkedList的顺序遍历比随机遍历快很多。

目录
相关文章
|
1月前
|
Java 开发工具
【Azure Storage Account】Java Code访问Storage Account File Share的上传和下载代码示例
本文介绍如何使用Java通过azure-storage-file-share SDK实现Azure文件共享的上传下载。包含依赖引入、客户端创建及完整示例代码,助你快速集成Azure File Share功能。
333 4
|
1月前
|
Java Go 开发工具
【Java】(9)抽象类、接口、内部的运用与作用分析,枚举类型的使用
抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接 口、枚举)5种成分。抽象类的构造器不能用于创建实例,主要是用于被其子类调用。抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类abstract static不能同时修饰一个方法。
200 1
|
1月前
|
存储 Java Go
【Java】(3)8种基本数据类型的分析、数据类型转换规则、转义字符的列举
牢记类型转换规则在脑海中将编译和运行两个阶段分开,这是两个不同的阶段,不要弄混!
187 2
|
2月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
|
2月前
|
存储 小程序 Java
热门小程序源码合集:微信抖音小程序源码支持PHP/Java/uni-app完整项目实践指南
小程序已成为企业获客与开发者创业的重要载体。本文详解PHP、Java、uni-app三大技术栈在电商、工具、服务类小程序中的源码应用,提供从开发到部署的全流程指南,并分享选型避坑与商业化落地策略,助力开发者高效构建稳定可扩展项目。
|
3月前
|
安全 Java 编译器
new出来的对象,不一定在堆上?聊聊Java虚拟机的优化技术:逃逸分析
逃逸分析是一种静态程序分析技术,用于判断对象的可见性与生命周期。它帮助即时编译器优化内存使用、降低同步开销。根据对象是否逃逸出方法或线程,分析结果分为未逃逸、方法逃逸和线程逃逸三种。基于分析结果,编译器可进行同步锁消除、标量替换和栈上分配等优化,从而提升程序性能。尽管逃逸分析计算复杂度较高,但其在热点代码中的应用为Java虚拟机带来了显著的优化效果。
125 4
|
3月前
|
存储 安全 Java
java: 无法访问org.springframework.ldap.core.LdapTemplate
java: 无法访问org.springframework.ldap.core.LdapTemplate
130 9
|
3月前
|
机器学习/深度学习 安全 Java
Java 大视界 -- Java 大数据在智能金融反洗钱监测与交易异常分析中的应用(224)
本文探讨 Java 大数据在智能金融反洗钱监测与交易异常分析中的应用,介绍其在数据处理、机器学习建模、实战案例及安全隐私等方面的技术方案与挑战,展现 Java 在金融风控中的强大能力。
|
4月前
|
存储 Java 大数据
Java 大视界 -- Java 大数据在智能家居能源消耗模式分析与节能策略制定中的应用(198)
简介:本文探讨Java大数据技术在智能家居能源消耗分析与节能策略中的应用。通过数据采集、存储与智能分析,构建能耗模型,挖掘用电模式,制定设备调度策略,实现节能目标。结合实际案例,展示Java大数据在智能家居节能中的关键作用。
|
5月前
|
缓存 Java 数据库
Java 访问修饰符使用方法与组件封装方法详细说明
本文详细介绍了Java中访问修饰符(`public`、`private`、`protected`、默认)的使用方法,并结合代码示例讲解了组件封装的核心思想与实现技巧。内容涵盖数据封装、继承扩展、模块化设计与接口隔离等关键技术点,帮助开发者提升代码的可维护性与安全性,适用于Java初学者及进阶开发者学习参考。
139 1
下一篇
oss云网关配置