第三章 Java 数组
一、数组的基本概念
通常声明一个基本数据类型的变量来表示一个数据,但是在实际应用中,经常需要处理具有相同性质的一批数据。如果需要声明10个数据类型相同的变量,通过基本数组类型来声明就显得极不方便。因此,Java中引入数组的概念,数组即用一个变量表示一组相同性质的数据。
数组元素在数组(二维数组)中的位置,如图:
1.1、数组
数组是数据类型相同且有序的一组数组元素的集合。
String类型的数组array:
String[] array = new String[]{
"Java","Python","C/C++"};
1.2、数组元素
构成数组的数据就称为数组元素。
数组元素:
array[0]="Java"; array[1]="Python"; array[2]="C/C++";
1.3、数组的数据类型
数组的数据类型即数组元素的数据类型,一个数组中,所有数组元素的数据类型应该是一致的。
类:Array
public class Array {
public static void main(String[] args) {
// 数组的数据类型
//基本数据类型
byte[] bytes = new byte[] {
0,1,2,3,4};
short[] shorts = new short[] {
5,6,7,8,9};
int[] ints = new int[] {
10,11,12,13,14,15};
long[] longs = new long[] {
16,17,18,19,20};
float[] floats = new float[] {
21.2f,22.2f,23.2f};
double[] doubles = new double[] {
24.1,25.1,26.1};
boolean[] booleans = new boolean[] {
};
char[] chars = new char[] {
'A','B','C'};
//引用数据类型
String[] strings = new String[] {
"Java","Python","C/C++"};
}
}
1.4、数组元素的下标
数组下标就是数组元素在数组当中的位置,且都是从0开始,后续元素的位置依次加1。
1、数组:
String[] array = new String[]{
"Java","Python","C/C++"};
2、数组元素的下标:
下标为0的元素:array[0]="Java";
下标为1的元素:array[1]="Python";
下标为2的元素:array[2]="C/C++";
1.5、数组的大小
数组的大小就是数组当中数组元素的个数,也称位数组的长度。
1、数组:
String[] array = new String[]{
"Java","Python","C/C++"};
2、数组的大小为:3;
3、注意:
数组的大小(长度) = 数组的最大下标 + 1;因为数组下标是从0开始的。
二、数组的使用
Java中,数组必须经过声明、内存分配、初始化后才能使用。
2.1、数组的声明
语法格式:
<1> 数据类型 数组名 [] ;
或者
<2> 数组类型[] 数组名 ;
注意:
建议使用<2>种的数组声明格式,因为可以把“数据类型[ ]”看作一种特殊的数据类型。
例如:
// 数组的声明
int nums[];
//或者
int[] nums;
2.2、为数组分配内存空间
通过new
运算符可以为数组元素分配内存空间。
语法格式:
数组名 = new 数组类型 [ 数组长度 ];
例如:
// 为数组分配内存空间
nums = new int[10];
注意:
数组的声明和内存分配可以一起来定义:
语法格式:
数据类型 数组名[] = new 数组类型[数组长度];
例如:
// 数组的声明和内存分配一起来定义
int nums[] = new int[10];
2.3、为数组元素初始化
语法格式:
数组名[元素下标] = 元素;
(这样为数组元素初始化,属于,数组元素的动态分配,可以更为灵活的更改数组中的指定元素值。)
例如:
// 为数组元素初始化
nums[0] = 110;
nums[1] = 111;
nums[2] = 112;
nums[3] = 113;
nums[4] = 114;
nums[5] = 115;
nums[6] = 116;
nums[7] = 117;
nums[8] = 118;
nums[9] = 119;
注意:
数组声明、为数组元素分配内存、数组元素初始化,这三步可以合并在一起写。
语法格式:
数据类型[] 数组名 = {数组元素};
或
数据类型[] 数组名 = new 数据类型[]{数组元素};
例如:
// 数组的声明、内存分配和初始化一起来定义
int nums[] = new int[]{
110,111,112,113,114,115,116,117,118,119};
// 注意:此时无需指定数组的大小(长度)。
2.3.1、数组没有初始化时的默认值
如果没为数组元素初始化,则数组元素则会使用默认值。 byte
、 short
、 int
、 long
类型的数组元素的默认值是 0
, float、double
类型数组的元素默认值是 0.0
, boolean
类型数组元素的默认值是 false
, char
类型的数组元素默认值是 '\u0000'
, 引用类型
数组元素的默认值是 null
。
如下表:
序号 | 数据类型 | 默认初始值 |
---|---|---|
1 | Byte |
0 |
2 | Short |
0 |
3 | Int |
0 |
4 | Long |
0 |
5 | Float |
0.0 |
6 | Double |
0.0 |
7 | Boolean |
false |
8 | char |
\u0000 |
9 | 引用数组类型 |
null |
例如:
类Array:
public class Array {
public static void main(String[] args) {
// 数组的声明和分配内存空间,但是不给数组元素初始化
byte[] bytes = new byte[2];
short[] shorts = new short[2];
int[] ints = new int[2];
long[] longs = new long[2];
float[] floats = new float[2];
double[] doubles = new double[2];
boolean[] booleans = new boolean[2];
char[] chars = new char[2];
String[] strings = new String[2];
//输出8种基本数据类型和引用数据类型的默认值
System.out.println("bytes[0]="+bytes[0]);
System.out.println("shorts[0]="+shorts[0]);
System.out.println("ints[0]="+ints[0]);
System.out.println("longs[0]="+longs[0]);
System.out.println("floats[0]="+floats[0]);
System.out.println("doubles[0]="+doubles[0]);
System.out.println("chars[0]="+chars[0]);
System.out.println("strings[0]="+strings[0]);
}
}
2.3.2、数组初始化的举例
例1:数组的声明、为数组分配内存空间、为数组元素初始化分步进行完成。
public class Array {
public static void main(String[] args) {
// 声明数组
int[] nums;
String names[];
// 为数组元素分配内存空间
nums = new int[2];
names = new String[3];
// 初始化数组元素
nums[0] = 520;
nums[1] = 1314;
names[0] = "Java";
names[1] = "Python";
names[2] = "C/C++";
}
}
例2:数组的声明和分配内存空间一起完成,最后为数组元素初始化。
public class Array {
public static void main(String[] args) {
// 数组的声明和内存分配
int[] nums = new int[2];
String names[] = new String[3];
// 初始化数组元素
nums[0] = 520;
nums[1] = 1314;
names[0] = "Java";
names[1] = "Python";
names[2] = "C/C++";
}
}
例3:数组的声明、为数组分配内存空间和为数组元素初始化一起完成。
public class Array {
public static void main(String[] args) {
// 数组的声明、内存分配和初始化一起完成
int[] nums = new int[]{
520,1314};
String names[] = new String[]{
"Java","Python","C/C++"};
}
}
2.4、访问数组元素
语法格式:
数组名[数组下标]
例如:
//访问数组元素
String names[] = new String[]{
"Java","Python","C/C++"};
//访问数组元素
System.out.println("names[0]"+names[0]);
System.out.println("names[1]"+names[1]);
System.out.println("names[2]"+names[2]);
注意:
数组和for循环的组合使用。
1>:
//声明、分配内存、初始化数组names
String names[] = new String[]{
"Java","Python","C/C++"};
//访问数组元素
for(int i=0;i<names.length;i++){
System.out.println("names["+i+"]"+names[i]);
}
2>:
//声明、分配内存、初始化数组names
String names[] = new String[]{
"Java","Python","C/C++"};
//访问数组元素
for(String str:names){
System.out.println(str);
}
解释:在2>for循环中,只需要一个变量和数组名就不能输出该数组的所有元素。
语法格式:
for( 声明循环变量 : 数组名称 ){
System.out.println(循环变量名);
}
注意:
1、声明循环变量时,声明的变量类型要和数组的类型形同;
2、声明循环变量 = 循环变量类型 + 循环变量名称。
三、Java的内存管理
3.1、初步了解Java的内存管理
使用一些编程语言编写的程序会直接向操作系统(OS)请求内存,但是Java语言为保证其平台无关性,并不允许程序直接向操作系统请求内存,而是由Java虚拟机来完成这一操作,开发者只需要关心Java虚拟机是如何管理内存空间的,而不用关心操作系统(OS)是如何管理内存的。
Java虚拟机在执行Java程序的过程中会把所管理的内存划分为若干个不同的数据区域,大概有以下几种:
1>、程序计数器:
也有称作为PC寄存器,在JVM中用来指示要执行哪条指令,程序计数器是每个线程所私有的。
2>、栈:
也被称作Java栈或虚拟机栈,Java栈是Java方法执行的内存模型。存放的是一个个的栈帧,每个栈帧对应一个被调用的方法。虚拟机栈也是每个线程所私有的。
本地方法栈,与栈类似,HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。
3>、堆:
Java中的堆是用来存储对象本身以及数组本身的。堆是被所有线程共享的,在JVM中只有一个堆。
4>、方法区:
存储类信息、静态变量、常量以及编译器编译后的代码等。方法区同堆一样,也是被线程共享的区域。
在Java程序运行过程中,栈内存和堆内存是最需要关注的内存区域。
Java的内存模型如图:
3.2、Java内存中的数组
数组在JVM中的内存分配情况。
举例说明如下:
public class Array {
public static void main(String[] args) {
//声明数组
int[] array;
//为数组分配内存
array = new int[2];
//数组元素初始化
array[0] = 520;
array[1] = 521;
}
}
Java将数组名称存储在栈中,数组元素分配在堆中。简单的理解上面这段代码执行时JVM中的内存分配过程。
第一步,Array
类的main
方法开始执行,创建该方法对应的栈帧,并将创建的栈帧在栈内存中压栈(存入栈顶):
(图:3.2_01)
第二步,执行int[] array
;,在main
方法对应栈帧的局部变量表中,为数组名称array
分配一块内存:
(图:3.2_02)
第三步,执行array = new int[2];
时,首先,JVM会在堆中分配能够连续存储两个int
类型数据的内存空间,之后,赋值操作会将堆内存中已经分配好的两个连续内存空间的首地址存储到栈内存main
方法对应栈帧的局部变量表中:
(图:3.2_03)
第四步,执行array[0] = 520; array[1] = 521;
,在堆内存分配好的两个连续内存空间中存入array[0]
对应的整型值520
和array[1]
对应的整型值521
:
(图:3.2_04)
第五步,main
方法结束,栈内存中main
方法对应的栈帧出栈,堆内存被回收(本例中main
方法结束意味着整个Java程序结束了,JVM将自己管理的内存交还给操作系统(OS
)):
(图:3.2_05)
3.3、基本数据类型和应用数据类型
Java将数据类型分为两大类,一类是基本数据类型,一类是引用数据类型。这两大类数据类型最核心的区别是:
1:基本数据类型的变量中存储的是真实的数据;
2:引用数据类型的变量中存储的是内存地址编号。
举例来解释一下基本数据类型和引用数据类型所谓区别:
public class Array {
public static void main(String[] args) {
// 基本数据类型的变量赋值
int num1, num2;
num1 = 520;
num2 = num1;
num2 = 521;
System.out.println("num1 = " + num1);
// 引用数据类型的变量赋值
int[] nums1, nums2;
nums1 = new int[1];
nums1[0] = 522;
nums2 = nums1;
nums2[0] = 523;
System.out.println("nums1[0] = " + nums1[0]);
}
}
代码解释图示:(直接从变量定义开始解释,main
方法的压栈参考(图:3.2_01))
1、基本数据类型:
//1、声明两个整型变量
int num1, num2;
(图:3.3_01)
//2、给变量num1赋初值
num1 = 520;
(图:3.3_02)
//3、将num1的值赋值给num2
num2 = num1;
(图:3.3_03)
//给num2变量赋值
num2 = 521;
(图:3.3_04)
2、引用数据类型:
//1、声明数组nums1、nums2
int[] nums1, nums2;
(图:3.3_05)
//2、为数组nums1分配内存空间
nums1 = new int[1];
(图:3.3_06)
//2、为数组元素初始化
nums1[0] = 522;
(图:3.3_07)
//3、将数组nums1赋值给数组nums2
nums2 = nums1;
(图:3.3_09)
//4、给数组nums2赋值
nums2[0] = 523;
(图:3.3_10)
3.4、二维数组
如行列式、矩阵、二维表格等,为了描述和处理其值的某个数据,需要两个下标,即行下标和列下标。有些情况下可能需要3个或多个下标,如描述三维空间中各点的位置就需要3个下标。
对于Java中的二维数组或者多维数组而言,并没有什么玄妙,以二维数组为例,只需牢记:二维数组只是一个特殊的一维数组,特殊在这个一维数组的每一个元素的值都是一个指向另一个一维数组的引用。
图示即可说明二维数组在JVM中的内存分配情况:
3.5、三维数组及多维数组
所谓的多维数组就是在一维数组里面套数组,比如在一位数组里面套一个一维数组就变成了二维数组,在二维数组里面套一个一维数组就变成了三维数组,在三维数组中套一个一维数组,就变成了四维数组,等等。
但大多数时候,我们也只能用到一维数组和二维数组。
三维数组举例:
public class Array {
public static void main(String[] args) {
//声明三维数组
int[][][] nums ;
//为三维数组分配内存空间
nums = new int[2][2][2];
//为三维数组的元素初始化
num[0][0][0]=1;
num[0][1][0]=2;
num[1][0][0]=3;
num[1][1][0]=4;
num[0][0][1]=5;
num[0][1][1]=6;
num[1][0][1]=7;
num[1][1][1]=8;
//输出三维数组的元素:
for(int i=0;i<num.length;i++) {
for(int j=0;j<num[i].length;j++) {
for(int k=0;k<num[i][j].length;k++) {
System.out.println(num[i][j][k]);
}
}
}
}
}
四、操作数组的Arrays工具类
JDK中提供的java.util.Arrays类,是一个常用于操作数组的工具类,该类中提供了一系列的静态方法,可以完成如数组拷贝、数组排序等功能,其中部分是泛型方法。
java.util.Arrays类中的一些常用API如下:
序号 | 方法 | 返回值 | 方法说明 |
---|---|---|---|
1 | asList(T... a) |
List<T> |
返回包含数组元素的列表集合 |
2 | binarySearch(int[] a, int key) |
int | 使用二叉搜索算法搜索指定数组的指定元素 |
3 | binarySearch(Object[] a, Object key) |
int | 使用二叉搜索算法搜索指定数组的指定元素 |
4 | copyOf(T[] original, int newLength) |
T[] |
将原数组中的元素拷贝到一个指定长度的新数组中,用空值截断或填充 |
5 | copyOfRange(T[] original, int from, int to) |
T[] |
将指定数组的指定范围拷贝到新数组中 |
6 | fill(int[] a, int val) |
void | 将指定的值分配给指定数组的每个元素 |
7 | fill(int[] a, int fromIndex, int toIndex, int val) |
void | 将指定的值分配给指定数组中指定范围的每个元素 |
8 | sort(int[] a) |
void | 按照数字顺序排列指定的数组 |
9 | sort(T[] a, Comparator<? super T> c) |
void | 根据指定的比较器实现的顺序对指定的对象数组进行排序 |
10 | equals(Object[] a, Object[] a2) |
boolean | 如果两个指定的对象数组彼此“相等”,则返回true |
11 | deepEquals(Object[] a1, Object[] a2) |
boolean | 如果两个指定的数组彼此“深度相等”,则返回true |
12 | toString(Object[] a) |
String | 返回指定数组的“内容”的字符串表示形式 |
13 | deepToString(Object[] a) |
String | 返回指定数组的“深度内容”的字符串表示形式 |