数组:
数组是一种线性表数据结构。用一组连续的内存开间,存储具有相同类型的数据。
线性表:
数据像线一样的结构,只有前和后两个方向,除了数组,链表,队列,栈也是线性表结构。
![](https://i-blog.csdnimg.cn/blog_migrate/355998459c665627c1b17620c655a0c7.jpeg)
非线性表:
数据并不是简单的前后关系,如二叉树,堆,图等数据结构
![](https://i-blog.csdnimg.cn/blog_migrate/8a9d185364ce6558bc83025ebbde25b1.jpeg)
连续的内存空间和相同的数据类型:
使得数据能够被根据下标随机访问,让数组的某些操作变得低效,比如在数组中删除某一个值,为了保证空间的连续性,需要进行数据迁移。
数组如何实现根据下标随机访问?
数组会得到一块连续的内存开间,其中首地址为已知
例如首地址是base_addr = 1000
计算机随机访问数组时会通过下面的公式:
a[i]_adderss = base_addr + i * data_type_size
注意:
数组根据下标随机访问的时间复杂度是 O(1)
数组查找的时间复杂度是O(n),即使使用二分查找法,复杂度也是O(logn)
低效的插入、删除
插入:为了保证数组空间的连续性,在进行插入操作时,需要将插入位置后面的所有元素一一移动。最好时间复杂度是插入到数组末尾,不需要移动,复杂度为O(1)。最坏复杂度是,每一个元素都需要移位,复杂度为O(n)。平均复杂度为(1+2+3+...+n)/n,为O(n)。
删除:删除与插入一样,都需要将后面的元素一一移动。如果我们记录下元素删除的位置,将多次删除合并为一次删除操作来完成,等数组容量不足时再进行删除操作,这样效率会快很多。(例如JVM垃圾回收中的标记清除算法的思想)
警惕数组下标越界异常:
c语言中,只要不是访问受限的内存,所有内存都可以自由访问。这样当数组下标越界的时候,可能访问到某块不属于数组的内存,导致程序出现不一样的结果。
在java程序中,会抛出java.lang.ArrayIndexOutOfBoundsException。
容器替代数组
以java语言为例,ArrayList将数组的很多操作做了封装,同时支持动态扩容,在底层数组空间不足时,会重新分配一块更大的内存(原来大小的1.5倍),并将原来的数据拷贝过去。
注意:最好在创建数组的时候指定数组大小,尽可能避免数组进行扩容。
jdk1.6中,数组的初始大小是10,在1.8中,初始大小是0,在第一次add时才会变为10。
有时候使用数组更合适:
1.ArrayList无法储存基本数据类型,如int,long等,需要封装为Integer,Long,自动拆装箱会有一定的性能损耗,所以如果特别关注性能,或者希望使用基本数据类型,可以选用数组。
2.对于数据操作比较简单的场景,可以直接使用数组
数组为什么从0开始而不是1?
从数组存储的内存模型上来看,下标指的是偏移(offset),例如用a来作为首地址,a[0],表示偏移0的位置,a[i]表示偏移i个位置,如果使用1来作为开始,在进行寻址操作时,每次都要多进行一次减法运算。
历史原因:c语言设计者从0开始计数数组下标,java语言沿用了这一习惯。