![](https://i-blog.csdnimg.cn/blog_migrate/adb220e3e0d930d24fba24e54bf9a044.jpeg)
一.数组的基本概念
1.1 什么是数组
数组:可以看成是 相同类型元素的一个集合。在内存中是一段 连续的空间。
好比停车场的车位
![](https://i-blog.csdnimg.cn/blog_migrate/ecafcb1c2bebbdf7070bb24b562772b6.png)
从上图中可以看到
数组中存放的元素其类型相同
数组的空间是连在一起的
每个空间有自己的编号,起始位置的编号为0,即数组的下标。
1.2 数组的创建及初始化
1.2.1 数组的创建
T[] 数组名 = new T[N];
T:表示数组中存放元素的类型
T[]:表示数组的类型
N:表示数组的长度,可以是变量
例如
int[] array=new int[5] ; //创建一个可以容纳5个int元素的数组
char[] array2=new char[10]; //创建一个可以容纳10个char元素的数组
1.2.2 数组的初始化
数组的初始化有两种
1.动态初始化,在创建数组时,直接指定数组中元素的个数
T[] array=new T[N]
2.静态初始化,在创建数组时不直接指定元素个数,而是根据给定的数据内容指定
T[] 数组名称={data1,data2,...}
或者
T[] 数组名称=new T[]{data1,data2,...}
例如
int[] array1 = new int[]{0,1,2,3,4,5,6,7,8,9};
double[] array2 = new double[]{1.0, 2.0, 3.0, 4.0, 5.0};
String[] array3 = new String[]{"hell", "Java", "!!!"};
注意:
静态初始化虽然没有指定数组的长度,编译器在编译时会根据{}中元素个数来确定数组的长度。
静态初始化时, {}中数据类型必须与[]前数据类型一致。
静态初始化可以简写,省去后面的new T[](实际在编译过程中简写会被替换)。
除非是动态初始化,否则[]里不能出现数字
int[5] array={1,2,3,4,5}; //error
int[] array=new int[5]{1,2,3,4,5};//error
数组也可以按照C语言的方式这样创建
int array[5]={1,2,3,4,5};
但是并不推荐,就像定义一个整型变量 int a=10;我们很清晰地知道a的类型是int
Java中也可以很清楚地显示array的数据类型是int[](C语言的定义方式并不这么直观)
静态初始化和动态初始化都可以分为两步,但是简写的静态初始化不可以
int[] array1;
array1=new int[10];//动态初始化
int[] array2;
array2=new int[]{1,2,3,4,5};//静态初始化
int[] array3;
array3={1,2,3,4,5};//error, 当整体初始化时只能在定义时初始化,这样写并不能定义数组
如果没有对数组进行初始化,数组元素有自己的默认值
情况一.数组元素是基本数据类型
类型 | 默认值 |
byte | 0 |
short | 0 |
int | 0 |
long | 0 |
float | 0.0f |
double | 0.0 |
char | /u0000(其实就是'0') |
boolean | false |
情况二.数据元素是引用类型,默认值为null
1.3 数组的使用
1.3.1 数组元素的访问
数组在内存中是一段连续的空间,空间的编号都是从0开始的,依次递增,该编号称为数组的下标,数组可以通过下标访问和修改其任意位置的元素
int[]array = new int[]{10, 20, 30, 40, 50};
System.out.println(array[0]);
System.out.println(array[1]);
System.out.println(array[2]);
System.out.println(array[3]);
System.out.println(array[4]);
// 也可以通过[]对数组中的元素进行修改
array[0] = 100;
System.out.println(array[0]);
注意,下标从0开始,到N-1结束,不能越界,否则会抛出异常
char[] array2=new char[10];
System.out.println(array2[15]);
![](https://i-blog.csdnimg.cn/blog_migrate/e4d6bd104c2c023ef680ed064e41ac03.png)
红色箭头表示数组越界异常
1.3.2 数组的遍历
所谓 "遍历" 是指将数组中的所有元素都访问一遍, 访问是指对数组中的元素进行某种操作
方法一
通过数组名.length来获取数组长度,然后用for循环进行遍历
int[] array = new int[]{10, 20, 30, 40, 50};
for(int i = 0; i < 5; i++){
System.out.println(array[i]);
}
方法二
用for-each遍历数组
int[] array={1,2,3,4,5};
for(int e:array) {
System. out.println(e);
}
for-each又名“增强版for循环”,它表示将数组的每个元素取出来放到e里
方法三
import java.util.Arrays;
int[] array={1,2,3,4,5};
System. out.println(Arrays. toString(array));
//toString表示将array数组里的值按照字符串形式打印,目前只需要了解该方法即可
二. 数组是引用类型
2.1 JVM内存分布
过年的时候妈妈去走亲戚,你因为沉迷手机不想去,经过一番苦苦劝告,你还是没有跟妈妈同行,于是你在家里逍遥了几天---吃饭-打游戏-睡觉-吃饭...然后你家的厨房就变成了这样
![](https://i-blog.csdnimg.cn/blog_migrate/c0a31fc62357b294ce39485982fe6f05.png)
妈妈终于回家了,看到这么脏的厨房,她也凌乱了,先是把你揍了一顿,然后勤劳地收拾了,于是厨房变成了这样
![](https://i-blog.csdnimg.cn/blog_migrate/4c52e19ba5b66cfef03a302a7bf3faee.png)
和你家的厨房一样,如果存储的数据不加区分地随意存储,那么管理起来会非常的麻烦
于是JVM也对所使用的的内存按照功能进行了划分:
![](https://i-blog.csdnimg.cn/blog_migrate/906f559091805195f2ab08a47b30facc.png)
如果想详细了解一下各个区的功能,可以仔细阅读下面的内容,如果不想,现在只需要记住我们今天的关键是虚拟机栈和堆
程序计数器 (PC Register): 只是一个很小的空间, 保存下一条执行的指令的地址
虚拟机栈(JVM Stack): 与方法调用相关的一些信息, 每个方法在执行时,都会先创建一个栈帧,栈帧中包含有: 局部变量表、 操作数栈、 动态链接、 返回地址以及其他的一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。 当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。
本地方法栈(Native Method Stack): 本地方法栈与虚拟机栈的作用类似. 只不过 保存的内容是Native方法的局部变量. 在有些版本的 JVM 实现中(例如HotSpot), 本地方法栈和虚拟机栈是一起的
堆(Heap): JVM所管理的最大内存区域. 使用 new 创建的对象都是在堆上保存 (例如前面的 new int[]{1, 2,3} ), 堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销毁。
方法区(Method Area): 用于 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. 方法编译出的的字节码就是保存在这个区域
2.2 基本类型变量和引用类型变量的区别
基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的是其所对应的值;
而引用数据类型创建的变量,一般称为对象的引用,其空间中存储的是对象所在空间的地址
局部变量--定义在方法内部的变量,都存放在虚拟栈上,它们的生命周期是方法的创建->方法的结束,作用域在{}内
public static void func() {
int a=10;
int[] array={1,2,3,4,5};
}
上面的代码中,因为变量a和array是在方法内部创建的,因此被分配在虚拟机栈上
a是基本数据类型,它的内存空间保存的是自己的值10
array是引用数据类型,它的内存空间可以简单地理解为保存的所指对象的地址
![](https://i-blog.csdnimg.cn/blog_migrate/1a1f1ba34bc1c6b77c8e4315b9b5d6bd.png)
因此,引用变量不直接存储对象本身,而是通过存储对象的地址操纵对象,有点类似于C语言中的指针,但引用要比指针的操作更简单
2.3 详解引用变量
来看下面一组代码,结合图解,帮助更好地理解引用变量
public static void func() {
int[] array1 = new int[3];
array1[0] = 10;
array1[1] = 20;
array1[2] = 30;
int[] array2 = new int[]{1,2,3,4,5};
array2[0] = 100;
array2[1] = 200;
很容易理解上面的代码
![](https://i-blog.csdnimg.cn/blog_migrate/93f5d69b18b7d1c7c39e016493872c66.png)
array1 = array2;
array1[2] = 300;
array1[3] = 400;
array2[4] = 500;
for (int i = 0; i < array2.length; i++) {
System.out.println(array2[i]);
}
}
这组代码先是把array2的值赋给了array1,也就是说,array1也指向了array2指向的对象,即对象2有两个引用
![](https://i-blog.csdnimg.cn/blog_migrate/c3690347c44438c8411e5eb3152b6d60.png)
然后对象2的两个引用变量分别修改了它的值,所以最终的打印结果应该是100,200,300,400,500
问:那么对象1呢?
答:
对象1没有引用变量指向它,因此系统会自动回收空间
2.4 认识空指针null
null在Java中表示空引用,也就是 一个不指向对象的引用
int[] array=null;
array[0]=1;
System.out.println(array.length);
上面的代码会出现空指针异常,因为array没有指向任何对象,所以不存在array[index],array也没有length这个属性
![](https://i-blog.csdnimg.cn/blog_migrate/0992778770a535491c5488022ab97a61.png)
空指针异常
null 的作用类似于 C 语言中的 NULL (空指针), 都是表示一个无效的内存位置. 因此不能对这个内存进行任何读写操作. 一旦尝试读写, 就会抛出 NullPointerException.
但是与C语言不同的是,Java并没有定义null与0号地址有任何关联
三.数组的应用场景
3.1 保存数据
当数据的类型相同的时候,用数组保存
public static void main(String[] args) {
int[] array={1,2,3,4,5};
for(int e:array) {
System. out.println(e+" ");
}
}
思考:如果System.out.println(e+' ')会输出什么?
3.2 作为函数的参数
当函数的参数是基本数据类型的时候,因为是传值拷贝,所以形参的改变不会影响实参
public static void swap(int x,int y) {
int tmp=x;
x=y;
y=tmp;
}
public static void main(String[] args) {
int a=10,b=5;
swap(a,b);
}//a和b的值不会被交换
当函数的参数是数组(引用类型)的时候,相当于把对象的地址给了形参,形参就可以修改对象
public static void swap(int[] arr) {
int tmp=arr[0];
arr[0]=arr[1];
arr[1]=tmp;
}
public static void main(String[] args) {
int[] array={10,5};
swap(array);
System. out.println(Arrays. toString(array));
}
形参arr拷贝了实参array的值,也就是arr和array指向了同一个对象
3.3 作为函数的返回值
如果我们想要接收多个同类型的返回值,可以用数组返回
比如,获取斐波那切数列的前n项
public static int[] fib(int n) {
int[] array=new int[n];
array[0]=0;
if(n==0) {
return array;
}
array[1]=1;
if(n==1) {
return array;
}
for(int i=2;i<n;i++) {
array[i]=array[i-1]+array[i-2];
}
return array;
}
public static void main(String[] args) {
System.out.println(Arrays.toString(fib(5)));
}
四. Arrays提供的“金手指”
4.1 数组转字符串
这个方法其实我们已经悄咪咪使用好多遍了---Arrays.toString(数组名)
它有很多重载方法,可以接收不同数据类型的数组
![](https://i-blog.csdnimg.cn/blog_migrate/8a2558f5a8e20970dd4b835819cd4667.png)
4.2 数组拷贝
这里的数组拷贝并不是指将一个数组对象的地址传给其他引用变量
int[] array1=new int[10];
int[] array2=array1;
// array2指向了array1指向的对象
而是要求创建一个新的数组对象,并将原数组的值复制过去,同样Arrays类提供了这样的方法,并且适用于多种数据类型的数组拷贝
方法一:
![](https://i-blog.csdnimg.cn/blog_migrate/6798e594e32aa6ace0a281549a63f0c0.png)
代码示例
char[] array1={'h','e','l','l','o'};
char[] array2=Arrays.copyOf(array1,array1.length);
System.out.println(Arrays.toString(array2));
方法二:
![](https://i-blog.csdnimg.cn/blog_migrate/bee794eab94f8e6f13153d6de683519a.png)
注:from和to表示下标,是左闭右开区间
代码示例
int[] array1={1,2,3,4,5};
int[] array2=Arrays.copyOfRange(array1,3,5);
System.out.println(Arrays.toString(array2));
方法三:
![](https://i-blog.csdnimg.cn/blog_migrate/d8cf33b44fafdedff83747d890122f9b.png)
src表示被拷贝的数组,srcPos表示被拷贝数组的下标起始位置,dest表示拷贝对象,destPos表示拷贝数组的下标起始位置,length指拷贝长度
与上面两个方法不同的是,方法三属于System类,并且需要自己创建空间
代码示例
拷贝array1下标为2,3的元素
int[] array1={1,2,3,4,5};
int[] array2=new int[10];
Arrays.fill(array2,10);//把array2的元素全部填充为10
System.arraycopy(array1,2,array2,0,2);
System.out.println(Arrays.toString(array2));
//输出结果
[3, 4, 10, 10, 10, 10, 10, 10, 10, 10]
4.3 查找数组中指定元素(二分查找)
![](https://i-blog.csdnimg.cn/blog_migrate/bd18f2a65073d5e655bd4cecc43f116e.png)
如果找到了一个>=0的数,是这个元素的下标
代码示例
int[] array1={1,2,3,4,5};
System.out.println(Arrays.binarySearch(array1,3));
//输出2
4.4 数组的排序
![](https://i-blog.csdnimg.cn/blog_migrate/19b1bb8b90483a4f59d5d278d5df7d1c.png)
代码示例
int[] array1={1,4,5,7};
Arrays.sort(array1);
System.out.println(Arrays.toString(array1));
五. 二维数组
请谨记这句话!!!
二维数组也是数组,只不过它的元素类型是一维数组
Java将把它体现地淋漓尽致
5.1 二维数组的创建
下面举一个创建3*3二维数组的例子
//创建且初始化
方式一:
int[][] arr={{1,2,3},
{4,5,6},
{7,8,9}};
方式二:
int[][] arr=new int[][]{{1,2,3},
{4,5,6},
{7,8,9}};
//只创建不初始化
方式三:
int[][] arr=new int[3][3];
5.2 二维数组的遍历
方法一:下标遍历
//定义一个3*4的数组array,元素按照0~11依次递增
int count=0;
int[][] array=new int[3][4];
for(int i=0;i<array.length;i++) {
for(int j=0;j<array[i].length;j++) {
array[i][j]=count++;
}
}
方法二:for-each
//输出数组array的每个元素
for(int[] tmp:array) {
for(int ret:tmp) {
System. out.print(ret+" ");
}
}
当然也可以方法一和二嵌套使用
for(int[] tmp:array) {
for(int i=0;i<tmp.length;i++) {
System. out.print(tmp[i]+" ");
}
}
方法三:
使用Arrays.deepToString()方法
String ret=Arrays. deepToString(array);
System. out.println(ret);
打印结果如下图
![](https://i-blog.csdnimg.cn/blog_migrate/f731cce317c0f8b45d3da9c496b96c1a.png)
5.3 二维数组新认知
1.二维数组在定义的的时候可以省略列,但不能省略行
int[][] array1=new int[][5];//error
int[][] array2=new int[2][];//可以通过编译
2.二维数组每一行的元素个数可以不相同
int[][] array1=new int[3][];//定义一个有三个元素的二维数组
array1[0]=new int[5];//第一行元素个数为5
array1[1]=new int[]{1,2,3};//第二行元素个数为3,分别赋值为1,2,3
array1[2]=new int[4];//第三行元素个数为4
Arrays. fill(array1[2],8);//把第三行元素填充为8
System. out.println(Arrays. deepToString(array1));//打印array1
用内存图表示如下
![](https://i-blog.csdnimg.cn/blog_migrate/1c9b495442cdc9ee6d458fcd0678ab4f.png)
所以打印结果是
![](https://i-blog.csdnimg.cn/blog_migrate/87c742e3427d7b0c203fad989feb7f80.png)