1、什么是数组

  • 数组是相同类型数据的有序集合。
  • 数组中的每一个数据称作一个数组元素,我们可以通过下标来访问数组元素。
  • 数组是最简单的数据结构。

2、数组的基本特点

  • 数组的长度是确定的
    • 数组一旦被创建,它的大小就是不可改变的。
    • 要对数组进行扩容(或缩容),就必须新建一个空间更大(或更小)的数组。【详见动态数组
  • 同一个数组的元素的数据类型必须相同,不允许出现混合类型。
  • 数组中的元素可以是任何数据类型,包括基本类型和引用类型。
  • 数组属于引用类型数组本身就是对象(可以被new出来),数组元素相当于对象的成员变量。

3、数组的声明和创建(定义)

数组变量必须先声明后使用。

3.1、数组的声明

方式一:

1
数组元素类型[] 数组名;//首选

方式二:

1
数组元素类型 数组名[];//(参照C++)效果相同,不首选

3.2、数组的创建

Java语言使用new操作符来创建数组。

1
数组元素类型[] 数组名 = new 数组元素类型[数组长度];

3.3、数组初始化

(1)静态初始化

  • 在定义数组的同时就为数组元素分配空间并赋值。如:
1
int[] a = {1,2,3,4,5};
1
Man[] menArray = {new Man(1,1),new Man(2,2),new Man(3,3)};

(2)动态初始化

  • 数组定义与为数组分配空间和赋值的操作分开进行。
1
2
3
int[] a = new int[2];
a[0] = 1;
a[1] = 2;

(3)默认初始化

  • 数组是引用类型,它的元素相当于类的实例,因此数组分配空间后,每个元素也会按照实例的类型被隐式初始化。
  • 如:int型、boolean型和引用类型数组默认的初始化值分别为:0、false和null。

附:Java内存区域浅析

Java内存结构是每个java程序员必须掌握理解的,这是Java的核心基础,对我们编写代码特别是并发编程时有很大帮助。由于Java程序是交由JVM执行的,所以我们在谈Java内存区域划分的时候事实上是指JVM内存区域划分。

JVM在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。

JVM 内存主要分为五个部分:Java堆(Heap)、Java虚拟机栈(Java Virtual Machine Stacks)、本地方法栈(Native Method Stack)、方法区(Method Area)和程序计数器(Program Counter Register)。具体如下图所示:

Java运行时数据区

(1)Java堆

  • 存放new的对象和数组
    • 几乎所有的对象实例都在这里分配内存,且每次分配的空间是不定长的。
  • 对于大多数应用而言,Java堆(Heap)是Java虚拟机所管理的内存中最大的一块。
  • Java堆是垃圾收集器管理的主要区域,因此也被称为:GC堆(Garbage Collected Heap)。
  • 被所有线程共享,在虚拟机启动时创建。
  • 堆中会分配一定的内存来保存对象实例。
    • 实际上只是保存对象实例的属性值,属性的类型和对象本身的类型标记等,并不保存对象的方法。
    • 方法是指令,保存在栈(Stack)中。
  • 对象实例在堆中分配好以后,需要在Stack中保存一个4字节的堆内存地址,用来定位该对象实例在堆中的位置,便于找到该对象实例。

(2)Java虚拟机栈

  • 存放局部变量表、对象引用、操作栈、动态链接、方法出口等信息
    • 存放基本变量类型(存放这个基本类型的具体数值)。
    • 引用对象的变量(存放这个引用在堆里面的具体地址)。
  • 线程私有,生命周期与线程相同。
  • 使用连续的内存空间。

