【数据结构与算法】数组为什么访问快?

前言

数组是一种数据结构,用于存储相同类型的多个元素。它是一种线性数据结构,其中的元素按照顺序排列,并且可以通过索引访问和操作。每个元素在数组中都有一个唯一的索引,用于标识其位置。

那大家有想过数组根据索引随机访问为什么很快呢?

数组存储结构

数组之所以能够快速进行随机访问,是因为数组内的元素是连续存储的。在创建数组时,内存会按照顺序进行分配,并且每个元素都会相邻地存储在内存中。

这种布局使得通过索引来进行快速访问成为可能,因为可以利用元素的索引来计算其内存地址。由于元素存储在相邻的内存位置上,处理器可以根据索引快速计算出任何元素的内存地址。

ini 复制代码
int[] array = {1,2,3,4,5}

知道了数组的数据 起始地址 BaseAddress,就可以由公式 BaseAddress + i * size 计算出索引 i 元素的地址

  • i 即索引,在 Java、C 等语言都是从 0 开始
  • size 是每个元素占用字节,例如 int 占 4,double 占 8

随机访问性能

即根据索引查找元素,时间复杂度是O(1)。

那么二维数组呢?

二维数组是一种特殊的数组,可以理解为一维数组中的每个元素存放的是一个数组,比如下面的二维数组:

ini 复制代码
int[][] array = {
    {11, 12, 13, 14, 15},
    {21, 22, 23, 24, 25},
    {31, 32, 33, 34, 35},
};

内存结构图如下:

  • 二维数组占 32 个字节,其中 array0,array1,array2 三个元素分别保存了指向三个一维数组的引用
  • 三个一维数组各占 40 个字节
  • 它们在内层布局上是连续的

二维数组哪种遍历顺序快呢?

针对二维数组的遍历方式,下面哪种比较快呢?

  • ij方法: 先遍历行
ini 复制代码
public static void ij(int[][] a, int rows, int columns) {
    long sum = 0L;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < columns; j++) {
            sum += a[i][j];
        }
    }
    System.out.println(sum);
}
  • ji方法: 先遍历列
ini 复制代码
public static void ji(int[][] a, int rows, int columns) {
    long sum = 0L;
    for (int j = 0; j < columns; j++) {
        for (int i = 0; i < rows; i++) {
            sum += a[i][j];
        }
    }
    System.out.println(sum);
}

测试一下

ini 复制代码
int rows = 1000000;
int columns = 14;
int[][] a = new int[rows][columns];

StopWatch sw = new StopWatch();
sw.start("ij");
ij(a, rows, columns);
sw.stop();
sw.start("ji");
ji(a, rows, columns);
sw.stop();
System.out.println(sw.prettyPrint());

执行结果

markdown 复制代码
0
0
StopWatch '': running time = 96283300 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
016196200  017%  ij
080087100  083%  ji

可以看到 ij 的效率比 ji 快很多,为什么呢?

这里不得不说计算机组成原理中的一个基础原理,局部性原理。

  • cpu 读取内存(速度慢)数据后,会将其放入高速缓存(速度快)当中,如果后来的计算再用到此数据,在缓存中能读到的话,就不必读内存了。
  • 缓存的最小存储单位是缓存行(cache line),一般是 64 bytes,一次读的数据少了不划算啊,因此最少读 64 bytes 填满一个缓存行,因此读入某个数据时也会读取其临近的数据 ,这就是所谓空间局部性。

这下原因显而易见了,因为ij的方式是按照数组存储的顺序读取,会命中高速缓存,而ji方式并不会。

总结

最后我们根据数组的存储特性总结下数组这一数据结构的特点如下 :

  1. 存储相同类型的元素:数组中只能存储相同类型的元素,例如整数、浮点数、字符等。这种限制使得数组在处理同一类型数据时非常有效。

  2. 连续的内存空间:数组的元素在内存中是连续存储的,每个元素占用相同的内存大小。这种连续存储的结构使得数组能够进行快速的随机访问。

  3. 固定大小:数组在创建时需要指定大小,一旦创建后,大小通常不能改变。这意味着数组的长度是固定的,无法动态增加或减少。

  4. 使用索引访问元素:数组中的每个元素都有一个唯一的索引,通过索引可以直接访问和修改数组中的元素。索引从0开始,依次递增。

  5. 随机访问效率高:由于数组的元素在内存中连续存储,并且可以通过索引直接访问,因此数组具有快速的随机访问能力。通过索引,可以在常数时间复杂度O(1)内访问特定位置的元素。

  6. 内存空间的浪费:由于数组需要在创建时指定固定大小,可能会导致内存空间的浪费。如果数组的大小远大于实际存储的元素数量,会浪费一部分内存空间。

  7. 插入和删除元素不高效:由于数组的大小固定,插入和删除元素时需要进行元素的移动操作,这可能导致效率较低。特别是在数组的开头或中间插入/删除元素时,需要移动较多的元素。

总的来说,数组是一种简单而有效的数据结构,适用于需要快速随机访问元素的场景。然而,由于其固定大小和插入/删除的低效性,对于频繁的插入和删除操作,可能需要考虑链表等数据结构的使用。

相关推荐
邪修king33 分钟前
Re:Linux 系统篇(三十五):进程间通信Chapter2:从匿名管道到进程池:Linux 进程间通信实战(深度理解「先描述,再组织」)
java·linux·运维
拆房老料1 小时前
【第四部分:Agentic 智能体设计模式】16. 一个 Prompt 搞不定复杂任务?试试 Prompt Chaining
java·前端·人工智能·设计模式·prompt
张张123y2 小时前
后端第十四课:Agent 记忆系统(让 Agent 记住上下文和用户,跨会话不健忘)
java·大数据·人工智能·python·agent·记忆
曹牧6 小时前
C#:空合并运算符
java·前端·数据库
布吉岛的石头7 小时前
Java 程序员第 50 阶段2:预训练的目标函数:自回归语言建模本质
java·开发语言·人工智能·深度学习·回归·transformer
golang学习记7 小时前
IDEA 2026年的玩法已经完全不同
java·ide·intellij-idea
紧跟先前的步伐7 小时前
Go AI Agent 实战 05|Temperature 与生成参数:让模型输出可控
java·人工智能·golang
金銀銅鐵7 小时前
[Java] 展示添加元素时 ArrayList 的内部变化
java·后端·ai编程
斌糖雪梨8 小时前
spring auth 核心流程详解
java·后端·spring
罗湖老棍子9 小时前
【例 1】Sightseeing Trip(信息学奥赛一本通- P1494)(洛谷-P10927)
数据结构·算法·图论·最短路·floyd·最小环