深入理解Java数组Array

本文详细介绍了数组的定义、有序性、有限性原因,以及数组如何实现高效随机访问。数组的内存地址计算、首地址、元素一致性及索引从0开始的原理得以阐述。同时,讨论了数组插入和删除操作的低效性及其优化策略,并讲解了数组扩容和拷贝的方法。最后,解释了偏移量和索引的概念以及计算方式。
摘要由CSDN通过智能技术生成

数组定义

数组(Array) :是有序1的元素序列。 若将有限2类型相同3的变量的集合命名,那么这个名称为数组名。组成数组的各个变量称为数组的分量,也称为数组的元素,有时也称为下标变量。用于区分数组的各个元素的数字编号称为下标,也叫索引4
当你刚开始接触数组时或许会有以下疑问:

  1. 为什么数组是有序的;
  2. 必须是有限个;
  3. 数组是如何高效地随机访问?数组地址是如何计算的?首地址存放在哪?为什么必须是相同的元素?为什么索引从0开始?
  4. 低效的“插入”和“删除”。
  5. 如何对数组进行扩容。
  6. 什么是偏移量,什么是索引。

    上述问题我们会在下面一一解答。

数组的优缺点

a. 优点:存取速度快。(数组直接按照地址进行存取)
b. 缺点:

  1. 需要一个连续的很大的内存(因为数组具有连续性,假设int[30000]arry 即需要分配连续的30000个空间,可能不存在)
  2. 插入和删除元素的效率低(当删除数组当中的一个元素时,因为数组具有连续性,所以需要后面的元素全部前移一位。同理:插入一位元素时,需要后面的全部后移一位)

一、为什么数组是有序的

  初始化一个数组的时候,jvm会在内存上分配一块连续的内存空间,每一个内存空间存一个元素,从首地址开始连续存放,所以数组是有序的。如下图所示:

数组内存空间
  右图可以看出数组的结构在内存空间上是连续的,内存地址也按照一定的规律顺序排序(下面讲有什么样的规律)每一块内存空间存放一个元素,内存空间块在整个数组的内存空间中的顺序就是数据的索引(下标)。

二、为什么数组必须是有限个

  由于数组的有序性,所以数组在初始化的时候,会需要一块很大的且连续的内存空间。如果数组元素能够无限添加,那么数组添加到一定的程度,可能就不存在这样一块连续的内存空间;而且数组的内存空间需要事先预留和分配,预留后才能保证数组的连续的内存空间不被其他数据占用。

三、数组是如何高效地随机访问?数组地址是如何计算的?首地址存放在哪?为什么必须是相同的元素?为什么索引从0开始?

  数组的一个特点是可以根据下标随机访问数组元素,其时间复杂度为 O(1),那么它是如何实现的呢?
  计算机分配的内存单元存储数据时,也会为内存单元分配一个地址,然后可以通过地址来访问内存中的数据。由数组的内存空间连续的特性,当需要访问某个元素时,它会通过下面的寻址公式来计算出该元素存储的内存地址:
在这里插入图片描述
  其中 data_type_size 表示数组中每个元素的大小,base_address 就是数组的首地址。如在 int 型的数组 arr 中,data_type_size 就为 4 个字节。
  那么数组的首地址是怎么知道的呢。其实数组名是一个指针,指向的就是数组的首地址。如上计算公式中的“arr”就是一个指针,指向数组的首地址,即base_address。
  由于数组只保存一个首地址,其余地址都是通过计算公式得出,而在公式中的base_address(首地址) 和 data_type_size(元素大小)都是常量,变量只是 i(索引),由此得出由于元素大小固定,所以数组中必须是相同的元素才能保证元素大小固定。
  当索引为0的时候,由公式可以看出 arr[0] 指向的是首地址,即数组中第一个元素,所以索引是从0开始的。

四、低效的“插入”和“删除”。

  在数组中,为了保持内存数据的连续性,会导致插入、删除这两个操作比较低效。
  例如在插入操作中,假设数组的长度为 n,若我们要在数组的第 k 个位置插入一个数据,为了把第 k 个位置腾出来给新的数据,我们需要将第 k ~ n 这部分的元素都顺序地向后挪一位: arr[i] = arr[i-1] 。其时间复杂度为 O(n)。
  而在删除操作中,若我们要删除数组的第 k 个元素,为了内存的连续性,就需要将第 k ~ n 这部分的元素都顺序地向前挪一位: arr[i] = arr[i+1] 。其时间复杂度为 O(n)。

“插入”和“删除”的优化

  然而在很多我们不需要考虑数组中元素的有序性,数组只被当作一个存储数据的集合的时候,为了避免大规模的数据搬移,我们可以对插入和删除操作做一些优化。例如:
  如果要将某个数据插入到第 k 个位置,可以直接将第 k 为的数据搬移到数组元素的末尾,然后将新的元素值直接赋值给第 k 个元素;
  如果要将第 k 个元素删除,可以直接将数组的最后一个元素赋值给第 k 个元素,然后删除最后一个元素即可。
  这样,其时间复杂度就会降为 O(1) 。

五、如何对数组进行扩容和拷贝

  1. 数组的扩容
      数组是根据固定容量创建的,在必要的时候我们需要对数组 arr 进行扩容,方法则是初始化更大的数组,然后再将原数组的数据拷贝到新数组中。
      在对数组进行拷贝时除了利用 for 循环遍历数组元素进行拷贝外,推荐使用更高效的 System.arraycopy() 方法。
  2. System.arraycopy() 方法拷贝数组
      System.arraycopy() 使用 native 关键字修饰,大大加快程序性能,为 JVM 内部固有方法。它通过手工编写汇编或其他优化方法来进行 Java 数组拷贝,这种方式比起直接在 Java 上进行 for 循环或 clone 是更加高效的。数组越大体现地越明显。
      该方法用于从指定源数组中进行拷贝操作,可以指定开始位置,拷贝指定长度的元素到指定目标数组中。
  3. 绝大部分数组和基于数组实现的容器(ArrayList ,栈等)的扩容都是基于 System.arraycopy() 方法进行操作的。

六、什么是偏移量,什么是索引。

  偏移量是指数组空间起始位置的偏移值。这个定义有点难懂,换个简单的说法则是到数组起始位置的元素个数。
  数组索引就是距数组首元素地址的偏移量。

如何计算偏移量

本文列举1-3维数组的计算方式。

  1. 一维数组:如arr[10],计算arr[4]的偏移量
      如果下标从0开始,则 arr[4] 是第5个元素,相对于起始位置相差4个元素,所以偏移量为4。
      如果下标从k开始,必要条件 k<=4 ,那么偏移量为4-k
  2. 二维数组:如以a[0…4][1…5]为例,计算a[2,2]的偏移量
      如果以行为主序:偏移量d=in+j(i,j下标从0开始)。上述例子可知,j的下标是以1为起始,则此时的偏移量为:d=25+(2-1)=11
      以列为主序:偏移量d=j*n+i(i,j下标从0开始)。此时偏移量d=(2-1)*5+2=7
  3. 三维数组:如数组a[0…3,0…2,1…4],求a[2,2,2]的偏移量
      三维数组计算a[i][j][k]的公式为d=i*n*o+j*o+k
      则可求得d=2*3*4+2*4+(2-1)=33
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值