- 为什么数组是从0开始不是从1开始呢
- 什么是数组?数组(Array)是一种线性表数据结构。他用一组连续的内存空间,来存储一组具有相同类型的数据
- 第一个是线性表(Linear List)线性表就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。除了数组,链表、队列、栈等等也是线性表结构
- 与线性表对立的概念是非线性表,比如二叉树、堆、图等等。之所以叫非线性,是因为,在非线性表中,数据之间并不是简单的前后关系
- 第二个是连续的内存空间和相同类型的数据。正是因为这两个限制,他才有了堪称杀手锏的特性“随机访问”。
- 但是这两个限制也让数组的很多操作变得非常低效,比如在数组中删除、插入一个数据,为了保证连续性,就需要做大量的数据搬移工作
- 数组是如何实现根据下标随机访问数组元素的????
- int[] a = new int [10]
内存块的首地址为base_address=1000
- 计算机会给每个内存单元分配一个地址,计算机通过地址来访问内存中的数据。当计算机需要随机访问数组中的某个元素时,会使用这个寻址公式,计算出该元素存储的内存地址 a[i]_address = base_address + i * data_type_size
- data_type_size表示数组中每个元素的大小
- 数组和链表的区别:链表适合插入、删除,数组适合查找,数组支持随机访问,根据下标随机访问的时间复杂度为O(1)
- 数组中低效的插入操作:假设数组长度为n,把一个数据插入到数组的第k个位置。为了给她腾位置,需要将第k~n的元素顺后挪一位。
- 最好的情况在末尾插入,不需要移动其他数据,时间复杂度为O(1)。但如果在数组的开头插入元素,那所有的数据都要往后挪一位,时间复杂度为O(n),随机插入的话平均情况时间复杂度为O(n).
- 为了避免大规模数据迁移,可以把第K个位置的元素移到最后,把新的元素直接放入k位置,这样的话时间复杂度O(1)
- 数组中的删除操作,跟插入数据类似,如果我们要删除第k个位置的数据,为了内存的连续性,也需要搬移数据,不然中间就会出现空洞,内存就不连续了
- 和插入类似,删除末尾元素,则最好情况时间复杂度为O(1),如果删除开头的数据,则最坏情况时间复杂度为O(n);平均情况时间复杂度也为O(n)
- 实际上,在某些特殊场景下,我们并不一定非得追求数组中数据的连续性。如果我们将多次删除操作集中在一起执行,删除的效率会提高很多。
- 类似于JVM标记清除垃圾回收算法的核心思想
- 警惕数组的访问越界问题
- 容器能否完全代替数组,针对数组类型,很多语言都提供了容器类,比如java的ArrayList,与数组相比有什么优势呢
- ArrayList最大的优势就是可以将很多数组操作的细节封装起来。比如前面提到的数组插入、删除数据时需要搬移其他数据等。并且他支持动态扩容
- 数组本身在定义的时候需要预先指定大小,因为需要分配连续的内存空间。如果我们申请了大小为10 的数组,当第11个数据需要存储时会重新分配一块更大的空间,将原来的数据复制过去,再将新的数据插入。
- 使用ArrayList,每次存储空间不足都会将空间自动扩容为1.5倍大小,因为扩容操作比较耗时,涉及到内存申请和数据搬移,所以我们可以在创建ArrayList的时候事先指定数据大小
- 当然了 数组也有很多适用场景
- ArrayList无法存储基本类型,比如int,long,需要封装为Integer、Long类,而Autoboxing、Unboxing则由一定的性能消耗,所以如果特别关注性能,或者希望使用基本类型,就可以用数组
- 如果数据大小已知,并且对数据的操作非常简单,用不到ArrayList提供的大部分方法,也可以直接使用数组
- 表示多维数组时,用数组往往会更加直观,比如Objectp[][] array;用容器的话:ArrayList<ArrayList> array.
- 对于业务开发,直接使用容器就足够了。底层开发还是数组,开发网络框架 性能的优化需要做到极致的时候 首选数组
总结:C语言设计者用0开始计数数组下标,之后的Java,JS等高级语言都效仿了C语言,或者说为了减少C语言工程师的学习Java成本,因此而为之。也有些语言,MatLab的数组不是从0开始计算。像Python支持负数下标