4、数组
重点:
1、数组的内存分析(引用数据类型)
2、数组的排序
难点:
1、数组的内存分析(引用数据类型)
1、会定义数组、实例化数组、元素访问
2、数组是一个引用数据类型
3、数组的排序
文章目录
4.1、数组的简介
1、数组,其实是一个容器。是一个用来存储相兼容数据类型的数据的一个容器。
2、数组的使用场景:当需要使用到多个相同数据类型的变量的时候。
3、数组的特点:数组的长度是不可变的,一个数组实例化完成以后,长度将不能发生改变。
长度:其实就是数组的容量,代表这个数组中能够存储多少个数据。 .length
元素:数组中存储的每一个数据,称为这个数组中的元素。
4.2、数组的定义与实例化
4.2.1、数组的定义
在定义数组的时候,用 []
来表示一个数组。
int[] array; // 声明一个数组,这个数组中存储的数据都是int类型的数据。
boolean[] array; // 声明一个数组,这个数组中存储的数据都是boolean类型的数据。
int array[]; // 这是C语言中定义数组的方法,这种方式,Java也是支持的。
如何看一个变量的类型?
盖住变量名,剩下的部分,就是这个变量的类型。
4.2.2、数组的实例化
实例化:开辟空间,并在这个空间中填充一些初始值。
new:
1、数组的实例化,需要用new完成。
2、以后,再在任意位置,只要是看到了new,都表示在堆内存上开辟空间。
// 默认值:
// 整型的默认值是 0
// 浮点型的默认值是 0.0
// 布尔型的默认值是 false
// 字符型的默认值是 '\0' '\u0000'
// 实例化一个数组,定义数组的长度为5。此时这个数组中的元素,填充的是指定数据类型的默认值
int[] array = new int[5];
// 实例化一个数组,并填充数组中的元素为 1, 2, 3, 4, 5 。但是这种方式的实例化没有显示的数组长度。
// 此时,这个数组的长度,由初始值的数量决定。
int[] array = new int[] { 1, 2, 3, 4, 5 };
// 在上方的基础上,省略了 new int[]
int[] array = { 1, 2, 3, 4, 5 };
4.3、数组的内存分析
4.3.1、数组的实例化
数组的实例化,需要使用关键字new。这个操作,其实是在堆上开辟的空间。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tOEwvXcb-1570542300538)(./images/array_memory.png)]
1、数组的实例化,是在堆上开辟的连续的指定长度的空间。
2、然后将首元素的地址给栈中的变量进行赋值。
4.3.2、引用数据类型
引用数据类型,是Java中的两大类型之一。
如果一个变量中存储的内容,不是一个字面的值,而是一个指向了其他位置的地址。这个变量就是一个引用数据类型的变量。同时这个变量,称为指向的空间的引用。
数组,是一个引用数据类型。
与引用类型对应的,另外一种类型,叫做值类型。
变量中存储的内容,就是字面的值。
int a = 10;
而值类型,在Java中,就是基本数据类型。
4.4、数组的元素访问
4.4.1、什么是元素访问
就是使用数组中的某一块空间,往这块空间中存储数据,也可以从这块空间中读取数据。
4.4.2、如何进行元素访问
访问数组中的元素,通过下标访问。
每一个元素,在数组中存储的时候,都是有一个索引的。这个索引,称为元素的下标。
注意事项:数组中的元素下标是从0开始的。因此数组的下标范围是 [0, 长度-1]
如果,在访问数组元素的时候,使用了一个越界的下标,则会出现异常 ArrayIndexOutOfBoundsException
int[] array = new int[5];
array[0] = 10; // 将数组中的第0个元素的值修改为10
System.out.println(array[3]); // 输出数组中的第3个元素
array[5] = 100; // ArrayIndexOutOfBoundsException
4.4.3、数组的下标分析
为什么数组的元素下标要从0开始?【了解】
数组是在堆上开辟的连续的空间,数组的变量中存储的其实是首元素的地址。在访问后面的元素的时候,其实是用首元素的地址,加上向后偏移的空间单位量来进行。因此首元素不需要进行任何的偏移,偏移量是0; 后面的一个元素,偏移量是1;以此类推。
因此数组的元素下标,可以认为是在进行元素访问的时候,需要使用首元素地址进行的偏移量。
4.4.4、遍历数组
遍历数组,其实就是因此获取到数组中的每一个元素。
1、使用下标遍历法
思路:数组的下标范围是[0, 长度-1],因此,我们可以使用循环,依次获取到每一个下标,再使用这个下标访问数组中的元素。
int[] arr = { 1, 2, 3, 4, 5 };
for (int i = 0; i < arr.length; i++) {
// 通过下标,获取数组中的元素
int ele = arr[i];
System.out.println(ele);
}
2、使用增强for循环
int[] array = { 1, 2, 3, 4, 5 };
// 增强for循环
// 原理:依次将数组中的每一个元素,给ele赋值
for (int ele : array) {
System.out.println(ele);
}
3、两种方式的不同点
1、下标遍历方式,在遍历的过程中,是可以获取到元素下标的。增强for循环中,没有下标的概念。
2、增强for循环中,不允许对数组的内容做修改。
3、增强for循环的遍历效率要高于下标遍历。
4.5、数组的排序
对数组中的元素,按照一定的大小关系,进行重新的排列。
4.5.1、常见的排序算法
冒泡排序、选择排序、快速排序、插入排序、堆排序、归并排序、希尔排序 …
4.5.2、冒泡排序
排序原理:依次比较两个相邻的元素,当满足交换的时候,交换这两个元素。
每一趟的比较,都从第0位开始,依次比较两个相邻的元素。
int[] array = { 10, 8, 6, 29, 11, 9, 3, 0, 18, 29 };
// 外层循环,控制比较发生了多少趟,数组长度-1趟
for (int i = 0; i < array.length - 1; i++) {
// 内层循环,依次比较两个相邻的元素 i + j <= array.length - 2
for (int j = 0; j < array.length - i - 1; j++) {
// 比较两个相邻的元素
if (array[j] > array[j + 1]) {
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
4.5.3、选择排序
排序原理:固定一个下标,使用这个下标的元素,依次和后面的每一个元素进行比较。
public static void sort(int[] array) {
for (int i = 0; i < array.length - 1; i++) {
for (int j = i + 1; j < array.length; j++) {
if (array[i] > array[j]) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
}
public static void sort(int[] array) {
// 每一趟的比较,最多只需要发生一次交换即可。
// 需要知道谁和谁交换
for (int i = 0; i < array.length - 1; i++) {
// 记录需要和第i位进行交换的下标
// 对于升序排序,认为这一位对应的元素最小
int swapIndex = i;
for (int j = i + 1; j < array.length; j++) {
// 内层循环,需要找出swapIndex位是谁
// 对于升序排序,需要找出最小值的下标
if (array[swapIndex] > array[j]) {
swapIndex = j;
}
}
if (swapIndex != i) {
int tmp = array[swapIndex];
array[swapIndex] = array[i];
array[i] = tmp;
}
}
}
4.6、数组的查询
查询,其实就是从一个数组中查询指定元素所在的下标。如果需要查询的数据,在数组中不存在,一般情况话,会将查询结果设置为-1。
4.6.1、顺序查询
从第0个元素开始,依次使用每一个元素和要查询的元素进行比较。如果比较成立,则当前下标就是要查询的下标。其实顺序查询,就是遍历数组。
/**
* 从数组array中,使用顺序查询,查询ele出现的下标
* @param array 需要查询的数组
* @param ele 需要查询的元素
* @returns 查询的结果,如果不存在,返回-1
*/
public static int search(int[] array, int ele) {
// 在这里,使用下标遍历更合适。因为我们就需要一个下标作为结果。
for (int i = 0; i < array.length; i++) {
if (array[i] == ele) {
return i;
}
}
// 如果循环结束了,方法还没结束,说明上方的比较,没有一个元素是匹配的
// 也就是说,要查询的元素不存在
return -1;
}
4.6.2、二分查询
在一个指定的范围内,查询数据。每次查询,取这个范围的一半。这样做可以将查询结果分为以下几种情况:
- 如果中间的值,就是我们想要查询到,说明查询到了。
- 如果中间的值,不是我们要查询的,可以将查询的范围缩减一半。
如果使用二分查询,查询数组中的元素下标,此时对数组是有要求的:
- 数组需要是排序的状态。
/**
* 从数组array中,使用二分查询,查询ele出现的下标
* @param array 需要查询的数组
* @param ele 需要查询的元素
* @returns 查询的结果,如果不存在,返回-1
*/
public static int binarySearch(int[] array, int ele) {
// 1、确定一个初始的查询范围
int min = 0, max = array.length - 1;
while (min <= max) {
// 2、计算这个范围的中间下标
int mid = (min + max) / 2;
// 3、用这个中间下标的元素和我们要查询的元素进行比较
if (array[mid] == ele) {
// 说明找到了
return mid;
}
else if (array[mid] > ele) {
// 中间位的元素比要查询的元素大,说明元素在左侧
// 修改新的区间
max = mid - 1;
}
else {
// 中间位的元素比要查询的元素小,说明元素在右侧
// 修改新的区间
min = mid + 1;
}
}
// 能执行到这里,说明区间已经缩小到不存在了,还没有找到指定的元素
// 也就是说,要查询的元素在数组中不存在
return -1;
}
4.7、数组拷贝
数组拷贝,分为浅拷贝和深拷贝。
4.7.1、浅拷贝
直接将一个数组的地址,赋值给另外的一个数组。这样拷贝出来的数组,拥有相同的地址,对任意的一个数组进行修改,都会影响到另外一个数组。
int[] array = new int[5];
// 这里,就是一个浅拷贝,target和array有着相同的内存地址,指向了相同的空间
int[] target = array;
array[0] = 100;
System.out.println(target[0]); // 100
4.7.2、深拷贝
实例化一个新的数组,长度与原数组相同。让新的数组指向内存中的一块新的空间。然后将原数组中的每一个元素拷贝到目标数组中。这样目标数组和原数组有着相同的元素,但是他们是两个数组。对一个数组进行修改,不会影响到另外一个数组。
int[] array = new int[5];
int[] target = new int[array.length];
// 这里,将原数组中的每一个元素依次拷贝到目标数组中,是一个深拷贝。
for (int i = 0; i < array.length; i++) {
target[i] = array[i];
}
array[0] = 100;
System.out.println(target[0]); // 0
4.8、数组操作中常见的异常
4.8.1、ArrayIndexOutOfBoundsException
数组下标越界异常,常常发生于使用一个不存在的下标访问数组中的元素。
int[] array = new int[5];
array[-1] = 10;
array[5] = 10;
4.8.2、NullPointerException
空指针异常,常常发生于使用null来进行空间访问。
1、null是什么?
null表示空、没有。只能用于引用数据类型,表示这个引用变量中没有存储任何的地址,没有指向任何的一块空间。所有的引用数据类型的默认值都是null。
2、使用null进行空间访问,会出现空指针异常。
4.9、Arrays工具类
4.9.1、什么是工具类?
工具类,是一个类,这个类中包装了若干个用来方便完成指定功能的方法。这些方法称为工具方法。
工具方法,为了方便调用,一般都是静态方法。
4.9.2、Arrays
Arrays是一个用来操作数组的工具类
Arrays类在java.util包里面,因此,使用之前,需要先导包。
import java.util.Arrays;
// 或者
import java.util.*;
// 1、排序
Arrays.sort(array);
// 2、将一个数组中的元素拼接成一个字符串
System.out.println(Arrays.toString(array));
// 3、元素拷贝
// 3.1、将数组中从0位开始,拷贝指定数量的元素到一个新的数组中,并返回这个新的数组
// 在拷贝的时候,如果需要拷贝的元素数量比数组长度长,后面的部分填充默认值
int[] newArray = Arrays.copyOf(array, 5);
System.out.println(Arrays.toString(newArray));
// 3.2、将数组中指定范围 [from, to) 的元素拷贝到一个新的数组中,并返回
// 注意:from不能越界,但是to可以; from <= to
newArray = Arrays.copyOfRange(array, 5, 12);
System.out.println(Arrays.toString(newArray));
// 4、判断两个数组是否相同
System.out.println(Arrays.equals(array, newArray));
// 5、使用指定的值,填充这个数组
Arrays.fill(target, 20);
System.out.println(Arrays.toString(target));
// 6、使用二分查找法,查询某一个元素的下标
int index = Arrays.binarySearch(array, 7);
System.out.println(index);
4.9.3、System.arraycopy
是System类中的一个方法,不开源。
// 将一个数组的指定位开始的元素,拷贝到目标数组的指定位
// src: 原数组,需要从这里拷贝元素
// srcPos: 从原数组的这一位开始拷贝元素
// dest: 目标数组,将元素拷贝到这个数组中
// destPos: 将元素从这个位置开始覆盖
// length: 拷贝的元素的数量
// System.arraycopy(src, srcPos, dest, destPos, length);
示例:
int[] src = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int[] dst = { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
System.arraycopy(src, 3, dst, 5, 4);
System.out.println(Arrays.toString(dst));
// 输出结果:[0, 10, 20, 30, 40, 3, 4, 5, 6, 90, 100]
4.10、二维数组
数组中的元素,是一个小的数组。数组嵌套数组。
// 声明了一个二维数组,数组array中存储的元素,是一个数组。
// 数组嵌套数组。
int[][] array;
4.10.1、二维数组的实例化
和一维数组是一样的。
// 1. 实例化的时候,在第一个中括号里面写上一个长度。
// 代表了二维数组的长度
// 数组array的长度是5,里面有5个元素,每一个元素都是一个int[]。
// 数组实例化完成后,里面的元素填充的是默认值,而二维数组中填充的是 null 。
int[][] array = new int[5][];
System.out.println(array[0]); // null
// 2. 实例化的时候,在每一个中括号里面定义一个长度
// 第一个中括号中的长度,是数组array的长度。
// 第二个中括号中的数字,数组array中存储的元素,默认是 new int[3]。
int[][] array = new int[5][3];
4.10.2、二维数组的使用
// 二维数组的元素访问,和一维数组相同,都是通过下标的方式访问的。
int[][] array = new int[5][3];
// array[0]是int[]类型的。
// 访问数组array中的第0个元素(是一个小数组)的第0个元素。
array[0][0] = 10;
4.11、关键字 …
在方法的参数定义中,可以使用 数据类型...
来表示一个数组。
public static void show(int... params) {
// 这个方法的中的参数params,其实就是一个 int[]
}
…作为参数,有什么特点:
1、这种方式定义的参数,在调用方法的时候,可以直接数组中的每一个元素直接写在实参列表中。
// 当数组作为参数,最传统的传参方式。
show(new int[]{1, 2, 3, 4, 5});
// 如果参数是int...,可以直接将数组中的所有元素写到参数列表中。
show(1, 2, 3, 4, 5);
2、这种方式定义的参数,必须放到参数列表的最后位。
标的方式访问的。
int[][] array = new int[5][3];
// array[0]是int[]类型的。
// 访问数组array中的第0个元素(是一个小数组)的第0个元素。
array[0][0] = 10;
#### 4.11、关键字 ...
在方法的参数定义中,可以使用 `数据类型...` 来表示一个数组。
```java
public static void show(int... params) {
// 这个方法的中的参数params,其实就是一个 int[]
}
…作为参数,有什么特点:
1、这种方式定义的参数,在调用方法的时候,可以直接数组中的每一个元素直接写在实参列表中。
// 当数组作为参数,最传统的传参方式。
show(new int[]{1, 2, 3, 4, 5});
// 如果参数是int...,可以直接将数组中的所有元素写到参数列表中。
show(1, 2, 3, 4, 5);
2、这种方式定义的参数,必须放到参数列表的最后位。