(3)本地方法栈

  • 与Java虚拟机栈作用很相似。
    • 它们的区别在于:虚拟机栈为虚拟机执行Java方法(即字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。
  • 虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。
  • 甚至有的虚拟机(如:Sun HotSpot 虚拟机)直接就把本地方法栈和虚拟机栈合二为一。

(4)方法区

  • Java虚拟机规范只是规定了方法区的概念和它的作用,并没有规定如何去实现它。

    • 它是java虚拟机规范去中定义的一种概念上的区域
    • 具有什么功能以及如何去实现它并没有规定。
    • 对于实现者来说,如何来实际方法区是有着很大的自由度。
  • 与Java堆一样,是各个线程共享的内存区域。

  • 对于JDK 1.8之前的版本,HotSpot虚拟机设计团队选择把GC分代收集扩展至方法区。

    • 即用永久代来实现方法区。
    • 这样HotSpot的垃圾收集器可以像管理Java堆一样管理这部分内存,能够省去专门为方法区编写内存管理代码的工作。

    **注:**永久代是HotSpot虚拟机中的一个概念,而对于其他的虚拟机(如:Oracle JRockit、IBM J9等)来说是不存在永久代这一概念的。

  • 在JDK 1.8中,HotSpot虚拟机设计团队为了促进HotSpot与 JRockit的融合,修改了方法区的实。Java8之后方法区的变化:

    • 移除了永久代(PermGen),替换为元空间(Metaspace);
    • 永久代中的 class metadata 转移到了 native memory(本地内存,而不是虚拟机);
    • 永久代中的 interned Strings 和 class static variables 转移到了 Java heap;
    • 永久代参数 (PermSize MaxPermSize) -> 元空间参数(MetaspaceSize MaxMetaspaceSize)

(5)程序计数器

  • 是一块较小的内存空间。

  • 线程私有,生命周期与线程相同。

  • 可以看作是当前线程所执行的字节码的行号指示器

    **注:**在虚拟机概念模型中,字节码解释器工作时就是通过改变计数器的值来选取下一条需要执行的字节码指令。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

4、数组的使用

数组的元素是通过索引访问的。数组索引从 0 开始,索引值从 0 到(数组名.length - 1)。

4.1、普通for循环

如:使用普通for循环遍历数组。

1
2
3
4
5
6
7
public static void main(String[] args) {
int[] arrays = {1,2,3,4,5};
//打印数组元素
for (int i = 0; i < arrays.length; i++) {
System.out.println(arrays[i]);
}
}

输出:

1
2
3
4
5

4.2、for-each循环

如:使用增强for循环遍历数组。

1
2
3
4
5
int[] arrays = {1,2,3,4,5};
//arrays.for可快速生成增强for循环(JDK1.5以后)
for (int array : arrays) {
System.out.println(array);
}

输出:

1
2
3
4
5

4.3、数组作为方法的输入参数

如:自定义打印数组元素函数。

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
int[] arrays = {1,2,3,4,5};
//调用自定义的打印数组元素函数
printArray(arrays);
}
//打印数组元素
public static void printArray(int[] arrays){
for (int i = 0; i < arrays.length; i++) {
System.out.println(arrays[i]);
}
}

输出:

1
2
3
4
5

4.4、数组作为返回值

如:反转数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) {
int[] arrays = {1,2,3,4,5};
//打印反转后的数组
printArray(reverse(arrays));
}

//反转数组
public static int[] reverse(int[] arrays){
int[] result = new int[arrays.length];
for (int i = 0; i < arrays.length; i++) {
result[i] = arrays[arrays.length - 1 - i];
}
return result;
}
//打印数组元素
public static void printArray(int[] arrays){
for (int i = 0; i < arrays.length; i++) {
System.out.println(arrays[i]);
}
}

输出:

5
4
3
2
1

5、多维数组

5.1、什么是多维数组

**多维数组可以看成是数组元素为数组的数组。**如:

  • 三维数组可以看做是数组元素为二维数组的数组。
  • 二维数组可以看做是数组元素为一维数组的数组。

5.2、二维数组的声明、创建和初始化

5.2.1、静态初始化

在定义数组的同时就为数组元素分配空间并赋值。

1
数组元素类型[][] 数组名 = new 数组元素类型[][]{{元素1,...},{元素1,...},{元素1,...}};
1
数组元素类型[][] 数组名 = {{元素1,...},{元素1,...},{元素1,...}};

如:

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

表示:

  • 这个二维数组有3个一维数组,名称是arr[0],arr[1],arr[2]。
  • 各一维数组分别为{1,2,3},{4,6},{6}。
  • 可以通过arr[m][n]获取各元素。

5.2.2、动态初始化

数组定义与为数组分配空间和赋值的操作分开进行。

(1)m行 - n列

1
数组元素类型[][] 数组名 = new 数组元素类型[m][n];

