从底层数据结构和CPU缓存两方面剖析LinkedList的查询效率为什么比ArrayList低

简介: 本文详细对比了ArrayList和LinkedList的查询效率,从底层数据结构和CPU缓存两个方面进行分析。ArrayList基于动态数组,支持随机访问,查询时间复杂度为O(1),且CPU缓存对其友好;而LinkedList基于双向链表,需要逐个节点遍历,查询时间复杂度为O(n),且CPU缓存对其帮助不大。文章还探讨了CPU缓存对数组增删操作的影响,指出缓存主要作用于读取而非修改。通过这些分析,加深了对这两种数据结构的理解。

虽然前面有写到LinkedList与ArrayList的增删改查效率的全面比较,但回想一下还是有必要对这两者的查询效率做一个单独的比较,也能进一步加深理解。这次分底层数据结构和CPU缓存两方面展开它们分别对查询效率的阐述。

一、底层数据结构对查询效率的影响

1.ArrayList底层数据结构

ArrayList底层数据结构是动态数组,创建数组时会给它分配一整段连续的物理内存空间,只要知道数组首地址和数组存储的元素类型,就可以根据指定索引值直接推导得出该索引位置对应的内存地址,进而就可以直接访问得到到该内存地址上存储的具体元素:
 数组存储的元素都是相同类型:
第n个元素地址=首地址+(元素类型类型长度单位)n。
 数组存储的元素是不同类型:
第n个元素地址=首地址+(第1个元素类型类型长度单位) +…+(第n个元素类型类型长度单位)。
代码如下:

public E get(int index) {
   
    rangeCheck(index);
    return elementData(index);
}
E elementData(int index) {
   
    return (E) elementData[index];
}

2.LinkedList底层数据结构

LinkedList底层数据结构是双向链表(因其每个节点Node中同时记录了该节点的的前一个节点prev和后一个节点next),链表在逻辑上是连续的,但在实际物理内存上却不是一整段连续的内存块;这样的底层数据结构限制了其不能像数组一样根据索引值直接推导得出某个索引位置对应的内存地址。而只能通过递进遍历的方式去一步步移动到要查询的索引位置,然后才能访问得到对应索引位置上的元素,递进的开始方向分两种:
 当要检索的索引位置index小于数组长度的一半size>>1时,则从数组头部first开始从前往后next遍历,直到移动到index所在位置:
 当要检索的索引位置index大于数组长度的一半size>>1时,则从数组尾部last开始从后向前prev遍历,直到移动到index所在位置;
代码如下:

public E get(int index) {
   
    checkElementIndex(index);
    return node(index).item;
}
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;
    }
}

小结1:从上述1、2点可以很明显看出,底层数据结构对两者的查询效率的影响,ArrayList得益于数组结构的随机访问特性,查询的时间复杂度为O(1);而LinkedList囿于其链表结构,查询的时间复杂度高于ArrayList,达到O(n)。

二、CPU缓存对查询效率的影响

不能单说CPU缓存,要拿CPU中的寄存器/一级缓存/二级缓存/内存/硬盘这些不同的存储器,对比着来说,这样才能看出端倪:
CPU寄存器-immediate access(0-1个CPU时钟周期)
CPU L1缓存-fast access(3个CPU时钟周期)
CPU L2缓存-slightly slower access(10个CPU时钟周期)
内存(RAM)-slow access(100个CPU时钟周期)
硬盘(file system)-very slow(10,000,000个CPU时钟周期)
可以看出,各级别的存储器速度差异很大,CPU寄存器是内存速度的100倍之多!这就是为什么CPU厂商发明CPU缓存的原因,为了弥补内存访问速度过慢和CPU执行速度过快的差异。而这个CPU缓存,会对数组和链表的查询效率产生很不一样的影响。

1. CPU缓存对数组友好

CPU缓存会把一整片连续的内存空间读入,因为数组结构是连续的内存,所以数组全部或部分元素会被连续存在CPU缓里面,平均读取每个元素的时间只要3个CPU时钟周期 。

2. CPU缓存对链表不友好

由于链表结构是非连续的内存,所以这时候CPU缓存帮不上忙,只能去读取内存,而读取内存中元素的平均耗时为100个CPU时钟周期。

小结2:由于CPU缓存对数组的助力,数组的查询效率比链表快了33倍!

三、疑问:CPU缓存会对数组的增加删除操作起到助力么?

尝试解答:应该不会。

因为CPU缓存属于存储性质,应该不可以在其中做增删操作。假设,数组增删操作真的也在CPU缓存中进行,当删除某个数组元素时,需要将该元素所在索引位置后面的连续内存块都应该向前移动一位,增加时则都需要向后移动一位。就是说,需要想办法对缓存内的数据进行修改,而且这还只是在缓存里修改了,修改后还需要将结果取出来,覆盖到实际的内存上,否则只是改了副本,实际的本体却没得到修改。想想就很麻烦。

