第四章:数组............................................................................................................................... 2
1.数组概述和特点
1.1数组的概念
数组概念:数组就是一种能够存放相同数据类型的有序集合。(通俗来讲数组其实就是一个容器)。
1.2数组的创建
1.2.1动态创建数组
语法格式:元素类型[] 数组名 = new 元素类型[数组长度];
元素类型 数组名[] = new 元素类型[数组长度];
注意:数组的声明建议大家使用第一种方式,避免数组名混淆。
【示例】
public static void main(String[] args) {
// 创建3个空间的int类型数组
int[] arr1 = new int[3];
// 创建5个空间的String类型数组
String[] arr2 = new String[5];
}
1.2.2静态创建数组
语法格式:元素类型[] 数组名 = new 元素类型[]{元素1, 元素2, 元素3,…};
注意:使用静态的方式来创建数组,数组的长度由元素个数来确定。
【示例】
public static void main(String[] args) {
// 创建指定内容的int类型数组
int[] arr1 = new int[]{1, 2, 3, 4, 5};
// 创建指定内容的String类型数组
String[] arr2 = new String[]{"11", "22", "33", "44"};
}
除了用new关键字来产生数组以外,还可以直接在定义数组的同时就为数组元素分配空间并赋值。
语法格式:元素类型[] 数组名 = {元素1, 元素2, 元素3,…};
【示例】
public static void main(String[] args) {
// 创建指定内容的int类型数组
int[] arr1 = {1, 2, 3, 4, 5};
// 创建指定内容的String类型数组
String[] arr2 = {"11", "22", "33", "44"};
}
注意事项:
- 数组类型可以是任何数据类型,包括基本类型和引用类型,例如String[]和float[]。
- 数组中存放元素的类型,必须是创建数组时指定的类型,不允许出现混合类型。
- 创建一个数组时,必须指定数组长度,创建成功数组的大小就不可以改变了。
1.3数组的基本操作
数组中的元素,我们可以通过下标(索引)来访问,索引从0开始。
数组索引的取值范围为:[0,数组长度-1],如果超出索引范围来操作数组元素,会抛出ArrayIndexOutOfBoundsException异常。
- 数组的赋值操作
【示例】
public static void main(String[] args) {
// 初始化5个空间的int类型数组
int[] arr = new int[5];
// 添加元素
arr[0] = 11; // 给第一个元素赋值
arr[1] = 22; // 给第二个元素赋值
arr[2] = 22; // 给第三个元素赋值
// 修改第二个元素的值
arr[1] = 222;
}
- 数组的取值操作
【示例】
public static void main(String[] args) {
// 创建指定内容的int类型数组
int[] arr = {1, 2, 3, 4, 5};
// 获取元素
int num1 = arr[0]; // 获取第一个元素
int num2 = arr[1]; // 获取第二个元素
int num3 = arr[2]; // 获取第三个元素
}
- 获取数组的长度
【示例】
public static void main(String[] args) {
// 创建指定内容的int类型数组
int[] arr = {1, 2, 3, 4, 5};
// 通过length属性,来获取数组的长度
System.out.println(arr.length); // 输出:5
}
注意事项:
- 通过length属性获取到的数组长度和开辟的内存空间的长度一致。
- 某些情况下,实际添加元素的个数(不算默认值元素),可能会小于了数组的长度。
- 通过for循环遍历数组
【示例】
public static void main(String[] args) {
// 创建指定内容的int类型数组
int[] arr = {1, 2, 3, 4, 5};
// 通过length属性,获取数组元素的个数
int length = arr.length;
// 通过for循环,遍历数组所有元素
for(int i = 0; i < length; i++) {
// 通过下标获取数组中的元素
System.out.println("第"+(i+1)+"个元素值:" + arr[i]);
}
}
因为数组的内存空间是连续的,我们通过数组的首地址+索引就能快速的找到数组对应的元素值,从而得出数组的优点:查找快。
索引操作数组原理:数组首地址 + 存放数据的字节数*索引。
【随堂练习】
1、 获取10个学生的成绩,然后保存在数组中,最后计算学生的总分和平均分。
1.4数组的默认初始化
数组是引用类型,只要数组一经分配空间,那么数组中的每个元素都会被隐式的设置一个默认值。
以下是针对不同数据类型的默认值:
1、整数类型(byte、short、int、long)的基本类型变量的默认值为0。
2、浮点型(float、double)的基本类型变量的默认值为0.0。
3、字符型(char)的基本类型变量的默认为 “/u0000”。
4、布尔性的基本类型变量的默认值为 false。
5、引用类型的变量是默认值为 null(null就是空对象)。
1.5 JVM中的堆和栈(重点)
JVM是基于堆栈的虚拟机,堆栈是一种数据结构,是用来存储数据的。对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。
【栈内存stack】
栈内存:用来存放局部变量。
当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。
栈内存特点:
- 栈内存存储特性为:“先进后出,后进先出”。
- 栈是一个连续的内存空间,由系统自动分配,速度快!
- 虚拟机会为每个线程创建一个栈,用于存放该线程执行方法的信息。
【堆内存heap】
堆内存:用来存储创建好的对象和数组(数组也是对象)
在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。
引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。
数组和对象在没有引用变量指向它的时候,数组或对象本身占用的堆内存就变成垃圾,不能再被使用,然后由Java虚拟机的自动垃圾回收器来管理释放该内存。
堆内存特点:
- 虚拟机中只有一个堆,被所有的线程共享。
- 堆是一个不连续的内存空间,分配灵活,但速度慢!
【思考一】
public static void main(String[] args) {
int[] arr1 = {1, 2, 3, 4, 5};
int[] arr2 = arr1;
arr2[2] = 33;
System.out.println(arr1[2]); // 输出结果为???
}
【思考二】
public static void main(String[] args) {
int[] arr1 = {1, 2, 3};
int[] arr2 = arr1;
arr1 = null;
System.out.println(arr1[2]); // 输出结果为???
System.out.println(arr2[2]); // 输出结果为???
}
2.数组常见
操作
2.1获取数组的最值
需求:获取数组{5, 12, 90, 18, 77, 76, 45, 28, 59, 72}的最大值,也就是该数组的元素90。
实现:先假设第一个元素就是最大值,并赋值给maxValue变量保存,然后依次取出数组中的元素和maxValue作比较,如果取出元素大于maxValue,那么把该元素设置为最大值。
【示例】
思考
/**
* 获取数组的最大值
* @param arr 需要查询的数组
* @return 返回查询到的最大值
*/
public static int maxElement(int[] arr) {
// 假设第一个元素的值就是最大值
int max = arr[0];
// 遍历数组元素,依次和假设的最大值作比较
for(int i = 1; i < arr.length; i++) {
// 取出每个元素的值和value作比较
if(arr[i] > max) {
// 推翻假设,更新最大值
max = arr[i];
}
}
return max;
}
:获取数组中最大值的索引,我们该怎么去做呢?
2.2通过值获取索引
需求:获取元素59在数组{5, 12, 90, 18, 77, 76, 45, 28, 59, 72}中的索引。
实现:通过for循环来遍历数组,把需要查询的值和数组中的元素一一做比较,如果需要查询的值和某个元素相等,则返回索引值并结束方法。如果循环完毕都没查找到,则返回-1。
【示例】
/**
* 根据value值,获取它在数组中的索引位置
* @param arr 需要查询的数组
* @param value 需要判断的值
* @return 找到,则返回对应的索引;未找到,则返回-1
*/
public static int search(int[] arr, int value) {
// 遍历数组,把数组中的元素依次和value作比较
for(int i = 0; i < arr.length; i++) {
// 取出元素值和value作比较
if(arr[i] == value) {
return i; // 找到相同的元素,返回索引位置
}
}
// 未找到,则返回-1
return -1;
}
2.3数组反序输出
需求:将数组反序输出,原数组{5, 12, 90, 18, 77, 76, 45, 28, 59, 72},反序输出后{72,59,28,45,76,77,18, 90,12,5}。
实现(一):引入一个外部数组变量,用于保存反序后的数组,然后把原数组中的元素倒序保存于新创建的数组中。
【示例一】
/**
* 将数组反序输出
* @param arr 需要反序的数组
* @return 返回反序后的数组
*/
public static int[] reverseOrderArray(int[] arr) {
// 定义一个反序后的数组
int[] desArr = new int[arr.length];
// 把原数组元素倒序遍历
for(int i = 0; i < arr.length; i++) {
// 把arr的第i个元素赋值给desArr的最后第i个元素中
desArr[arr.length - 1 - i] = arr[i];
}
// 返回倒序后的数组
return desArr;
}
实现(二):直接对数组中的元素进行收尾交换。
【示例二】
/**
* 将数组反序输出
* @param arr 需要反序的数组
*/
public static void reverseOrderArray(int[] arr) {
// 把原数组元素倒序遍历
for(int i = 0; i < arr.length/2; i++) {
// 把数组中的元素收尾交换
int temp = arr[i];
arr[i] = arr[arr.length - i - 1];
arr[arr.length - i - 1] = temp;
}
}
2.4数组元素删除
需求:删除数组{5, 12, 90, 18, 77, 76, 45, 28, 59, 72}索引为2的元素,删除后:{5, 12, 18, 77, 76, 45, 28, 59, 72,0}。
实现:把数组索引2以后的元素向前移动一位,最后把数组的最后一个元素的值设置为默认值(整数类型的默认值为0)。
【示例】
/**
* 根据索引删除数组中的元素
* @param arr 需要删除元素的数组
* @param index 需要删除数组元素的索引
*/
public static void deleteElement(int[] arr, int index) {
// 第一步:判断索引是否合法
if(index < 0 || index >= arr.length) {
System.out.println("索引越界");
return; // 索引不合法,直接结束方法
}
// 第二步:从index个元素开始,将后一个元素向前移动一位
for(int i = index; i < arr.length - 1; i++) {
// 将后一个元素向前移动一位
arr[i] = arr[i + 1];
}
// 第三步:将最后一个元素设置为默认值
arr[arr.length - 1] = 0;
}
数组的缺点:因为数组是连续的内存空间,当数组进行删除和插入操作的时候,效率相对低下!
2.5数组元素插入
需求:在数组{5, 12, 90, 18, 77, 76, 45, 28, 59, 72}索引为2的位置插入元素222,插入后:{5, 12, 222, 90, 18, 77, 76, 45, 28, 59, 72}。
实现:首先准备给数组扩容,然后把插入索引位置之后的元素往后移动一位,最后在插入索引的位置插入元素。
【示例】
/**
* 在数组指定位置插入元素
* @param arr 需要插入元素的数组
* @param index 插入元素的位置
* @param value 需要插入的元素值
* @return 返回插入元素成功的数组
*/
public static int[] insertElement(int[] arr, int index, int value) {
// 第一步:判断索引是否合法
if(index < 0 || index >= arr.length) {
System.out.println("索引越界");
// 抛出一个索引越界异常(异常第六章学习)。
throw new ArrayIndexOutOfBoundsException("索引越界:"+index);
}
// 第二步:给数组扩容
// 创建一个更大的数组
int[] newArr = new int[arr.length + 1];
// 把原数组中的数据,复制给新创建的数组
for (int i = 0; i < arr.length; i++) {
newArr[i] = arr[i];// 拷贝操作
}
// 让arr指向堆里面的newArr数组
arr = newArr;
// 第三步:插入索引位置之后的元素往后移动一位
for (int i = arr.length - 2; i >= 2; i--) {
arr[i + 1] = arr[i];
}
// 第四步:给index索引位置赋值
arr[index] = value;
// 返回插入元素成功的数组
return arr;
}
2.6数组冒泡排序
工作原理:重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。
这个算法的名字由来是因为越大的元素会经由交换慢慢“浮”到数列的顶端,故名。
【升序示例】
/**
* 对数组进行升序排列(冒泡排序)
* @param arr 需要排序的数组
*/
public static void sort(int[] arr) {
// 外侧循环控制趟数,一共要执行arr.length-1趟
for (int i = 0; i < arr.length - 1; i++) {
// 假设本次循环就已经排序成功
boolean flag = true;
// 内侧循环控制每趟比较的次数
for (int j = 0; j < arr.length - i - 1; j++) {
// 如果上一个大于下一个,则交换
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
// 如果发生交换,则证明没有排序成功
flag = false; // 推翻假设
}
}
// 根据flag值,判断是否需要继续排序
if (flag) break;
}
2.7二分法查找
二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。
【示例】
public static void main(String[] args) {
// 使用二分法查找的数组,必须是有序数组
int[] arr = {0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20};
// 调用二分法查找,找到查找元素的索引
int index = binarySearch(arr, 18);
System.out.println(index); // 输出:9
}
/**
* 使用二分法查找元素的索引
* @param arr 需要查找的数组
* @param value 需要查找的元素值
* @return 如果返回值为正数,则证明找到;如果返回-1,则证明没找到。
*/
public static int y(int[] arr, int value) {
// 最小索引
int min = 0;
// 最大索引
int max = arr.length - 1;
// 开始循环查找
while(true) {
// 获取中间索引
int mid = (min + max)/2;
// 如果arr[mid]>value,则证明在上半区
if(arr[mid] > value) {
// 更新max的值
max = mid - 1;
}
// 如果arr[mid]<value,则证明在下半区
else if(arr[mid] < value) {
// 更新min的值
min = mid + 1;
}
// 如果arr[mid]==value,则证明找到
else {
return mid;
}
// 如果min>max,则证明没找到该值
if(min > max) {
return -1;
}
}
}
3.Arrays工具类
Arrays用于操作数组工具类,里面定义了常见操作数组的静态方法。
注意:要使用Arrays工具类,必须导入Arrays工具类。
import java.util.Arrays;
3.1 toString方法
public static String toString(Type[] arr),返回指定数组内容的字符串表示形式。
【示例】
int[] arr = {3, 5, 1, 7, 6, 2, 4};
// 把数组转化为字符串输出
System.out.println(Arrays.toString(arr)); // 输出:[1, 2, 3, 4, 5, 6]
3.2 equals判断
public static boolean equals(Type[] a1, Type[] a2), 判断两个数组中的内容是否相同。
【示例】
int[] arr1 = {3, 5, 1, 7, 6, 2, 4};
int[] arr2 = {3, 5, 1, 7, 6, 2};
// 判断两个数组的内容是否相同
boolean flag = Arrays.equals(arr1, arr2);
System.out.println(flag); // 输出:false
3.3 sort排序
public static void sort(Type[] arr) ,对数组中的内容进行升序排序。
【示例】
int[] arr = {3, 5, 1, 7, 6, 2, 4};
// 对数组中的内容进行升序排序
Arrays.sort(arr);
System.out.println(Arrays.toString(arr)); // 输出:[1, 2, 3, 4, 5, 6, 7]
3.4 二分法查找
public static int binarySearch(Type[] arr, Type key),查找key在数组中的索引位置。如果找到,则返回索引位置;如果没找到,则返回一个负数。
注意:在调用此调用之前,必须先对数组进行排序。
【示例】
// 排序好的数组
int[] arr = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
// 查找6在数组中的索引位置
int index = Arrays.binarySearch(arr, 6);
System.out.println(index); // 输出:6
// 查找18在数组中的索引位置
index = Arrays.binarySearch(arr, 18);
System.out.println(index); // 输出:-11,证明没找到
3.5 fill填充数组
public static void fill(Type[] a, Type val),给数组填充指定内容。
【示例】
int[] arr = new int[5];
Arrays.fill(arr, 89);
System.out.println(Arrays.toString(arr)); // 输出:[89, 89, 89, 89, 89]
3.6 数组拷贝
public static Type[] copyOf(Type[] original, Type newLength),从数组的第一个元素开始拷贝,拷贝指定长度的数组,拷贝完成返回一个新的数组。
【示例】
int[] arr = {1, 2, 3, 4, 5};
int[] newArr = Arrays.copyOf(arr, 3);
System.out.println(Arrays.toString(newArr)); // 输出:[1, 2, 3]
public static Type[] copyOfRange(Type[] original, int from, int to),从指定范围拷贝数组,拷贝完成返回一个新的数组。
【示例】
int[] arr = {1, 2, 3, 4, 5, 6, 7, 8};
int[] newArr = Arrays.copyOfRange(arr, 2, 5);
System.out.println(Arrays.toString(newArr)); // 输出:[3, 4, 5]
4.数组知识点补充
4.1 for-each循环遍历
for each是java SE 5.0增加了一种功能很强的循环结构,可以用来一次处理数组中的每个元素(其他类型的元素集合亦可)而不必为指定下标值而分心。
这种增强的for each循环的语法格式为:
for (type element : array) {
System.out.println(element); // 输出数组中的每一个元素
}
【示例】
public static void main(String[] args) {
int[] arr = {1, 2, 3, 4, 5, 6};
// 通过增强for循环遍历数组
for(int element : arr) {
// 依次输出数组的元素
System.out.println(element);
}
}
优点:语法简洁,比普通for循环的效率高。
缺点:相比较普通for循环,增强for循环无法获得数组下标。
4.2 main方法的形参
参数String[ ] args的作用就是可以在main方法运行前将参数传入main方法中。
- 从控制台,输入编译执行命令时传参数
例如下面代码:
public static void main(String[] args) {
System.out.println("args数组长度:" + args.length);
// 遍历args中的每一个元素
for(String arg : args) {
System.out.println(arg);
}
}
但是此时args[]并没有赋值,我们需要从控制台命令行进行赋值,就像这样:
- 在Eclipse使用String[ ] args参数
在代码编辑窗口,点击鼠标右键
出现以下窗口,切换到(x)=Arguments窗口,输入参数,最后点击Run运行即可。
4.3方法的可变参数
可变参数:适用于参数个数不确定,但类型确定的情况,java把可变参数当做数组处理。
我们使用...表示可变长参数,...位于变量类型和变量名之间,前后有无空格都可以。
【示例】
public static void main(String[] args) {
System.out.println(add(1, 2)); // 输出:3
System.out.println(add(1, 2, 3)); // 输出:6
}
// 可变参数函数
public static int add(int a, int b, int ... arr) {
int sum = a + b;
for(int i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
可变参数的特点:
- 一个方法中可变参数最多只能有一个,并且只能出现在参数列表的最后面。
- 调用可变参数的方法时可以给出任意个参数,在方法体中以数组的形式访问可变参数。
5.二维数组
5.1二维数组的定义
二维数组本质上是以数组作为数组元素的数组,即“数组的数组”。(通俗来讲二维数组的每一个元素又是一个一维数组)
5.2二维数组的创建
- 创建格式一,创建等长的二维数组
语法语法:数据类型[][] 数组名 = new 数据类型[m][n];
m: 表示这个二维数组的长度。
n: 表示二维数组中每个元素的长度。
注意以下格式也可以表示二维数组:
数据类型 数组名[][] = new 数据类型[m][n]; 数据类型[] 数组名[] = new 数据类型[m][n]; |
- 创建格式二,创建不定长二维数组
语法格式:数据类型[][] 数组名 = new 数据类型[m][];
m: 表示这个二维数组的长度。
二维数组中元素的长度没有给出,可以动态的给。
- 创建格式三,创建静态二维维数组
基本格式:
数据类型[][] 数组名 = new 数据类型[][]{{元素1,元素2...},{元素1,元素2...},{元素1,元素2...}}; |
简化格式:
数据类型[][] 数组名 = {{元素1,元素2...},{元素1,元素2...},{元素1,元素2...}}; |
【随堂练习】
- 有三个班级,第一个班级3个学生,第二个班级4个学生,第三个班级5个学生。要求通过键盘录入三个班级学生的成绩,并计算每个班级学生的的平均成绩和三个班级学生的总均成绩。