其中:

  • m表示这个二维数组有m个一维数组(行);
  • n表示每一个一维数组的元素个数(列)。

如:

1
int[][] arr = new int[3][2];

表示:

  • 这个二维数组有3个一维数组,名称是arr[0],arr[1],arr[2]。
  • 每个一维数组有2个元素。
  • 可以通过arr[m][n]获取各元素。

(2)m行 - 动态列

1
数组元素类型[][] 数组名 = new 数组元素类型[m][];

其中:

  • m表示这个二维数组有m个一维数组(行);
  • 未直接给出各一维数组的元素个数(列)。

如:

1
2
3
4
int[][] arr = new int[3][];
arr[0] = new int[2];
arr[1] = new int[3];
arr[2] = new int[1];

表示:

  • 这个二维数组有3个一维数组,名称是arr[0],arr[1],arr[2]。
  • 各一维数组分别有2、3、1个元素。
  • 可以通过arr[m][n]获取各元素。

**注:**随着数组维数的增加,数组所占的存储空间会大幅度增加,所以要慎用多维数组。

6、Java 常用类——Arrays类

6.1、关于Arrays类

  • java.util.Arrays类是JDK提供的一个专门用于操作数组的工具类。

  • Arrays类中的方法都是static修饰的静态方法,在使用时可以直接使用类名进行调用,而“不用”使用对象来调用。(注:是“不用”,而不是“不能”)

6.2、Arrays类常用方法

(1)String toString(array):将一个数组array转换成一个字符串。

如:打印数组中的所有元素。

1
2
3
4
5
6
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
//打印数组元素
System.out.println(arr);//对象的hashCode
System.out.println(Arrays.toString(arr));//将数组转换成字符串
}

输出:

[I@4554617c
[1, 2, 3, 4, 5]

(2)void sort(array):对数组array元素进行升序排序。

如:

1
2
3
4
5
6
public static void main(String[] args) {
int[] arr = {1,4,4,5,3,7,2,0};
//数组元素升序排序
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));
}

输出:

[0, 1, 2, 3, 4, 4, 5, 7]

(3)void fill(array,val):把数组array所有元素都赋值为val。

(4)boolean equals(array1,array2):比较两个数组是否相等。

(5)copyof(array,length):把数组array复制成一个长度为length的新数组

(6)int binarySearch(array,val):二分查找元素值val在数组array中下标。

7、冒泡排序

冒泡排序

(1)口诀:逐个比相邻,大的往下沉,每趟沉一个,循环排泡沫。

(2)两层循环:

  • 外层为趟数(共n - 1趟);
  • 内层逐个比相邻(比n - i - 1次),大的往下沉(每趟排好最后一个元素)。
  • 输出结果为升序。
  • 时间复杂度为O(n2n^2)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) {
int[] arr = {1,4,4,5,3,7,2,0};
//冒泡排序
System.out.println(Arrays.toString(bubbleSort(arr)));
}

//冒泡排序(输出结果为升序)
public static int[] bubbleSort(int[] array){
//外层为趟数(共n - 1趟)
for (int i = 0; i < array.length - 1; i++) {
//内层逐个比相邻(比n - i - 1次)
for (int j = 0; j < array.length - i - 1; j++) {
//大的往下沉(每趟排好最后一个元素)
if (array[j] > array[j+1]){
int temp = array[j+1];
array[j+1] = array[j];
array[j] = temp;
}
}
}
return array;
}

输出:

[0, 1, 2, 3, 4, 4, 5, 7]

8、稀疏数组

8.1、定义

稀疏数组:是一种只为(二维)数组中的非零元素分配内存的特殊类型数组,内存中存储了稀疏数组中非零元素的下标和值

注:

  • 稀疏数组是一种数据结构。
  • 稀疏数组是一种特殊的数组,它的首元素为:{二维数组总行数,二维数组总列数,二维数组中非0元素的个数},其它元素为:二维数组中非0元素的**{行号,列号,值}**。
  • 稀疏数组是一个“行数可变、列数为3”的二维数组。
  • 当一个数组中大部分元素为0,或大部分元素为同一值时,可以使用稀疏数组来保存该数组。

如:

二维数组 ,对应的稀疏数组为:

8.2、处理方式

