前言
希望基于此系列文章能以轻松,简单的方式帮助大家学习并使用数据结构。
今天进入编程修仙第三期,炼气一层,数组(下),数组的基本概念和思想在上次文章中已经详细介绍,有感兴趣的同学可以跳转查看穿越到修仙界,筑基功法居然是数据结构(2)——数组(上)。本篇将介绍数组的使用。
什么时候使用数组
数组适用于以下场景:
- 根据元素序号快速访问数据
- 没有频繁的插入删除操作
- 数组元素数量相对固定,不会频繁变化扩容
但同时,由于数组是最简单,最基础的数据结构,在需要存储一批数据数据时,如果没有其他明显更优的数据结构或者特殊需求,数组都是最保险的选择(之一)。
选择合适的数组实现
静态数组
在使用数组前,需要先创建变量,这时候就会发现一个问题,像java中的array
,在定义变量的时候必须要指定类型和数组个数,如下:
int[] array = new int[5];
但是在数组的定义里面,对于数组元素的数量是没有限制的,那为什么在变量定义的时候要指定数量呢?
这是因为数组是需要连续保存数据的结构,这隐含了一个前提条件,那就是内存中需要有足够大的连续空间保存数据,这段地址需要被预留下来,不能插入其他变量数据,否则就会打破数组元素的连续性。所以为了预留足够的空间,就必须在变量定义的时候,指定数据类型和元素数量。
同样,在变量定义,内存分配完之后,就不能再增加元素数量,因为不能确保超出了预留地址之后的邻近连续地址还是空闲的,可能已经存储了其他数据。
像这种在变量定义的时候就已经确定元素数量的数组,就是静态数组。
也有资料说:静态数组是在编译时声明并分配内存空间的数组。
个人理解,这里的静态对应的理解是static关键字(静态变量/成员),和上文提到的是两种观念。
动态数组
那与静态数组相反,在变量定义的时候不用确定元素数量,且在变量使用过程中可以动态调整数组大小,就是动态数组。
那动态数组是如何实现的?其实非常简单:
- 动态数组和静态数组一样在变量定义的时候,就会指定数组大小用于内存空间分配(如果程序中没有指定的话,就会分配默认值),接下来
- 运行过程中,随着不断添加元素,当内存预留空间被用完时,将会创建一个新的更大内存空间的数组,并将之前数组的数据复制到新数组中,之后原来数组将不被继续使用
由此可见,动态数组和静态数组底层逻辑其实相同,只是动态数组贴心提供了自动扩容及“搬家”服务。像Java中的ArrayList,Python中的list,array都是动态数组。
具体使用如下
Java ArrayList :
//初始化时不定义数组大小
ArrayList<String> array = new ArrayList<String>();
array.add("array1");
array.add("array2");
array.add("array3");
Python list :
//初始化时不定义数组大小
list1 = [];
list1.append('array1')
list1.append('array2')
list1.append('array3')
可以看出,动态数组可以实现动态扩容,并且在声明时可以不定义数组大小。
静态数组 VS 动态数组
动态数组可以动态调整数组大小,在不同的语言实现中,还会封装更多的方法,使用更灵活,几乎可以胜任任何使用数组的场景,这从业务代码开发中大量的ArrayList就可以看出。
但静态数组也有其优势,动态数组为了实现动态扩容等功能,需要额外的内存存储对象头信息,导致性能不如静态数组。所以当数组大小可以明确,并且对性能有高要求时(如网络框架,图像处理等),应该使用静态数组。
数组的使用
注意数组的大小
针对静态数组,需要在定义时确定好数组大小,这时需要尽量精确,如果定义小了,那后续程序不够用到处报错,如果定义太大,又会导致内存浪费。
动态数组虽然可以动态扩容,但是这仍然是需要消耗大量性能(O(n)),所以应该尽量减少扩容的次数。因此在初始化时也需要对数组大小进行预估,并定义合适的大小,以免频繁扩容造成性能浪费。比如,要插入10000条数据,可以在创建时指定初始化大小为10000,避免大量的扩容复制迁移操作。
Java:
//初始化时定义数组大小,避免频繁扩容
ArrayList<String> array2 = new ArrayList<String>(10000);
Python:
//初始化时不定义数组大小
list2 = [None] * 10000;
注意数组中元素的类型
如上一篇文章提到的,数组其实并不一定要求元素类型一致(只要求大小),但是出于实际业务场景和开发的需要,一般还是会指定相同的数据类型。
同时在一些高级语言中,如Java,使用ArrayList时无法使用基本类型,只能用Integer,Long等封装类,这会产生装箱和拆箱的操作,造成性能影响,所以如果有类似场景并且对性能有影响,可以直接使用Array。
结束
数组篇基本就到此为止,下一篇将开始链表。数组相关的使用写的比较少,因为确实简单。等后面写到更多数据结构,体现出不同的时候就会有对比了。