Java:数组

数组

概述

数组是存储同一种数据类型多个元素的集合。也可以看成是一个容器。
数组既可以存储基本数据类型,也可以存储引用数据类型(后面讲解)。

定义格式
  1. 数据类型[] 数组名;
  2. 数据类型 数组名[];
数组的初始化

动态初始化:只指定长度,由系统给出初始化值
格式:

数据类型[] 数组名=new 数据类型[数组长度];
例如:
int[] arr=new int[3];

静态初始化:给出初始化值,由系统决定长度
格式:

数据类型[] 数组名=new 数据类型[]{元素1,元素2,...};
例如:int[] arr=new int[]{1,2,3};
简化格式:
数组类型[] 数组名={元素1,元素2,元素3,};
例如:int[] arr={1,2,3};
数组的内存叙述

在叙述有关数组的内存操作时,我们先来大概了解一下Java中的内存分配
Java中的内存分为五个部分:
1.栈:存放的是局部变量
局部变量:在方法定义中或者方法声明上的变量都是局部变量。
2.堆:存放的是所有new出来的东西
特点:
(1)每一个new出来的东西都会为其分配一个地址值。
(2)与局部变量不同,局部变量需要我们手动初始化赋给它一个初始值。而堆内存中的每一个变量都有一个默认的值
byte,short,int,long ---- 0
float,double ---- 0.0
char ---- ‘\u0000’(代表空格)
boolean ---- false
引用数据类型 ---- null(后面讲解)
(3)使用完毕就变成了垃圾,等待垃圾回收器对其回收
Java在内存上的优化强于C++
当C++内存中出现垃圾时,需要程序员手动调用功能清除,如果不清除,运行速度会变慢,甚至会死机
而Java有其特有的垃圾回收机制,当一个堆内存中的实体没有被引用的话,它就会被视为垃圾,但不会立刻被清除,在不定时的时间内,JVM会启动垃圾回收机制,将其在堆内存中清除
3.方法区(面向对象部分讲解)
4.本地方法区(和系统相关)
5.寄存器(CPU使用)
接下来我们来看有关数组的内存操作(这部分内容只有文字叙述,不明白看视频效果最佳)

数组初始化时在内存中的操作
动态初始化:

int[] arr=new int[3]

1.当执行到这句话时,首先在栈内存中开辟一块空间存储数组变量arr。
2.然后在堆内存中通过new开辟一块空间存储具有3个元素的数组实体,并有一个对应的地址值,数组中每一个元素的初始化值为0。
3.将该数组实体的地址值赋给栈内存中的变量arr,这样arr就指向了堆内存中的这个数组实体。

静态初始化:

int[] arr=new int[3]{1,2,3};

1.当执行到这句话时,首先在栈内存中开辟一块空间存储数组变量arr。
2.然后在堆内存中通过new开辟一块空间存储具有3个元素的数组实体,并有一个对应的地址值,数组中每一个元素的初始化值为0,然后将初始化值替换为我们赋给它的值,即1,2,3。
3.将该数组实体的地址值赋给栈内存中的变量arr,这样arr就指向了堆内存中的这个数组实体。

数组的赋值时内存中的操作

int[] arr=new int[3];
arr[0]=1;
arr[1]=2;
arr[2]=3;
//在完成初始化操作后,找到堆内存中的数组实体,根据角标将新的值按顺序赋值给对应角标的数组元素

多个数组初始化时内存中的操作

int[] arr1=new int[3];
int[] arr2=new int[4];
/*
和单个数组的初始化操作没有区别,不过是将初始化操作运行多次
需要注意的是,每new一次,堆内存中就多一个实体,只要该实体被引用,就不会消失
多个数组初始化时,在堆内存中同样会开辟多个空间来存储多个数组实体,且每个数组实体都有特有的地址值。
*/

两个引用指向同一个数组实体时内存中的操作

