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

前言

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

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

数组存储结构

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

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

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

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

相关推荐
弗拉唐1 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi771 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
少说多做3432 小时前
Android 不同情况下使用 runOnUiThread
android·java
知兀2 小时前
Java的方法、基本和引用数据类型
java·笔记·黑马程序员
蓝黑20202 小时前
IntelliJ IDEA常用快捷键
java·ide·intellij-idea
Ysjt | 深2 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
shuangrenlong2 小时前
slice介绍slice查看器
java·ubuntu
牧竹子2 小时前
对原jar包解压后修改原class文件后重新打包为jar
java·jar
数据小爬虫@2 小时前
如何利用java爬虫获得淘宝商品评论
java·开发语言·爬虫