1. 标准数组
1.1 数组的定义
在计算机科学中,数组数据结构,简称数组(Array),是由相同类型的元素的集合所组成的数据结构,分配一块连续的内存来存储。利用元素的索引可以计算出该元素对应的存储地址。
Java
int[] arr = new int[3];
C/C++
int arr[3];
1.2 解释一些疑问
1.2.1 为什么要指明数据类型和大小?
首先在创建数组的时候,需要向内存申请一块空间,而这块空间的大小就是由数据类型和数组长度来决定的。比如上文中,int 类型是 4 个字节,长度为 3,那么就需要 4 * 3 = 12 个字节的内存空间。
在内存中的存放方式形如:
1.2.2 为什么数组长度不可变?
首先数组的内存是连续分配的,如果长度发生改变,那说明占用的内存空间也会发生改变,但是原空间的后面部分并不能保证是可用的。
1.2.3 为什么数组下标从 0 开始?
这都归结于数组的寻址方式:array[i]Address = headAddress + i * dataTypeSize。如果改为从 1 开始,当然是符合人类的直观逻辑,但是对于 CPU 来说,每次就需要多做一次减法。
2. JavaScript 中的数组
通过上一节的介绍,其实很明显地就能发现,JS 中的数组有点特殊,除了下标也是从 0 开始以外,其他的几乎都不太一样,甚至可以说是毫不相干。JS 中的数组,可以存储各种类型的数据并且也没有固定的长度。
2.1 为什么可以存储不同类型的值
因为,Array 继承自 Object。
2.2 JS 中的数组是怎么存储的?
const arr = [1];
arr[2000] = 2000;
// arr 会如何存储?
2.2.1 数组的模式
由注释可以看出,JSArray 分为两种模式:
- 快模式,储存结构为 FixedArray,数组长度 <= 元素长度。push 和 pop 操作会被用来扩容/缩容。
- 慢模式,储存结构为 HashTable,以数字为键值。
快、慢数组的区别
- 存储方式:快数组是连续存储的,慢数组是零散的。
- 内存使用:快数组因为是连续的,其中可能还存在空洞,所以比较费内存。慢数组不存在空洞,省内存。
- 遍历效率:快数组空间连续,遍历速度快。慢数组每次都要寻找 key 的位置,效率差一些。
2.2.2 数组的转换
- 快数组 => 慢数组 a. 新容量 >= 3 * 旧容量 * 3
b. 新增索引与原来最大索引差值大于 1024
- 慢数组 => 快数组
当能节省不少于 50% 的空间时,才会转换。
2.2.3 动态扩容和缩容
初始化一个空数组会预先分配四个元素的内存大小。
扩容:
新容量 = 1.5 * 旧容量 + 16
缩容:
如果容量大于等于 length * 2 + 16,则进行缩容调整。通过判断 length + 1 == old_length 来确定是收缩一半容量还是全部。
2.3 数组中的元素类型
const arr1 = [1, 2, 3]; // PACKED_SMI_ELEMENTS
const arr2 = [1, 2.2, 3]; // PACKED_DOUBLE_ELEMENTS
const arr3 = [1, '2', 3]; // PACKED_ELEMENTS
const arr = [1, 2, 3]; // PACKED_SMI_ELEMENTS
arr[9] = 4; // HOLEY_SMI_ELEMENTS
// bad
const arr = new Array(3); // HOLEY_SMI_ELEMENTS
arr[0] = 1; // HOLEY_SMI_ELEMENTS
arr[1] = 2; // HOLEY_SMI_ELEMENTS
arr[2] = 3; // HOLEY_SMI_ELEMENTS
// good
const arr = new Array(1, 2, 3); // PACKED_SMI_ELEMENTS
const arr = [1, 2, 3]; // PACKED_SMI_ELEMENTS
但,这也不绝对。