int[] arr1=new int[3];
arr1[0]=1;
arr1[1]=2;
arr1[2]=3;
System.out.println(arr1[0]);
System.out.println(arr1[1]);
System.out.println(arr1[2]);
int[] arr2=arr1;
arr2[0]=10;
arr2[1]=20;
arr2[2]=30;
System.out.println(arr1[0]);
System.out.println(arr1[1]);
System.out.println(arr1[2]);
System.out.println(arr2[0]);
System.out.println(arr2[1]);
System.out.println(arr2[2]);
/*
运行结果:
1	
2	
3	
10	
20	
30	
10	
20	
30
分析:
我们首先要明确arr1是什么?
arr1是一个数组类型的变量,他存储的是堆内存中数组实体的地址值
我们将arr1的值赋值给arr2,相当于把arr1指向的数组实体的地址值赋值给了arr2
那么arr2也指向了这个数组实体,对arr2指向的数组实体进行操作时
因为该数组实体只有一个,且有两个引用指向它
好比两个人使用一台电脑,一个人用这台电脑下了一个游戏,另一个人需要使用这台电脑时,这个游戏依旧存在,另一个人也可以打开这个游戏
所以当操作arr2指向的数组实体后,在访问arr1指向的数组实体时,输出的值自然是操作过后的值
*/
//我们可以试着输出arr1的值
System.out.println(arr1)
/*
输出结果:
[I@15db9742
[代表数组
I代表数组中元素数据类型为int
@后面的值就是数组实体的地址值(这个地址值是不确定的)
*/
数组操作的两个常见小问题

1.ArrayIndexOutOfBoundsException:数组索引越界异常
访问的索引超出了数组的长度
例如 int[] arr=new int[3];
在该数组中,数组arr的角标分别为0、1、2,数组长度为3,如果访问arr[3],就会报出这种异常
2.NullPointerException:空指针异常
数组已经不在指向堆内存了,但我们还用数组名去访问元素。
例如:
int[] arr=new int[3];
arr=null;
System.out.println(arr[1]);
在这种情况下,我们手动将arr的值变为null,这个变量已经不再指向堆内存中的数组,再去访问就会出现这种异常。

数组的操作

1.遍历

 //数组遍历,依次输出数组中的元素
int[] arr={1,2,3,4,5};
for (int i = 0; i < arr.length; i++) //arr.length代表数组的长度
{
	System.out.print(arr[i]+" ");
}
//数组的角标从0到arr.length-1,我们使用循环遍历这个范围就行了
//增强型for循环遍历数组(foreach循环)
int[] arr={1,5,2,7,8};
for(int each:arr)
	System.out.print(each+" ");
//foreach循环,即每次取出arr数组中的一个值给each
/*
优点:代码简单,使用方便
缺点:无法指定特定元素,失去了索引
使用场景:
当我们只想使用数组中元素的值时,可以使用foreach循环
但如果要访问或者数组的下标时,使用常规的for循环
*/

2.获取最值

//获取数组中的最大值
int[] arr={1,2,3,4,5};
int max=arr[0];
for (int i = 1; i < arr.length; i++) 
{
    if(max<arr[i])
    	max=arr[i];
}
/*
首先定义一个变量max来存储一个数组中的最大值,初始化值可以是数组中的任一元素(推荐arr[0])
然后我将max与数组中的所有元素进行比较(数组遍历),如果存在元素大于max,将这个元素赋值给max
我们从角标1开始遍历是因为max存储的是arr[0],没有必要再和arr[0]比较
*/

3.数组反转

int[] arr={1,2,3,4,5};
//数组反转:使这个数组变成{5,4,3,2,1}
for (int i = 0; i < arr.length/2; i++)
{
	int temp=0;
	temp=arr[i];
	arr[i]=arr[arr.length-1-i];
	arr[arr.length-1-i]=temp;
}
/*
数组反转,即将arr[0]与arr[4]的值换位,arr[1]和arr[3]的值换位
在遍历操作中,当i=0时,arr.length-i-1=4
			 当i=1时,arr.length-i-1=3
则在遍历操作时,只需要将arr[i]和arr[arr.length-i-1]换位即可
而这样的操作只需要执行的i=1时即可,即arr.length/2
arr.length为奇数还是偶数都不影响首尾互换
*/

