基础数据结构——数组(动态数组,二维数组,缓存与局部性原理)

embedded/2024/10/22 12:38:34/

1.概述

在计算机科学中,数组是由一组元素(值或变量)组成的数据结构,每个元素有至少一个索引或键来标识

因为数组内的元素是连续存储的,所以数组中元素的地址,可以通过其索引计算出来,例如:

int[] array = {1,2,3,4,5}

知道了数组的数据起始地址 B a s e A d d r e s s BaseAddress BaseAddress,就可以由公式 B a s e A d d r e s s + i ∗ s i z e BaseAddress + i * size BaseAddress+isize 计算出索引 i i i 元素的地址

  • i i i 即索引,在 Java、C 等语言都是从 0 开始
  • s i z e size size 是每个元素占用字节,例如 i n t int int 4 4 4 d o u b l e double double 8 8 8
空间占用

Java 中数组结构为

  • 8 字节 markword(记录了这个对象的 HashCode,分代年龄,锁信息等等)
  • 4 字节 class 指针(压缩 class 指针的情况)
  • 4 字节 数组大小(决定了数组最大容量是 2 32 2^{32} 232
  • 数组元素 + 对齐字节(java 中所有对象大小都是 8 字节的整数倍[^12],不足的要用对齐字节补足)
  • 在这里插入图片描述
随机访问性能

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

2.动态数组

java">package com.lemon.demo.array;import java.util.Arrays;
import java.util.Iterator;
import java.util.function.Consumer;
import java.util.stream.IntStream;/*** @author 李猛* @datetime 2024/10/17 17:41* @description 动态数组*/
public class DynamicArray implements Iterable<Integer> {private int capacity = 8;//容量,初始容量8private int size = 0;//有效的元素个数,初始个数0private int[] array = new int[capacity];//数组信息/*** 向数组末尾添加元素** @param element*/public void addLast(int element) {add(size, element);}/*** 根据索引位置添加元素** @param index* @param element*/public void add(int index, int element) {if (index < 0 || index > size) {throw new IndexOutOfBoundsException("index:" + index + " error");}//扩容checkAddExpand();System.arraycopy(array, index, array, index + 1, size - index);array[index] = element;size++;}/*** 根据索引获取元素** @param index* @return*/public int get(int index) {if (index < 0 || index >= size) {throw new IndexOutOfBoundsException("index:" + index + " error");}return array[index];}public int remove(int index) {if (index < 0 || index >= size) {throw new IndexOutOfBoundsException("index:" + index + " error");}int element = array[index];if (index == size - 1) {//如果是删除最后一个元素/*** 数组copy* 1.原数组* 2.原数组起始位置* 3.目标数组* 4.目标数组起始位置* 5.要复制的数组元素的数量*/System.arraycopy(array, index + 1, array, index, size - index - 1);}size--;return element;}/*** 扩容*/private void checkAddExpand() {if (capacity == size) {/*** 扩容1.5倍*///capacity = capacity + capacity >> 1;capacity += capacity >> 1;int[] newArray = new int[capacity];System.arraycopy(array, 0, newArray, 0, size);array = newArray;}}/*** 循环遍历** @param consumer*/public void foreach(Consumer<Integer> consumer) {for (int i = 0; i < size; i++) {consumer.accept(array[i]);}}/*** 流遍历** @return*/public IntStream stream() {int[] range = Arrays.copyOfRange(array, 0, size - 1);return IntStream.of(range);}/*** 迭代器** @return*/@Overridepublic Iterator<Integer> iterator() {return new Iterator<>() {int pointer = 0;@Overridepublic boolean hasNext() {return pointer < size;}@Overridepublic Integer next() {return array[pointer++];}};}
}

3.二维数组

java">int[][] arr = {{11, 12, 13, 14, 15},{21, 22, 23, 24, 25},{31, 32, 33, 34, 35},
};

在这里插入图片描述

  • 二维数组占 32 个字节,其中 array[0],array[1],array[2] 三个元素分别保存了指向三个一维数组的引用

  • 三个一维数组各占 40 个字节

  • 它们在内层布局上是连续

更一般的,对一个二维数组 A r r a y [ m ] [ n ] Array[m][n] Array[m][n]

  • m m m 是外层数组的长度,可以看作 row 行
  • n n n 是内层数组的长度,可以看作 column 列
  • 当访问 A r r a y [ i ] [ j ] Array[i][j] Array[i][j] 0 ≤ i < m , 0 ≤ j < n 0\leq i \lt m, 0\leq j \lt n 0i<m,0j<n时,就相当于
    • 先找到第 i i i 个内层数组(行)
    • 再找到此内层数组中第 j j j 个元素(列)

4.缓存与局部性原理

