3.数组
数组(Array)是一种线性表数据结构。它是一组连续的内存空间,来存储一组具有相同类型的数据。
3.1 特性
-
线性表
数据排成像一条线的结构,如数组、链表、队列、栈等。
与之相对立的是非线性,如二叉树、堆、图等,其数据之间并不是简单的前后关系。
-
连续的内存空间和相同类型的数据
-
可以实现随机访问
数组支持随即访问,通过寻址公式,计算该元素存储的内存地址
a[i]_address = base_address + i * data_type_size
根据下标随即访问的时间复杂度为
-
低效的插入和删除
为保证数组的连续性,就要做大量的数据搬移工作。
插入:
若在第k个位置插入数据,则k-n位置的数据需往后移。- 最好情况时间复杂度: (末尾插入)
- 最坏情况时间复杂度:
- 和平均情况时间复杂度:
改进:如果数组中数据是无序的,也就是无规律的情况下,在插入元素时,可以直接将第k位的数据搬移到数组元素的最后,把新的元素直接放入第k个位置。这样时间复杂度就会降为 。
删除:
和插入类似。- 最好情况时间复杂度: (删除末尾数据)
- 最坏情况时间复杂度:
- 平均情况时间复杂度:
改进:在某些特殊场景下,不追求数组中数据的连续性时,可以将多次删除操作集中在一起执行,减少数据搬移次数。先记录下已经删除的数据,但不进行数据迁移,仅仅是记录,当数组没有更多空间存储数据时,才触发执行一次真正的删除。这也是JVM标记清除垃圾回收算法的核心思想。
-
3.2 数组访问越界问题
要警惕数组的访问越界问题,例如在C语言中的数组越界是一种未决行文,循环越界访问会导致死循环bug。
3.3 用数组还是容器
数组需要预先指定了空间大小,容器如ArrayList可以将很多数组操作的细节封装起来,支持动态扩容。
- 希望存储基本类型数据,可以用数组
- 事先已知数据大小,并且对数据操作简单,用不到ArrayList提供的大部分方法,可以用数组
- 表示多维数组时,可以用数组
- 业务开发,使用容器足够;开发框架,追求性能,首先数组
3.4 为什么数组要从0开始编号
从数组存储的内存模型,下标最确切的定义是偏移,如果a来表示数组的首地址,a[0]就是偏移为0的位置,a[k]表示偏移k个type_size的位置,所以a[k]的内存地址计算如下:
a[i]_address = base_address + i * data_type_size
如果数组是从 1 开始计数,那么就会变成:
a[i]_address = base_address + (i-1)* data_type_size
对于CPU来说,多了一次减法的指令。
当然,还有一定的历史原因。