4.查表法

//数组是一个容器,查表法就是将数组中元素作为表中的内容
//通过用户输入内容,提取相对应的数组中的内容
//根据键盘录入索引,查找对应星期
String[] arr={"星期一","星期二","星期三","星期四","星期五","星期六","星期日"}
int d=3;
System.out.println(arr[d-1]);

5.查找元素

//查找指定元素第一次在数组中出现的索引
int[] arr={1,4,2,9,6,8};
int key=4;
int index=-1;
for (int i = 0; i < arr.length; i++)
{
	if(key==arr[i])
	{
		index=i;
		break;
	}
}
/*
定义一个变量key作为我们要查找的元素,定义index来存储该元素的角标
遍历数组,如果key的值与arr[i]相等,让把i的值赋值给index,并跳出循环
index初始化值为-1,因为如果我们在数组中找不到这个元素,用-1代表这个元素的角标不存在
*/
//折半查找
/*
我们介绍一种更加高效的查找方式,折半查找
举个例子,如果在1~100之间指定一个数字,你来猜,猜出数字后,会提示你你猜的这个数大了还是小了。
最快的方式就是猜中间值,这样可以最快的缩小范围,进而最快的找到这个数。
而折半查找用的就是这种思想
先比较中间元素与目标值(key)的大小关系,然后一步步缩小范围,最后确定目标值在数组中的位置
*/
int[] arr={1,2,3,4,5,6};
int key=4;
int index=-1;
//初始化条件
int max=arr.length-1;
int min=0;
int mid=(min+max)/2;
//因为我们不知道max何时小于min,即不知道具体的循环次数,所以使用while循环
while(max>=min)
{
    if(key==arr[mid])
    {
        index = mid;
        break;
    }
    else if(key>arr[mid])
         min=mid+1;
    else
        max=mid-1;
    mid=(min+max)/2;
}
 System.out.println(index);
 /*
 首先我们要判断key是否与arr[mid]相等,如果相等就找到了目标元素的角标,直接跳出循环
 如果不相等,判断key与arr[mid]的大小关系
 如果,key>arr[mid],说明猜的值小了,就要把下限提高,我们key已经和arr[mid]比较过了,所以下限变为mid+1
 如果,key<arr[mid],说明猜的值大了,就要把上限降低,所以下限变成max-1
 当范围发生改变后,要改变mid的值,不然就是死循环,然后再次循环比较,知道找到目标值的角标
 当min>max时,代表数组中不存在目标值,循环结束
 */
 //折半查找还有第二种表现方式
 while(key!=arr[mid])
 {
 	if(key>arr[mid])
 		min=mid+1;
    else
        max=mid-1;
    mid=(min+max)/2;
    if(min<max)
    	break;
 }
 /*
 我们把key!=arr[mid]作为控制循环体的条件
 如果min小于max,说明目标值在数组中不存在,直接跳出循环。
 */
 //注意:折半查找只适用于有顺序的数组,不适合乱序的数组!!!

6.排序

