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

前言

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

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

数组存储结构

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

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

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 个字节,其中 array[0],array[1],array[2] 三个元素分别保存了指向三个一维数组的引用
  • 三个一维数组各占 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. 插入和删除元素不高效:由于数组的大小固定,插入和删除元素时需要进行元素的移动操作,这可能导致效率较低。特别是在数组的开头或中间插入/删除元素时,需要移动较多的元素。

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

相关推荐
xrgs_shz4 小时前
MATLAB的数据类型和各类数据类型转化示例
开发语言·数据结构·matlab
独正己身4 小时前
代码随想录day4
数据结构·c++·算法
customer085 小时前
【开源免费】基于SpringBoot+Vue.JS体育馆管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
Miketutu6 小时前
Spring MVC消息转换器
java·spring
乔冠宇6 小时前
Java手写简单Merkle树
java·区块链·merkle树
LUCIAZZZ7 小时前
简单的SQL语句的快速复习
java·数据库·sql
komo莫莫da8 小时前
寒假刷题Day19
java·开发语言
Rachela_z8 小时前
代码随想录算法训练营第十四天| 二叉树2
数据结构·算法
细嗅蔷薇@8 小时前
迪杰斯特拉(Dijkstra)算法
数据结构·算法
S-X-S8 小时前
算法总结-数组/字符串
java·数据结构·算法