这里只讨论空间局部性

  • cpu 读取内存(速度慢)数据后,会将其放入高速缓存(速度快)当中,如果后来的计算再用到此数据,在缓存中能读到的话,就不必读内存了
  • 缓存的最小存储单位是缓存行(cache line),一般是 64 bytes,一次读的数据少了不划算啊,因此最少读 64 bytes 填满一个缓存行,因此读入某个数据时也会读取其临近的数据,这就是所谓空间局部性
定义两个求和方法
java">public static void sum1(int[][] arr, int rows, int columns) {long sum = 0;for (int i = 0; i < rows; i++) {for (int j = 0; j < columns; j++) {sum += arr[i][j];}}System.out.println("sum1:" + sum);
}
java">public static void sum2(int[][] arr, int rows, int columns) {long sum = 0;for (int j = 0; j < columns; j++) {for (int i = 0; i < rows; i++) {sum += arr[i][j];}}System.out.println("sum2:" + sum);
}

比较下面 s u m 1 sum1 sum1 s u m 2 sum2 sum2 两个方法的执行效率

java">int rows = 1000000;
int columns = 14;
int[][] a = new int[rows][columns];StopWatch sw = new StopWatch();sw.start("sum1");
sum1(a, rows, columns);
sw.stop();sw.start("sum2");
sum2(a, rows, columns);
sw.stop();System.out.println(sw.prettyPrint());

执行结果
在这里插入图片描述
可以看到 s u m 1 sum1 sum1 的效率比 s u m 2 sum2 sum2 快很多,为什么呢?

  • 缓存是有限的,当新数据来了后,一些旧的缓存行数据就会被覆盖
  • 如果不能充分利用缓存的数据,就会造成效率低下

http://www.ppmy.cn/embedded/129558.html

相关文章

PHP 任务管理:跨行业的科技驱动力量

PHP 任务管理至关重要&#xff0c;它能实现自动化流程&#xff0c;提升工作效率&#xff0c;如自动执行代码测试、订单处理等任务&#xff0c;减少人工操作的繁琐与错误。同时&#xff0c;通过合理的任务调度&#xff0c;确保关键任务优先执行&#xff0c;优化资源分配。在可靠…

网络连接设备的功能与应用概述

目录 一、集线器 二、交换机 三、网桥 四、路由器 五、集线器、交换机、网桥与路由器的比较 备注 一、集线器 定义&#xff1a; 集线器&#xff08;Hub&#xff09;是一种物理层设备&#xff0c;它提供多个端口&#xff0c;用于将多个计算机或其他网络设备连接在一起&am…

PetaLinux工程的常用命令——petalinux-config

petalinux-config&#xff1a;使用菜单配置项目或指定组件。 注&#xff1a;有些命令我没用过&#xff0c;瞎翻译有可能会翻译错了。 用法: petalinux-config [options] {--component <COMPONENT> |--get-hw-description[SRC]} 可选参数: -h, --help 显示函数用法…

【微信小程序_19_自定义组件(1)】

摘要:本文主要介绍了小程序开发中自定义组件的相关知识。包括组件的创建与引用,可在项目根目录创建组件文件夹,生成相应文件,并根据使用频率选择全局或局部引用。还阐述了组件和页面的区别,如组件的.json 文件需声明 “component: true”,.js 文件调用 Component () 函数…

MongoDB的基本操作

&#x1f337;数据库准备 &#x1f388;Mongoshell 1.在指定目录下创建mongodb文件夹、其子文件log和data以及mongodb.log cd /home/ubuntu mkdir -p mongodb/data mkdir -p mongodb/log touch mongodb/log/mongodb.log 执行mongodb命令启动mongdb服务 mongod --dbpath /h…

探索KPM71RUG7T68 SSD:企业级存储的可靠选择

KPM71RUG7T68 SSD是一款高性能企业级固态硬盘&#xff0c;专为满足数据中心及企业级应用的需求而设计。它采用了先进的NAND闪存技术&#xff0c;提供卓越的读写速度和可靠性&#xff0c;确保在各种工作负载下都能稳定运行。 首先&#xff0c;从性能上来看&#xff0c;KPM71RUG…

特步引入IPD管理,钉钉项目 Teambition 助力高效产品研发管理

中国是全球第二大消费市场&#xff0c;运动鞋服行业拥有着巨大的发展潜力。在过去五年时间里&#xff0c;随着中国产品品牌和质量的提升&#xff0c;体育市场的占有率格局发生了显著变化&#xff0c;不同于部分国际品牌巨头营收持续减弱&#xff0c;国产领军体育运动品牌「特步…

更改USB 网卡名称

在code/kernel-6.1/drivers/net/usb/usbnet.c文件中更 有几处地都可以改 最好在添加下面一行。但是要注意同时只能用一个usb网卡&#xff0c;多个不知道会怎么样 strcpy(dev->name,“eth1”); //第三处添加 usbnet_probe (struct usb_interface *udev, const struct usb_d…