//数组的排序有选择排序、冒泡排序、希尔排序等
//此处不细讲排序的算法,且排序都是以从小到大为例
//选择排序
int[] arr={6,2,8,0,1,9};
for (int i = 0; i < arr.length-1; i++)	
{
    for(int j=i+1;j<arr.length-1;j++)
    {
        if(arr[i]>arr[j])
        {
            int temp=0;
            temp=arr[i];
            arr[i]=arr[j];
            arr[j]=temp;
        }
    }
}
/*
将第一个元素与后面的元素依次比较,如果这个元素大于后面的元素,换位
第一次循环结束,最小值出现在了0角标位
我们再将第二个元素与后面元素依次比较,如果这个元素大于后面的元素,换位
第二次循环结束,第二小的值出现在了1角标位
......
就这样依次比较,最终将数组中的元素从小到大排序
此处i<arr.length-1是因为当遍历最后一个元素时,不需要进行比较了,该元素就是最大值。
哪怕此处循环条件为i<arr.length,i=arr.length-1时,j=arr.length,内循环也无法执行循环体
*/
//冒泡排序
int[] arr={6,2,8,0,1,9};
for (int i = 0; i < arr.length-1; i++)
{
	for(int j=0;j<arr.length-i-1;j++)
	{
		if(arr[j]>arr[j+1])
		{
			int temp=0;
			temp=arr[j];
			arr[j]=arr[j+1];
			arr[j+1]=temp;
		}
	}
}
/*
将两个相邻元素进行比较,如果前一元素比后一元素大,换位
第一次循环
将第一个元素与第二个元素进行比较,第一个元素大于第二个元素,进行换位
将第二个元素与第三个元素进行比较,第二个元素大于第三个元素,进行换位
将第三个元素与第四个元素进行比较,第三个元素大于第四个元素,进行换位
......
当第一次循环结束后,最大值出现在了最后
第二次循环依旧从第一个元素开始,但是只遍历到arr[arr.lenfgth-2],
即最后一个元素不遍历,次数第二大的元素在倒数第二个角标位上
第三次循环时,最后两个元素不遍历
......
就这样不断循环,每次将最大值放在遍历到的最后一个角标位上,每次少遍历一个角标位
将数组从小到大进行排序
j<arr.length-i-1的目的就是为了控制让每次循环少遍历一个元素。
*/
//在实际开发中我们会使用Java给我提供的排序方法,但这种排序思想还是要掌握
/*
使用Java提供的排序方法的步骤
1.导包
import java.util.*;
2.调用方法
Arrays.sort(arr);
*/

7.复制数组

/*
System.arraycopy(src,srcPos,dest,destPos,length)方法
src:源数组
srcPos:从源数组赋值元素的起始位置
dest:目标数组
destPos:复制到目标位置的其实位置
length:复制的长度
*/
int[] arr1={4,1,2,6,9,5};
int[] arr2=new int[3];
System.arraycopy(arr1,1,arr2,0,3);
//最后arr2中的元素为{1,2,6}

二维数组

定义格式
//1
数据类型[][] 变量名 = new 数据类型[m][n];
//m表示这个二维数组有多少个一维数组 必须写上
//n表示每一个一维数组的元素个数 可选
int[][] arr=new int[3][2];
/*
定义了一个二维数组
这个二维数组中有3个一维数组,分别是arr[0],arr[1],arr[2]
每个一维数组中有两个元素
arr[0][0]:表示第一个数组的第一个元素
arr[0][1]:表述第一个数组的第二个元素
*/
//以下定义格式也可以定义二维数组
数据类型 数组名[][] = new 数据类型[m][n];
数据类型[] 数组名[] = new 数据类型[m][n];//这种格式面试题中会出现

