为什么很多编程语言中数组从0开始编号?而不是从1呢? 从1开始不是更符合人类的思维习惯吗?
什么是数组?
数组(Array)是一种线性表
数据结构,它用一组连续的内存空间
,来存储一组有相同类型
的数据。
线性表
顾名思义,线性表就是数据排成一条线一样的结构,每个线性表最多只有前
和后
两个方向。
除了数组,链表、队列、栈也是线性表结构
。
非线性表
比如二叉树、堆、图等。之所以是非线性,因为里面数据之间并不是简单的前后关系
。
连续的内存空间和相同类型的数据
这两个特性给了数组一个杀手锏的特性:随机访问
。
有利有弊,这两个限制让数组很多操作非常低效
。
比如在数组添加或删除一个数据
,为了保证连续性
,需要大量的数据搬移
工作。
如何根据下标随机访问数组元素
计算机会给每个内存单元分配一个地址,计算机通过地址来访问内存中的数据。
当计算机需要随机访问数组中的某个元素时,通过下面的寻址公式,计算出该元素存储的内存地址。
根据寻址公式:
1 | a[i]_address = base_address + i * data_type_size |
比如C语言, 且此时为int类型。此时data_type_size为4个字节。
纠正错误
链表适合插入、删除、时间复杂度为O(1),数组适合查找,查找复杂度为O(1)
正确表达是:
数组支持随机访问,根据下表随机访问的时间复杂度为O(1)
低效的插入和删除
如果在数组的开头插入数据,此时时间复杂度为O(n)
如果在数组的尾部插入数据,此时时间复杂度为O(1)
因为我们在每个位置插入元素的概率是一样的。
平均时间复杂度:(1 + 2 + 3 + … + n) / n = O(n)
如果说数组有序,我们在某个位置插入一个新的元素,就必须按照上面的方法搬移k之后的数据。
如果数组中存储的数据并没任何规律,此时要将某个数组插入到第k个位置,为了避免大规模的数据搬移,还有个简单的办法是,直接将第k位的数据半到数组元素的最后,把新的元素直接放入第k个位置。
此时在第k个位置插入一个元素的时间复杂度为O(1)
删除操作
与添加操作类似
警惕数组访问越界
容器是否能完全替代数组
ArrayList可以将数组操作的细节封装起来
,且支持动态扩容
。
大部分方法的话,比如申请了一个大小为10的数组,当第11个数据需要存储到数组中,我们就重新分配一块更大的空间,将原来的数据复制过去,再将新的数据插入。
但扩容操作涉及内存申请和数据搬移,是比较耗时的,所以最好实现确定需要存储的数据大小。
总结一下:业务开发用容器省时省力,会损失一些性能。但完全不会影响到系统整体的性能。
若是开发底层的,比如并发网络框架,性能的优化就必须要做到极致。
解答开篇
数组存储的内存模型上,”下标”k 就是”偏移(offset)”
寻址公式:1
a[k]_address = base_address + k * type_size
但要是按照从1开始计数:1
a[k]_address = base_address + (k-1) * type_size
此时多了一条减法指令, CPU就多执行一次减法运算. 所以最好从0开始编号.