目录
什么是数组
数组(Array)是⼀种线性表数据结构。它⽤⼀组连续的内存空间,来存储⼀组具有相同类型的数据。
线性表就是好比一条线。把它弄直了,线上的点(数据)只有前后关系。它对应的非线性结构,点(数据)之间就不是单纯的前后关系了。也就是说数组就是一条线,一个简单序列,这个特点让数组变得非常快!但为了这种速度,数组是有所牺牲的,数组的大小被固定死了,在其生命周期中不可改变。
连续内存空间也好理解,数组这个数据结构在内存中申请的内存空间是连续的、挨在一起的,(与之对应的就是链表啦,不连续的内存空间)再加上数组存储的是相同类型,也就是说:知道了存储的数据类型与首个数组元素的地址,我们也就能够推算出任意一个数组元素的地址。比如我们知道首个数组元素的位置是3,而每个数组元素占2两个位置,那么就能算出此数组的第5个元素应该再3+2*(5-1)=11的位置。
数组的这个特点带来数组最强的特性:随机访问。可以根据上面的思想,在根据下标进行的随机访问时间时一步到位,复杂度为O(1)。
数组的特点
数组的优点
是骡子是马,咱们得拉出来遛遛。
首先数组得和基本数据类型变量比较,数组是同一种类型数据的集合,它表示:我能打N个!但容器表示俺也一样。
然后数组再和容器进行比较,在容器没有掌握泛型和自动包装机制之前,数组和容器打的那可叫有来有回,数组可以存储基本数据类型和引用类型,再以一个“快”字睥睨天下。但容器掌握泛型和自动包装机制之后,容器看起来能够搞定基本类型了,于是乎表示俺也一样,数组也就只剩下了“快”这个特点了。
简单的说:数组优点有:效率高(简单序列,快)、类型(存储同一个类型的数据)、保存基本类型的能力这三个优点。后面那两个优点多多少少已经被蚕食掉咯~
数组的缺点
前面我们有说到数组的优点是“快”现在我们来讲讲数组“快”在哪里?为什么会快?这个“快”牺牲了什么?
上面提到数组因为有“连续的内存空间”“相同类型的数据”,从而get到了“随机访问”的特性,快就快在这里。
而为什么会快我们也提到了“知道了存储的数据类型与首个数组元素的地址,我们也就能够推算出任意一个数组元素的地址。”“访问”也就是查找,数组的查找能够做到O(1)时间内完成。
当然,优劣同源,“连续的内存空间”导致数组在进行删除和增加操作时需要O(n)时间才能完成,具体的操作我们下面会讲到。
怎么用数组
数组的初始化与赋值
//前面说到,数组既可以存储基本数据类型也可以存储对象。
//基本类型数组
//初始化
int[] a;
int[] b = new int[5];
//赋值
a=new int[]{1,2,3,4,};
for(int i=0;i<b.length;i++){
//length是数组的大小,而不是实际保存的元素个数。
b[i]=i;
}
//对象数组
//初始化
cat[] c;
dog[] d = new dog[5];
//赋值
c=new cat[]{new cat(),new cat(),};
for(int i=0;i<d.length;i++){
if(d[i]==null){
d[i]=new dog();
}
}
对象数组保存的是引用,基本类型数组保存的是基本类型的值。
增删改查
初始化后,当然就到了最基础的操作:增删改查啦~
增:
增加分为两种,一种是末端增加(后者的特殊情况),一种是在中间随机插入,我们来讲讲中间插入的情况。
由于数组是占用连续内存空间的结构,要想在中间插入元素,就必须要把插入位置右边的所有元素依次向右移动,这是非常繁琐的操作。
如果在数组的末尾插⼊元素,那就不需要移动数据了,这时的时间复杂度为O(1)。
但如果在数组的开头插⼊元素,那所有的数据都需要依次往后移动⼀位,所以最坏时间复杂度是O(n)。
因为我们在每个位置插⼊元素的概率是⼀样的,所以平均情况时间复杂度为(1+2+…n)/n=O(n)。
(删除操作同理)
特殊情况:若数组不需要保证插入顺序,也不需要保证元素有序的话,可以将要插入的位置上的元素换至数组末端,这样就避免了大规模的数据移动!妙啊~
删:
删除操作和插入操作有异曲同工之妙,两个操作本身也就是互补的。在进行删除操作时,为了保证连续的内存空间,后面的元素必须向前挤,所以也是O(n)的复杂度
特殊情况:执行一次删除操作是O(n)我们有时候需要连续删除数组中的元素,这样就涉及到了多次移动数据。为了一定的效率,我们能不能将要删除的元素一起删除呢?这样就只需要进行一次移动数据的操作,那肯定是比多次移动要来的快的呀。于是乎我们记录下来需要删除的数组元素,等到触发一定的条件时(比如数组不够用了)我们就删除已经记录下来需要删除的数组元素。妙啊~其实这就是JVM标记清除垃圾回收的核心思想,嘻嘻。
改和查
改和查都涉及访问,在这一块数组终于扬眉吐气啦,时间复杂度为O(1)。改就是在查的基础上,将数组元素修改为指定的值。
总结:数组在增删改查四个基本操作里,增删分别为O(n)的时间复杂度,改查分别为O(1)的时间复杂度。在需要大量修改与查询的数据中,数组作为数据结构是很好的选择。但增删操作较多的数据集中,数组并不是好的选择。
数组的返回
当你写了一个方法想要返回的不止一个值,而是需要返回一组值时,你可能就需要用到数组的返回。
在c和c++中,你不能返回一个数组,只能返回数组的指针,这可能会造成内存泄漏,而在java中,你可以直接返回一个数组。
即 return a; //a是一个数组
二维数组
二维数组就存储数组的数组,你可以将int[行][列]看作 int[int[ ]]。同理直到n维数组。
//二维基本类型数组,对象数组也是一样的,嵌套for循环赋值即可。
int[][] a ={
{1,2,3},
{4,5,6},
}
Arrays
java.util类库中能找到Arrays类,有六个基本方法:
1、equals():判断两个数组是否相等,deepEquals()用于多维数组。相等条件:元素个数相等,对应位置元素相等
2、fill():用同一个值填充各个位置(复制同一个引用填充位置)
3、sort():给数组排序
4、binarySearch():在已排序数组中查找元素
5、toString():产生数组的String表示
6、hashCode():产生数组的散列码
还有
System.arraycopy():复制数组,比for循环来的快
数组通过实现Comparable接口用compareTo()方法实现数组元素的比较。(数组排序也依赖于数组元素的比较)