稀疏数组的处理方式是:

  • 记录(二维)数组共有几行几列,有多少个值不相同的元素。
  • 把具有不同值的元素的行、列和值记录在一个小规模的数组中,从而缩小程序的规模(用时间换空间)。

8.3、应用场景

稀疏数组常用于数据压缩。如:五子棋游戏中,保存并退出和继续上一盘功能的实现。

五子棋

实现步骤:

(1)创建一个11*11的二维数组,用于保存五子棋。 其中:0:没有棋子, 1:黑棋 ,2:白棋。

(2)原始二维数组转换为稀疏数组

(3)稀疏数组转换为二维数组

具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
public static void main(String[] args) {
//1、创建一个11*11的二维数组,用于保存五子棋。 其中:0:没有棋子 1:黑棋 2:白棋
int[][] array = new int[11][11];
//设置棋子位置
array[1][2] = 1;
array[2][3] = 2;
//输出原始二维数组
System.out.println("1、原始二维数组为:");
printArray(array);
System.out.println("#########################################");

//2、原始二维数组转换为稀疏数组
//先获取原始二维数组中的有效值(非0值)个数
int effectiveValueNum = get2DimeArrayEffectiveValueNum(array);
System.out.println("有效值个数为:" + effectiveValueNum);
System.out.println("#########################################");
//再将原始二维数组转换为稀疏数组
int[][] sparseArray = trans2DimeArrayToSparseArray(array, effectiveValueNum);
//输出稀疏数组
System.out.println("2、原始二维数组转换成稀疏数组为:");
printArray(sparseArray);
System.out.println("#########################################");

//3、稀疏数组转换为二维数组
System.out.println("3、稀疏数组还原成二维数组为:");
int[][] ints = transSparseArrayTo2DimeArray(sparseArray);
printArray(ints);
System.out.println("#########################################");
}

//输出二维数组
public static int[][] printArray(int[][] arr){
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
System.out.print(arr[i][j] + "\t");
}
System.out.println();
}
return arr;
}

//获取二维数组中的有效值(非0值)个数
public static int get2DimeArrayEffectiveValueNum(int[][] arr){
int num = 0;
//遍历二维数组
for (int[] ints : arr) {
for (int anInt : ints) {
//若二维数组元素为非0值,则有效值个数+1
if (anInt != 0){
num++;
}
}
}
return num;
}

//二维数组转换为稀疏数组
public static int[][] trans2DimeArrayToSparseArray(int[][] arr,int effectiveValueNum){
//(1)新建一个稀疏数组
int[][]sparseArray = new int[effectiveValueNum + 1][3];
//(2)先填稀疏数组的第一行
sparseArray[0] = new int[]{arr.length,arr[0].length,effectiveValueNum};
//(3)将原始二维数组中有效值的行号、列号和值分别填入稀疏数组
//遍历原始二维数组
int index = 1;
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
//若原始二维数组数字有效,则将该有效值的行号、列号和值分别填入稀疏数组
if (arr[i][j] != 0){
sparseArray[index][0] = i;//行号
sparseArray[index][1] = j;//列号
sparseArray[index][2] = arr[i][j];//值
index++;
}
}
}
return sparseArray;
}

//稀疏数组转换为二维数组
public static int[][] transSparseArrayTo2DimeArray(int[][] sparseArray){
//(1)新建一个二维数组
int[][] arr = new int[sparseArray[0][0]][sparseArray[0][1]];
//(2)还原二维数组中的有效值
for (int i = 1; i < sparseArray.length; i++) {
arr[sparseArray[i][0]][sparseArray[i][1]] = sparseArray[i][2];
}
return arr;
}

输出:

1、原始二维数组为:
0 0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0 0
0 0 0 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
#########################################
有效值个数为:2
#########################################
2、原始二维数组转换成稀疏数组为:
11 11 2
1 2 1
2 3 2
#########################################
3、稀疏数组还原成二维数组为:
0 0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0 0
0 0 0 2 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0
#########################################

(本讲完,系列博文持续更新中…… )

阿汤笔迹微信公众平台

关注**“阿汤笔迹”** 微信公众号,获取更多学习笔记。
原文地址:http://www.atangbiji.com/2020/11/22/arrayInDetail
博主最新文章在个人博客 http://www.atangbiji.com/ 发布。