编辑
前言
大家好,我是程序猿爱打拳,本期主要讲解数组的基本概念、数组的类型、数组的应用场景。并且配有练习题目的讲解,图文并茂通俗易懂,欢迎大家的阅读。
目录
1.数组的基本概念
1.1数组的好处
当我们打印出3个同学的数学成绩的时候。我们可以这样做:
public class Test { public static void main(String[] args) { int score1 = 99; int score2 = 80; int score3 = 60; System.out.println(score1); System.out.println(score2); System.out.println(score3); } }
输出效果:
编辑
我们发现少量的数据进行打印的时候是非常容易,那么如果有100个人的成绩或者200个同学的成绩需要打印呢?这时候一个一个去打印就非常的麻烦了,因此我们就有了一个概念就是本期所要学的数组。它可以将这些相同类型的数据存放在一个集合里面,这就是它的好处。
1.2什么是数组
数组是相同类型元素的一个集合。在内存是连续存放的,类似于现实生活中的车库。
编辑
以上图片就是两个连续的存储方式,左侧就是我们的数组大概的模样。数组的几个注意事项:
- 数组中存放每个元素类型都一致
- 数组的空间是连续存放的
- 数组的下标从0开始依次往后递增
1.3数组的定义及初始化
1.3.1数组的创建
在上面我们知道数组的用处,以及在内存大概的布局模样后,数组是如何创建的呢?
type[] 数组名 = new type[N];
type:表示数组中存放的各个元素的类型
type[]:表示数组的类型
N:表示数组的长度
创建三个数组:
int[] arry1 = new int[10]; double[] arry2 = new double[5]; String[] arry3 = new String[3];
int[] arry1 = new int[10];//定义一个名为arry1的数组,里面可以存放10个整型元素。
double[] arry2 = new double[5];//定义一个名为arry2的数组,里面可以存放5个双精度浮点型元素。
String[] arry3 = new String[3];//定义一个名为arry3的数组,里面可以存放3个字符串。
1.3.2数组的初始化
数组的初始化分为动态初始化以及静态初始化。
(1)动态初始化,我们直接规定数组元素的个数,如:
int[] arry= new int[10];
(2)静态初始化,在创建数组的时候不规定数组元素的个数,并直接将数组的内容进行设定,如:
int[] arry1 ={1,2,3,4,5}; double[] arry2 ={3.1,2.4,6.3}; String[] arry3 ={"abc","123","hello"};
注意:
- 静态初始化的时,虽然没有指定数组元素的个数。但直接将数组设定为固定的值后,编译器会自动计算出该数组元素的个数。因此大家不必担心。
- 静态初始化时,{}里面的每个元素应保持类型一致,并且使用,号隔开。
当我们创建数组后并未对其进行初始化,数组中元素会自动设置默认值为0。如:
public class Test { public static void main(String[] args) { int[] arry = new int[10]; System.out.println(Arrays.toString(arry)); } }
输出:
编辑
以上代码,我们发现创建一个整型数组后没有对其进行初始化,它的每个元素默认都为0。在上述代码中,我引用了一个方法Arrays.toString(数组名)意为:以字符串的形式输出该数组。那么数组中存储元素的类型为基本类型,其各个类型对应的默认值,如下表所示:
类型 | 默认值 |
byte | 0 |
short | 0 |
int | 0 |
long | 0 |
float | 0.0f |
double | 0.0 |
char | /u0000 |
boolean | false |
大家可以下来自己测试一下,在此我就不一一测试了。注意,如果数组中存储元素类型为引用类型,其默认值为null,下面我会讲到。
1.4数组的使用
1.4.1访问数组中的元素
在1.2中我们知道了,数组是从下标为0的下标依次往后执行的。并且数组可以通过下标访问到想访问到的数据。比如我想访问到一个数组的第5个数,我们可以通过下标为4的数组找到该元素。
编辑
请看以下代码:
public class Test { public static void main(String[] args) { int[] arry ={1,2,3,4,5}; System.out.println(arry[0]); System.out.println(arry[4]); arry[0] = 6; System.out.println(arry[0]); } }
输出:
编辑
观察以上代码,我们不难发现。当我们想访问那个元素的时候,直接数组名[下标]就行。唯一要注意的是第一个元素下标为0因此,当你想访问第5个元素时下标应当为4。当然我们也可以修改数组里面的元素,例如上述代码中,我修改了第一个元素使得1变为6。
注意,数组是一段连续的存储空间,我们需要依次通过下标来访问各个元素。当我们知道一个数组总共有6个元素的时候,我们不能去访问第6个元素以后的空间。如:
public class Test { public static void main(String[] args) { int[] arry ={1,2,3,4,5,5}; System.out.println(arry[6]); } }
显示:
编辑
当我们访问超出该数组的范围后的元素,会出现java.lang.ArrayIndexOutOfBoundsException 异常。我们称为数据越界访问,因此我们把数组下标的范围介于[0,N)。
1.4.2遍历数组
遍历,我们认为是依次从左往右打印出数组的各个元素,类似于现实生活中的从左往右的点到。例:
public class Test { public static void main(String[] args) { int[] arry ={1,2,3,4}; System.out.println(arry[0]); System.out.println(arry[1]); System.out.println(arry[2]); System.out.println(arry[3]); } }
输出:
编辑
以上代码就是遍历一个数组的过程,但我们发现遍历这个数组的代码太大了。如果一个数组的元素很少,我们可以用此方法进行遍历。但如果一个数组了有成千上万的元素时候,一行一行的去打印的话会很浪费大量时间。因此我们可以用到for循环语句来实现该遍历。
public class Test { public static void main(String[] args) { int[] arry ={1,2,3,4}; for (int i = 0; i < 4; i++) { System.out.println(arry[i]); } } }
输出:
编辑
通过for语句,进行打印我们会便利许多,不必一行一行进行遍历。在上述代码中,我们发现创建数组时候我们知道了元素的个数,可以进行设置for语句的终止条件,但如果给我们一个别人创建的数组我们如何取得其长度呢?Java中提供了一个数组名.length的方法可取得数组元素的个数。如:
public class Test { public static void main(String[] args) { int[] arry ={1,2,3,4}; for (int i = 0; i < arry.length; i++) { System.out.println(arry[i]); } } }
输出:
编辑
还有一种遍历方法,那就是for-each:
public class Test { public static void main(String[] args) { int[] arry ={1,2,3,4}; for (int x:arry) { System.out.println(x); } } }
输出:
编辑
for-each是for循环的另一种表示方式,它能很清晰的遍历出数组的各个元素,可以避免循环条件和更新语句写错。
2.数组的类型
数组是一种引用类型,数组也是一数据结构,只不过数组的结构比较易懂,存储的都是相同元素。为什么数组是一种引用类型,下面就让我来一一讲解。
2.1认识JVM的内存分布
内存是一段连续的存储空间,主要是用来存放正在运行的程序的数据。比如:
- 程序运行时代码要加载到内存
- 程序运行时产生的中间变量要存放在内存
- 程序中的常量也在内存中
如果内存没有把这些程序运行的结果有条理的存放在一块块类似于单元格里边,那内存将存不了一点东西,甚至会造成我们数据的丢失。就像我们生活中,我刚回到家把钥匙随手一扔。由于桌面上很乱,导致我下一次出门时一时半找不到钥匙,甚至会把钥匙当成垃圾给扔掉。内存亦是如此,如果我们不按条理的把一些数据任意存放就会造成无法挽回的后果。
编辑
因此JVM按照使用内存的功能依次划分了不同的存储区域,如下图所示:
编辑
- 程序计数器 (PC Register): 只是一个很小的空间, 保存下一条执行的指令的地址。
- 虚拟机栈(JVM Stack): 与方法调用相关的一些信息,每个方法在执行时,都会先创建一个栈帧,栈帧中包含有:局部变量表、操作数栈、动态链接、返回地址以及其他的一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。
- 本地方法栈(Native Method Stack): 本地方法栈与虚拟机栈的作用类似. 只不过保存的内容是Native方法的局部变量. 在有些版本的 JVM 实现中(例如HotSpot), 本地方法栈和虚拟机栈是一起的
- 堆(Heap): JVM所管理的最大内存区域. 使用 new 创建的对象都是在堆上保存 (例如前面的 new int[]{1, 2,3} ),堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销毁。
- 方法区(Method Area): 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. 方法编译出的的字节码就是保存在这个区域。
在本篇博文中,我们现只简单关心堆和虚拟机栈这两块空间,后序博文中我会把JVM更为详细的介绍给大家。
2.2基本类型变量与引用类型变量
基本数据类型创建的变量,我们称为基本变量,该变量在内存中存放的是相应的值。基本数据类型有八种其中有六种数字类型(四个整数型,两个浮点型)和一个字符型与布尔型。四个整数型又分为:字节型、短整型、整型、长整型,两个浮点型又分为:单精度浮点型和双精度浮点型。
引用数据类型创建的变量,一般称为对象的引用,其内存中存放的是对象所在空间的地址。
public static void main(String[] args) { int a = 10; int b = 20; int[]arry ={1,2,3,4}; }
以上代码中,a、b、arry都是变量,都在main方法中对应相应的栈帧。其中a和b为基本类型的变量因此在内存中保存的就是初始化相应的值。而arry是数组类型也就是引用变量,其在内存中保存的为数组在堆空间中的首地址。结合下图理解:
编辑
通过上图,我们可以理解到基本的类型,在内存中存储的就是初始化的值。而像数组这种引用类型存放的则是一个地址。这个地址从左往右里面放到就是数组里面的各个元素。
2.3认识null
null在Java中表示"空引用",也就是一个不指向对象的引用。比如我创建一个数组并初始化null:
public class Test { public static void main(String[] args) { int[]arry = null; System.out.println(arry[0]); } }
输出:
编辑
Java中的null类似于C语言中的NULL(空指针),都表示指向一个未知的未知。因此不能对这个内存进行任何操作,一旦操作就会抛出NullPointerException(空指针异常)。一般把数组初始化null就是暂时不用这个数组或是特意把这个数组置为空。
3.数组的应用场景
数组的应用场景一般有三种:保存数据、作为函数参数、作为函数返回值。
3.1保存数据
保存数据就比较容易理解,比如我有一组相同的数据。那么这时候可以用数组进行保存:
public class Test { public static void main(String[] args) { int[]arry ={1,2,3,4,5}; for (int i = 0; i < arry.length; i++) { System.out.println(arry[i]); } } }
输出:
编辑
3.2作为函数的参数
(1参数为基本数据类型)
public class Test { public static void fun(int x) { x = 0; System.out.println("x="+x); } public static void main(String[] args) { int num = 10; fun(num); System.out.println("num="+num); } }
输出:
编辑
以上代码,我们可以发现fun方法里面对x进行修改。最终num的值并未发生改变。它在内存中应该是这个样子的:
编辑
(2参数为引用数据类型)
public class Test { public static void fun(int[] a) { a[0] = 10; System.out.println("a[0]="+a[0]); } public static void main(String[] args) { int[] arry={1,2,3,4}; fun(arry); System.out.println("arry[0]="+arry[0]); } }
输出:
编辑
上述代码,我们发现。我在fun方法中对a[0]进行修改,后。main方法里面的arry[0]也发生了改变,原因在于数组是引用类型,按照引用类型来进行传递,是可以进行修改的。结合下图理解:
编辑通过上图,我们可以理解到。当fun方法中接收了arry的数组名意味着a数组也指向了堆上面整个数组的地址。因此通过a数组可以修改arry数组里面的内容。
3.3作为函数的返回值
public class Test { public static int[] fun() { int[] arr ={1,2,3,4}; return arr; } public static void main(String[] args) { int[] arry=fun(); System.out.println(arry[0]); } }
输出:
编辑
上述代码,展示了数组名作为返回值的演示。arry数组通过返回来的arr数组名指向了arr数组。因此可以通过arry数组得到arr数组的元素。在内存中的布局大概是这个样子:
编辑
总结:
- 数组是一段连续的内存空间,因此支持随机访问,即通过下标访问快速访问数组中任意位置的元素。
- 数组下标从0开始,介于[0, N)之间不包含N,N为元素个数,不能越界,否则会报出下标越界异常。
- 数组是引用类型,数组名在方法栈中存放的是引用对象的地址。
4.数组练习
4.1数组转字符串
(方法1)
public class Test { public static void main(String[] args) { int[] arry={1,2,3,4}; System.out.println(Arrays.toString(arry)); } }
输出:
编辑
上述代码中,我用到了一个Arrays.toString(数组名)的方法。这个方法的功能就是把,一个数组里面所有的元素以字符串的形式打印出来。
(方法2)
public class Test { public static void main(String[] args) { int[] arry={1,2,3,4}; System.out.print("["); for (int i = 0; i <arry.length; i++) { System.out.print(arry[i]); if(i<arry.length-1) { System.out.print(","); } } System.out.print("]"); } }
输出:
编辑
上述代码,则是用for循环遍历来实现。实现的就是Arrays.toString的“盗版”。
4.2求数组的元素平均值(保留整数)
public class Test { public static void main(String[] args) { int[]arr={9,9,1}; int sum = 0; for (int i = 0; i < arr.length; i++) { sum+=arr[i]; } int avg = sum/arr.length; System.out.println(avg); }
输出:
编辑
求数组元素的平均值,我们只需要将数组的各个元素加起来然后除以元素总个数就好了。
4.3查找数组中的指定元素(顺序查找)
public class Test { public static void main(String[] args) { int[] arry={1,2,3,4,5}; Scanner sc = new Scanner(System.in); System.out.print("输入一个整数:"); int num = sc.nextInt(); for (int i = 0; i < arry.length; i++) { if (num == arry[i]) { System.out.println("有此数,下标为:"+i); break; } if (i == arry.length-1) { System.out.println("没有此数"); } } } }
输出:
编辑
顺序查找,就是把数组遍历一遍。直到找到出想要的数字然后使用break退出循环即可。
4.4查找数组中的指定元素(二分查找)
public class Test { public static void main(String[] args) { int[] arry={1,2,3,4,5}; Scanner sc = new Scanner(System.in); int key = sc.nextInt(); int left=0; int right=arry.length-1; int mid = 0; int flag=-1; while(left<=right) { mid=(left+right)/2; if (key < arry[mid]) { right = mid-1; }else if(key > arry[mid]) { left = mid+1; }else { System.out.println("找到了"); flag=1; break; } } if (flag==-1) { System.out.println("没有这个数"); } } }
结果:
编辑
二分查找又叫折半查找,算法:从最中间开始查找。按照第一次查找来取范围,如果这个数小于中间数,就在第一个数与中间数之间查找,并再次从最中间开始查找。如果这个数大于中间数,就在中间数与最后一个数之间查找,并再次从最中间开始查找。如果中间数等于这个数第一次就直接能找到,否则按照前两步骤查找直到找到该数。就拿上述程序举例:
编辑多学两招:
public class Test { public static void main(String[] args) { int[] arr = {1,2,3,4,5,6,7}; int ret = Arrays.binarySearch(arr,6); System.out.println(ret); } }
输出:
编辑
我们知道二分查找,必须保证数组是有序的。如果一个数组乱序的是无法完成排序的,因此我们可以使用方法来实现一个乱序的数组变为有序的。
public class Test { public static void main(String[] args) { int[] arr = {9,4,2,1,0,7,8,3,6,5}; Arrays.sort(arr); String ret = Arrays.toString(arr); System.out.println(ret); } }
输出:
编辑
我们可以看到原本乱序的数组在经过Arrays.sort(数组名)这个方法排序变得顺序了,我们再用到一个Arrays.toString(数组名)的方法来实现以字符串的形式输出数组各个元素。
4.5数组排序(冒泡排序)
public class Test { public static int[] Fun(int[] arr) { for (int i = 0; i < arr.length-1; i++) { for (int j = 0; j < arr.length-1-i; j++) { if (arr[j]>arr[j+1]) { int tmp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = tmp; } } } return arr; } public static void main(String[] args) { int[] arr = {9,4,2,1,0,7,8,3,6,5}; Fun(arr); for (int i = 0; i < arr.length; i++) { System.out.print(arr[i]+" "); } } }
输出:
编辑
冒泡排序是将一个乱序的数组,排序为一个有序的数组。它的算法为:遍历这个数组数组元素的个-1次。每一次遍历使得这个数组的最大的数或最小的数到数组的最后一位,当然下一次遍历的时候不会再将上一次遍历得到的最大数进行比较了,因为每次遍历时候已经把本次遍历的最大数或最小数移到最后一位。比如拿上述程序数组来举例:
编辑
4.6数组逆序
public class Test { public static void main(String[] args) { int[] arr= {1,2,3,4,5,6}; int left = 0; int right = arr.length-1; int tmp = 0; while(left<right) { tmp = arr[left]; arr[left] = arr[right]; arr[right] = tmp; left++; right--; } for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } }
输出:
编辑
数组的逆序就比较简单,我们只需要将第一与最后一个进行交换、第二个与倒数第二个进行交换依次类推,直到完成交换。我们可以用到两个下标和一个中间变量来实现这些操作。
编辑
5.二维数组
5.1二维数组的定义
二维数组的定义,分为初始化定义与不初始化定义。
int[][] arry1 = {{1,2,3},{4,5,6}};//初始化定义 int[][] arry2 = new int[2][3];//不初始化定义
编辑
5.2二维数组的遍历
public class Test { public static void main(String[] args) { int[][] arr = {{1,2,3},{4,5,6}}; for (int i = 0; i < 2; i++) { for (int j = 0; j < 3; j++) { System.out.print(arr[i][j]+" "); } } } }
输出:
编辑
当我们知道二维数组有几行几列的时候可以这样去遍历。当我们不知道的时候,可以使用数组名.length来实现行数,列数我们只需要将数组名[行数].length就可以得到。
public class Test { public static void main(String[] args) { int[][] arr = {{1,2,3},{4,5,6}}; for (int i = 0; i < arr.length; i++) { for (int j = 0; j < arr[i].length; j++) { System.out.println(arr[i][j]+" "); } } } }
输出:
编辑
由于二维数组比较简单,因此在本篇博文就讲到这里。一般情况下,我们使用一维数组比较多。二维数组用得较少。
本期博客到这里就结束了,感谢你的观看。
编辑
下期博客预告:类和对象