而且通过查找资料,了解到,CPU缓存只对CPU开放。

只有CPU可以对缓存进行读写操作,当cpu想要访问内存数据时,会先到缓存中查看数据是否已存在,若存在则直接从缓存读取,若缓存中不存在,则会先把数据从内存加载进缓存,再进行读取。就是说,缓存中的数据是来源于内存的,是内存数据的拷贝,内存决定了缓存的内容,但是缓存里缓存的内容无法影响到内存。就像人影被光线拉长,但人的身高还是不会变的。

四、总结

以前只知其一,而不知其二,没有想到数组和链表的查询效率还与CPU缓存有联系。多逛逛技术社区还是收获蛮多的,可以拓展视界,对某个知识点能了解更多,思考的时候也有了方向,不会漫无目的绕很多的弯路,认识也更深刻。
ava

目录
相关文章
|
15天前
|
存储 Java 索引
Java中的数据结构:ArrayList和LinkedList的比较
【10月更文挑战第28天】在Java编程世界中,数据结构是构建复杂程序的基石。本文将深入探讨两种常用的数据结构:ArrayList和LinkedList,通过直观的比喻和实例分析,揭示它们各自的优势与局限,帮助你在面对不同的编程挑战时做出明智的选择。
|
18天前
|
存储 关系型数据库 MySQL
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
192 2
|
1月前
|
Java C++ 索引
让星星⭐月亮告诉你,LinkedList和ArrayList底层数据结构及方法源码说明
`LinkedList` 和 `ArrayList` 是 Java 中两种常见的列表实现。`LinkedList` 基于双向链表,适合频繁的插入和删除操作,但按索引访问元素效率较低。`ArrayList` 基于动态数组,支持快速随机访问,但在中间位置插入或删除元素时性能较差。两者均实现了 `List` 接口,`LinkedList` 还额外实现了 `Deque` 接口,提供了更多队列操作。
23 3
|
1月前
|
设计模式 安全 容器
数据结构第一篇【探究List和ArrayList之间的奥秘 】
数据结构第一篇【探究List和ArrayList之间的奥秘 】
23 5
|
2月前
|
存储 关系型数据库 MySQL
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
查询服务器CPU、内存、磁盘、网络IO、队列、数据库占用空间等等信息
130 5
|
2月前
|
存储 Java 程序员
【数据结构】初识集合&深入剖析顺序表(Arraylist)
Java集合框架主要由接口、实现类及迭代器组成,包括Collection和Map两大类。Collection涵盖List(有序、可重复)、Set(无序、不可重复),Map则由键值对构成。集合通过接口定义基本操作,具体实现由各类如ArrayList、HashSet等提供。迭代器允许遍历集合而不暴露其实现细节。List系列集合元素有序且可重复,Set系列元素无序且不可重复。集合遍历可通过迭代器、增强for循环、普通for循环及Lambda表达式实现,各有适用场景。其中ArrayList实现了动态数组功能,可根据需求自动调整大小。
37 11
|
1月前
|
算法 Java 测试技术
数据结构 —— Java自定义代码实现顺序表,包含测试用例以及ArrayList的使用以及相关算法题
文章详细介绍了如何用Java自定义实现一个顺序表类,包括插入、删除、获取数据元素、求数据个数等功能,并对顺序表进行了测试,最后还提及了Java中自带的顺序表实现类ArrayList。
20 0
|
3月前
|
安全 C# 数据安全/隐私保护
WPF安全加固全攻略:从数据绑定到网络通信,多维度防范让你的应用固若金汤,抵御各类攻击
【8月更文挑战第31天】安全性是WPF应用程序开发中不可或缺的一部分。本文从技术角度探讨了WPF应用面临的多种安全威胁及防护措施。通过严格验证绑定数据、限制资源加载来源、实施基于角色的权限管理和使用加密技术保障网络通信安全,可有效提升应用安全性,增强用户信任。例如,使用HTML编码防止XSS攻击、检查资源签名确保其可信度、定义安全策略限制文件访问权限,以及采用HTTPS和加密算法保护数据传输。这些措施有助于全面保障WPF应用的安全性。
51 0
|
3月前
|
存储 缓存 NoSQL
微服务复杂查询之缓存策略
微服务复杂查询之缓存策略
|
3月前
|
缓存 关系型数据库 MySQL
【缓存大对决】Memcached VS MySQL查询缓存,谁才是真正的性能之王?
【8月更文挑战第24天】在现代Web应用中,缓存技术对于提升性能与响应速度至关重要。本文对比分析了Memcached与MySQL查询缓存这两种常用方案。Memcached是一款高性能分布式内存对象缓存系统,支持跨服务器共享缓存,具备灵活性与容错性,但受限于内存大小且不支持数据持久化。MySQL查询缓存内置在MySQL服务器中,简化了缓存管理,特别适用于重复查询,但功能较为单一且扩展性有限。两者各有所长,实际应用中可根据需求单独或结合使用,实现最佳性能优化。
116 0