这一次,我就给大家介绍一下数组的定义和应用。 话不多说,正文开始!
一、数组的基本用法
1.什么是数组
数组本质上允许我们“批量”创建相同类型的变量。
例如:
如果需要表示两个数据,可以直接创建两个变量int a; 整数
如果需要表示五个数据,可以创建五个变量int a1; 诠释a2; 诠释 a3; 诠释 a4; 诠释 a5;
但是如果你需要表示 10,000 个数据,那么你就不能创建 10,000 个变量。 这时候就需要用一个数组来帮我们批量创建。
上面是一个整数数组。
注意:在 Java 中,数组中包含的变量必须是同一类型。
2.创建数组
基本语法
在上面的例子中,我们以整型数组为例,创建了一个int[]类型的array数组,存储了5个整型数据。 讲解了数组创建的用法,Java数组的创建和C语言很相似,但是还是有区别的。
C语言版本数组的创建:
Java版本数组的创建:
我们可以通过两种写法看出区别。 在 Java 中,[ ] 必须紧挨着数据类型。
数组的数据在内存中连续存储。 继续以上面的代码为例:
数组的每个元素都有其对应的下标,下标->从0开始。如果我们要在这个数组中查找某个数据,就需要通过数组的下标来访问。
注意:数组也称为集合,存储一组相同类型的数据! ! 下标从位置 0 开始。
3.定义数组的方式
定义一
我们上面写的代码定义并初始化了一个数组。
这种方式只定义了一个数组。 这是我们定义数组的第一种方式。 这样定义的数组默认大小为0。
定义方法二
这样也定义了一个数组,但是这个数组的定义是通过关键字new给数组分配了一块内存,而这块内存可以存放10个数据。
int [ 10 ] 分配了连续的内存空间。
但是此时我们还没有对array数组进行初始化,所以此时array中十个数据的默认值为0。
定义方法三
在Java中,初始化后,=左边的[]不能填数字,=右边的[]只能在第二种定义方式中赋值。
在第三个定义
总结一下定义数组的三种方式:
在这三种方式中,Java中定义数组最常用的方式是方式一。
4.数组的使用
(1) 获取长度
防范措施
使用 arr.length 获取数组的长度。 这个操作是一个成员访问操作符,后面在面向对象中会经常用到。
代码示例:
编译结果:
(2)访问数组中的元素
如何访问数组:
防范措施:
1. 使用 [ ] 选择数组元素。 需要注意的是下标从0开始计数
2、使用[ ]操作不仅可以读取数据,还可以修改数据。
3、下标访问操作不能超过有效范围[0,length),如果超过有效范围,会出现下标越界异常
代码示例:
编译结果:
(3)下标越界
数组下标从0开始,取值范围为[0, arr.length),左闭右开区间,即[0, arr.length-1]。
如果我们下标数组边界之外的值…
如下
编译结果:
(4)遍历数组
所谓“遍历”是指访问数组中的所有元素,不重复不遗漏。 通常需要搭配循环语句
1.遍历方法(一)—–for循环
编译结果:
我们可以看到,数组中的元素是用for循环一个一个遍历并打印出来的。
2.遍历方式(2)—->for-each
for-each 是使用 for 循环的另一种方式。 可以更轻松的完成数组的遍历。 它可以避免循环条件和更新语句。
for-each 的基本用法
代码示例:
编译结果:
for-each遍历的原理
遍历数组中的每一个元素,取出每一个元素,然后赋值给x,最后打印x,直到遍历完数组中的所有元素。
两种遍历方式我们介绍完了,那么for循环和for-each有什么区别呢?
for循环可以得到数组下标,但是for-each不能得到数组下标java初始化数组,所以for-each只能遍历所有,不能对数组元素进行修改和操作。
3.遍历方法(3)——使用工具类操作数组打印数组
Arrays是一个用于操作Java数组的工具类。 如果你想用一个数组做一些事情,你可以通过它来完成。 当然,有些事情是它做不到的。
比如:我们要打印一个数组,我们本来是用for循环或者for-each来写的,但是我们也可以使用Arrays工具类来打印。
通过JDK的工具文档,我们找到了对应的工具类。
好吧,我们对arr数组进行如下操作:
编辑结果:
5.数组在内存中的存储
我们在之前的博客中简单介绍过Java中的内存区域划分,那么今天我们就知道了数组的引用类型,那么它是如何存储在内存中的呢?
下面简单回顾一下Java的内存区域
我们知道局部变量存放在Java虚拟机栈中,而数组数据存放在堆中。 数组的数据在堆上有一个特定的地址,数组的变量实际存放的是这组数据的地址。 栈上的这个变量根据这个地址找到堆上的数据。
数组的具体存储如下图所示:
当心:
上图是arr指向的数据在堆中的地址。 这个地址不是真实地址,是通过官方地址哈希得到的。 但是我们可以把它当作一个真实的地址,因为这个地址也是唯一的。
那么为什么需要对真实地址进行哈希处理呢?
这就是Java的安全性,自身数据的地址不会轻易暴露。
二、数组作为方法的参数
一、基本用法
代码示例:打印数组内容
在这段代码中
1.int[ ] a是函数的形参,int[ ] arr是函数的实参。
2.如果需要获取数组的长度,也可以使用a.length
2.理解引用类型
在上一篇博客的方法使用中,我们介绍了一个使用方法交换两个变量的具体案例。 现在让我们回顾一下。
(1)参数传递内置类型
我们使用内置类型作为参数来交换变量,但是在最终的编译结果中这两个变量并没有交换。
为什么是这样?
在不影响实参值的情况下交换形参的值。
(2)参数传递数组类型
我们使用数组作为参数来交换变量。 编译运行后,我们发现两个变量的值已经成功交换了。 这时候数组名arr就是一个“引用”。 传递参数时,参数通过引用传递。
那么为什么传引用类型既可以有形参又可以对实参进行操作呢?
在这里,我们将从内存开始。 上面我们介绍了数组在内存中的存储。
我们可以知道,栈中存储的变量实际上存储的是数据在堆中的地址。 当我们将arr数组作为参数传递给方法时,我们传入的是数据在堆中的地址。 在方法内部,我们可以根据这个地址找到堆中的数据,然后修改数据,从而实现形参改变实参的操作。
总结:
所谓“引用”,本质上只是存储了一个地址。 Java 将数组设置为引用类型。 这样,后面的数组参数传递实际上只是将数组的地址传递给函数参数。 这样可以避免整个数组的Copy(数组可能比较长,所以copy开销会很大)。
3.识别null
引用类型的 0 值为 null。
当我们将 null 分配给引用类型 arr 时,这意味着什么?
表示arr的引用,不指向任何对象。
当我们运行这段代码时,显示的结果是 null,而不是值 0。
那我们再看一个问题,当我们给arr赋null的时候,arr数组的长度是多少?
我们猜测它可能是 0,现在让我们运行代码。
运行结果如下:
此时小编报错,错误类型:空指针异常。
好了,到这里我们就可以知道,null被赋给了arr,而arr并没有指向任何数组对象,堆上也没有分配内存空间,所以我们无法找到它的长度。
总结:
无效的。 任何事情,都会出现空指针异常错误。
经验:以后只要出现这样的异常,就一定是this reference为null。
4.初识JVM内存区域划分
一栋宿舍楼会分成几个不同的区域:大一、大二……计算机专业、通信专业……
记忆也差不多,这条宽阔的走廊被分成了很多部分,每个区域存储着不同的数据。
JVM的内存分为几个区域,如图:
程序计数器(PC寄存器):
它只是一个小空间,用于保存下一条要执行的指令的地址。
虚拟机堆栈(JVM Stack):
重点是存放局部变量表(当然还有其他信息)。 我们刚刚创建的int[] arr的存储地址的引用就存储在这里。
本机方法堆栈
本地方法栈的作用类似于虚拟机栈。 只有保存的内容才是Native方法的局部变量。 在某些版本的 JVM 实现中(如 HotSpot),本地方法栈和虚拟机栈是在一起的。
堆:
JVM 管理的最大内存区域。 使用new创建的对象存放在堆上(比如之前的new int[]{1, 2, 3})
方法区:
用于存放虚拟机已经加载的类信息、常量、静态变量、just-in-time编译器编译后的代码等数据。 方法编译后的字节码存放在这个区域。
运行时常量池:
它是方法区的一部分,存储文字(字符串常量)和符号引用。 (注意从JDK1.7开始,运行时常量池在堆上)
关于上面的划分方式,我们会在后面的学习中慢慢了解。 这里重点了解虚拟机栈和堆。
1.局部变量和引用存放在栈中,新对象存放在堆中。
2.堆空间很大,栈空间比较小。
3、堆是整个JVM共享的,每个线程都有一个栈(一个Java程序中可能存在多个栈)。
三、数组作为方法的返回值
代码示例:
这段代码当然是可行的,但是它破坏了原来的数组。 有时候我们不想破坏原来的数组,就需要在方法内部新建一个数组,并且有方法返回。
修改后的代码:
这样原数组就不会被破坏。
另外,由于数组是引用类型,返回时只将数组首地址返回给函数调用者,并不复制数组内容,效率更高。
4.数组练习
1.数组转字符串
题目要求:
实现方法 toString 将整数数组转换为字符串。
比如数组{1, 2, 3},返回的字符串是“[1, 2, 3]”,注意逗号的位置和个数。
2.数组复制
如何复制数组
1.for循环复制
在这个copy方法中,我们先通过new新建一个与arr等长的数组copy,然后通过for循环将arr数组的内容逐一赋值给copy数组,达到最终的数组copy效果。
2.Arrays数组的工具类
1) 副本()
我们先通过JKD的工具文档看看copy工具的使用方法
功能:复制指定的数组,用零截断或填充(如果需要)以使副本具有指定的长度。
看一下Java中copyOf方法的具体实现
首先,Arrays.copyOf()的返回类型是int[],第一个参数是原数组(要复制的数组),第二个参数是新数组的长度(可以自己设置)。 如果新数组的长度大于 如果原数组长,则大于原数组长度的元素用0填充。具体如下…
(2) copyOfRange()
我们先通过JDK文档查看一下这个工具类的功能
功能:将指定数组的指定范围复制到一个新数组中。
看一下Java中copyof方法的具体实现
copyOfRange方法的返回类型是int[],第一个参数是原数组,第二个和第三个参数是要复制的原数组数据的下标,一定要记住是左闭右- 开区间,[ 从 , 到 )。
代码示例
3.系统。 阵列复制
我们打开System.arraycopy方法的具体实现,发现并没有上述copy方法的实现过程。 System.arraycopy是之前native中的native方法。
本机方法
1.在本地方法栈上运行
2.底层用C/C++代码实现
System.arraycopy 没有返回值。 第一个参数是原数组(要复制的数组),第二个参数是要复制的原数组的下标,第三个参数是目标数组,第四个参数是目标数组下标,第五个数组是要复制的长度。
代码示例:
当心:
System.arraycopy 的最后一个参数是要复制的数组的长度。 这个数据不能超过原数组的长度,否则编辑器会报错:数组越界。
4. Array name.clone —->生成当前数组的副本
功能:生成当前数组的副本。
代码示例:
3.找到数组中最大的元素
题目内容:给定一个整型数组,找出其中最大的元素(同理,寻找最小的元素)
代码:
4.求数组中元素的平均值
主题内容
给定一个整数数组,求平均值
代码:
当心:
最后,在aver方法中计算平均值的时候,一定要记住sum * 1.0,这样计算出来的平均值就是double类型的数据。
5.在数组中查找指定元素(顺序查找)
主题内容
给定一个数组和一个元素,找出该元素在数组中的位置。
代码
6.在数组中查找指定元素(二分查找)
对于排序数组,可以使用更高效的二分查找。
什么是有序数组?
顺序分为“升序”和“降序”。 比如1 2 3 4,按顺序递增就是升序。 比如4 3 2 1,降序就是降序。
以升序数组为例,二分查找的思路是先取中间位置的元素,看你要找的值是大于还是小于中间的元素。 如果比较小,往左边找; 否则,去右边找到它。
代码:
二分查找的具体思路可以参考我之前的一篇博客——在一个有序数组中查找具体的数字n(二分查找)详解。
7.检查数组的顺序
主题内容
给定一个整型数组,判断该数组是否有序(升序)
代码:
当心:
记得检查传入的数组是否为null,如果传入的arr数组为null,则返回false类型。
8.数组排序(冒泡排序)
主题内容
给定一个数组,按升序(降序)顺序对数组进行排序。
算法思路
每次它试图在当前区间中找到最小(或最大)的元素进行排序,并将其放在数组的前面(或最后)。
代码:
关于冒泡排序的详细讲解可以看我之前的博客——排序算法之冒泡排序,这里就不过多介绍了。
9.数组倒序
主题内容
给定一个数组,以相反的顺序对元素进行排序。
思路
设置两个下标,分别指向第一个元素和最后一个元素。 交换两个位置的元素。
然后让上一个下标自己增加,下一个下标自己减少,如此循环下去
代码:
五、二维数组
二维数组本质上是一维数组,只是每个元素都是一维数组。
正则二维数组
(1) 二维数组的定义
(2)内存中的二维数组
以上面的int[2][3]为例
前面我们说过,二维数组本质上是一种特殊的一维数组。
这个数组的每一行arr[0]和arr[1]构成一个一维数组,每一行存储指向每一列数据的地址。
每一列也是一个单独的一维数组,指向堆中的每一个数据。
(3) 二维数组的打印
我们知道二维数组在内存中的存储和方向,所以我们知道arr.length获取行数,arr[i].length获取列数。
for循环打印
我们用 for 循环打印这个二维数据。
代码示例:
打印结果:
2. for-each 打印
如果我们使用 for-each 打印,代码示例:
打印结果如下:
3.Arrays工具类打印
在一维数组中,我们希望使用 Arrays.toString() 将数组转换为字符串以进行打印。 那么二维数组转字符串的工具类是什么呢?
如果我们使用 Arrays.toString() 打印
结果打印 arr 行表示的数组的内容——列表示的一维数组的地址。 显然 Arrays.toString() 无法打印出二维数组的全部内容。
我们在 JDK 文档中找到了 deepToString() 工具类。
功能:返回指定数组的“深度内容”的字符串表示。
我们用 deepToString() 打印…
结果如下:
成功打印出二维数组的内容。
deepToString()可以正确打印二维数组的所有数据。
不规则二维数组
在C语言中,当我们定义一个二维数组时,我们可以只定义列而不指定行的值。
C语言数组的定义
在Java中java初始化数组,我们可以只定义行,列不需要指定值。
Java中不规则二维数组的定义
什么是不规则二维数组?
前面的常规二维数组,每一行的数据个数是一样的,列数也是一样的。 对于一个不规则的二维数组,行数是指定的,列数是自己定的,每行的列数是自己定的。
(1) 不规则二维数组的定义
编译结果:
首先我们指定一个有两行的二维数组
int [ ] [ ] arr = new int [2] [ ];
我们指定数组的每一行有多少列。
arr[ 0 ] = new int[ ] { 1,2,3 }
arr[ 1 ] = new int[ ] { 4,5 };
这就是不规则二维数组的定义。
(2)内存中的二维数组
与常规的二维数组内存存储基本相同。
(3)方式
同样,还有“三维数组”、“四维数组”等更复杂的数组,但出现的频率很低。
今天的分享到此结束,还请大家多多包涵,指点指点!