int[] x,y[];
/*
定义了一个一维数组x
定义了一个二维数组y
*/
/*
int[][] arr=new int[3][2];
重点:内存中的操作
1.首先将.class文件加载进方法区
2.在栈内存中加载main方法,在main方法中开辟一块空间存储二维数组变量arr
3.在堆内存中通过new开辟一块空间存储具有3个一维数组的二维数组实体,并有一个对应的地址值。
二维数组中元素的默认初始化值为null(数组为引用数据类型),并把地址值赋值给arr,arr指向这个二维数组
4.在堆内存中开辟3块空间分别用来存放二维数组中一维数组元素,每个一维数组元素都分配了其对应的地址值
将地址值赋值给二维数组中的元素,这时null被覆盖,二维数组中每个元素存储的是地址值,指向对应的数组
*/
int[][] arr=int[3][2];
arr[0]=new int[4];
//问:是否会报错?
/*
不会报错
表面上,二维数组arr中存储的每个一维数组的长度是2,如果存储长度为4的一维数组,会出现角标越界
我们要弄清楚arr[0]是什么,arr[0]的值是一个地址值,该地址指向一个长度为2的一维数组
现在在堆内存中new一个长度为4的一维数组,并让arr[0]指向这个数组,那么arr[0]的值就是长度为4的一维数组的地址值
arr[0]只不过指向了另一个一维数组,而原来指向的数组,因为没有被引用,会被当做垃圾处理,在堆内存中消失
*/
//2
数据类型[][] 变量名 = new 数据类型[m][];
//m表示这个二维数组有多少个一维数组
int[][] arr=new int[3][];
//定义了一个二维数组,存放了3个一维数组,但没有直接给出一维数组的元素个数
/*
内存中的操作:
1.首先将.class文件加载进方法区
2.在栈内存中加载main方法,在main方法中开辟一块空间存储二维数组变量arr
3.在堆内存中通过new开辟一块空间存储具有3个一维数组的二维数组实体,并有一个对应的地址值。
二维数组中元素的默认初始化值为null(数组为引用数据类型),并把地址值赋值给arr,arr指向这个二维数组
*/
arr[0]=new int[4];
/*
1.此时在堆内存中通过new开辟一块空间存放长度为4的一维数组,并为其分配对应的地址值,元素的默认初始化值为0
2.将这个一维数组的地址值赋值给arr[0],arr[0]指向这个一维数组,即二维数组的第一个元素为长度为4的一维数组
*/
//可以手动让二维数组中的元素指向一维数组
//3
数据类型[][] 变量名 = new 数据类型[][]{{元素…},{元素…},{元素…}...};
简化格式:
数据类型[][] 变量名 = {{元素…},{元素…},{元素…}};
int[][] arr = {{1,2,3},{4,5,6}};
/*
内存中的操作:
1.首先将.class文件加载进方法区
2.在栈内存中加载main方法,在main方法中开辟一块空间存储二维数组变量arr
3.在堆内存中通过new开辟一块空间存储具有3个一维数组的二维数组实体,并有一个对应的地址值。
二维数组中元素的默认初始化值为null(数组为引用数据类型)。
4.随后在堆内存中分别开辟两个空间存放二维数组中的一维数组元素,并分配对应的地址值一维数组中的元素的
初始化值都为0,第一个一维数组的初始化值被{1,2,3}覆盖,第二个一维数组的初始化值被{4,5,6}覆盖。
5.把两个一维数组的地址值分别赋值给arr[0]和arr[1],arr[0]和arr[1]分别指向这两个一维数组。
并把二维数组的地址值赋值给arr,arr指向这个二维数组。
*/
二维数组的操作

1.遍历

/*
二维数组遍历
外循环控制的是二维数组的长度,其实就是一维数组的个数。
内循环控制的是一维数组的长度。
*/
//打印1个二维数组
int[][] arr={{1,2,3},{3,4,5},{7,8,9}};
for (int i = 0; i < arr.length; i++) 
{
   	for (int j = 0; j < arr[i].length; j++) 
   	{
       	System.out.print(arr[i][j]+" ");
   	}
  	System.out.println();
}
/*
输出结果:
1 2 3
4 5 6
7 8 9
*/

2.求和

/*
需求:公司年销售额求和
	某公司按照季度和月份统计的数据如下:单位(万元)
	第一季度:22,66,44
	第二季度:77,33,88
	第三季度:25,45,65
	第四季度:11,66,99
*/
int[][] arr={{22,66,44},{77,33,88},{25,45,65},{11,66,99}};
int sum=0;
for (int i = 0; i < arr.length; i++)
{
    for (int j = 0; j < arr[i].length; j++)
    {
        sum=sum+arr[i][j];
    }
}
System.out.println(sum);
/*
输出结果:
641
*/
发布了26 篇原创文章 · 获赞 1 · 访问量 374

猜你喜欢

转载自blog.csdn.net/weixin_45919908/article/details/103374754