4.1 数组存在的意义
数组是一个容器,其存在是为了存储同种数据类型
的多个值
4.2 数组的相关概念
4.2.1 数组
-
容器的概念:
-
生活中的容器:水杯、教室、柜子… 这些都是可以存储物体的
-
程序种的容器:是将多个数据存储到一起,每个数据称为该容器的元素
-
-
数组的概念
数组是一个长度固定用于存储数据的容器,且存储的数据的类型要一致
数组可以存储基本数据类型,也可以存储引用数据类型
所谓数组(array),就是相同数据类型的元素按一定顺序排列的集合,是把有限个类型相同的变量用一个名字命名,以便统一管理,然后用编号区分他们,这个名字称为数组名,编号称为下标或索引(index)。组成数组的各个变量称为数组的元素(element)。数组中元素的个数称为数组的长度(length)。
4.2.2 数组的特点
* 数组的长度一旦确定就不能修改
* 创建数组时会在内存中开辟一块连续的空间
* 数组存取元素的速度快,根据下标定位
4.2.3 数组的分类
- 按维度分
- 一维数组
- 二维数组
- 多维数组
- 按元素类型分
- 基本数据类型的数组:存储数据的值
- 引用数据类型的数组:存储对象(本质是存储对象的首地址)
4.3 一维数组的声明和使用
4.3.1 一维数组的定义格式
数据类型[] 数组名 = new 数据类型[数组长度];
-
new :关键字,创建引用数据类型所使用到的关键字,
数组属于引用数据类型
-
长度:数组的长度,表示数组容器中可以存储多少个元素
案例:创建一维数组
public class CreateArray {
public static void main(String[] args) {
// 创建一维数组
int[] array = new int[5] ;
}
}
* 左边:
* int:数组的数据类型
* []:代表的数组的维度,几个中括号就代表几维数组
* arr:数组名,合法的标识符
* 右边:
* new:创建新的实体或对象
* int:数据类型
* []:代表的数组
* 5:代表数组的长度
4.3.2 一维数组的初始化
数组初始化就是为数组开辟连续的内存空间
,确定数组元素的个数(即数组长度),并为每个数组元素赋值
-
数组初始化的两种方式
-
动态初始化
- 只指定长度,由系统给出元素的默认值
- 格式:
数据类型[] 数组名= new 数据类型[数组长度];
- 例:
int[] array = new int[5];
-
静态初始化
- 给出初始化值,又系统决定长度
- 格式:
数据类型[] 数组名 = new 数据类型[] {元素1,元素2,元素3...};
- 举例:
int[] array = new int[] {1 , 2 , 3 , 4 , 5};
- 简写:
int[] array = {1 , 2 , 3 , 4 , 5};
-
案例:创建数组
public class CreateArray {
public static void main(String[] args) {
// 动态初始化
int[] array1 = new int[5] ;
// 静态初始化
int[] array2 = new int[] {1 , 2 , 3 , 4 , 5} ;
int[] array3 = {1 , 2 , 3 , 4 , 5} ;
}
}
- 数组元素默认初始值
当我们使用动态初始化方式创建数组时,元素是默认值。
4.3.2 一维数组的使用
-
获取数组长度:
数组名.length
- 每个数组都具有长度,而且是固定的,Java中赋予了数组的一个属性,可以获取到数组的长度,语句为:
数组名.length
,属性length的执行结果是数组的长度,int类型结果。
- 每个数组都具有长度,而且是固定的,Java中赋予了数组的一个属性,可以获取到数组的长度,语句为:
-
表示数组中的一个元素:数组名[索引/下标]
- 每一个存储到数组的元素,都会自动的拥有一个编号,
从0开始
,这个自动编号称为数组索引(index)或下标
,可以通过数组的索引/下标访问到数组中的元素。
- 每一个存储到数组的元素,都会自动的拥有一个编号,
-
数组下标的范围:数组长度-1
- Java中数组的下标
从[0]开始
,下标范围是[0, 数组的长度-1]
,即[0, 数组名.length-1]
- Java中数组的下标
案例:遍历一维数组
public class ForToArray {
public static void main(String[] args) {
int[] array = new int[] {4 , 5 , 7 , 9 , 12 , 80} ;
for(int i = 0 ; i < array.length ; i ++) {
System.out.println(array[i]);
}
}
}
public class ArrayTest1 {
public static void main(String[] args) {
int[] arr = new int[5];
arr[0] = 5 ;
arr[3] = 2 ;
System.out.println(arr);
}
}
[I@1b6d3586
[
表示数组,几个[
就代表几维
I
表示是int类型
@
是固定格式
1b6d3586
表示数组的内存地址
4.4 一维数组内存分析
4.4.1 内存概述
内存是计算机中重要的部件之一,它是与CPU进行沟通的桥梁。其作用是用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。只要计算机在运行中,CPU就会把需要运算的数据调到内存中进行运算,当运算完成后CPU再将结果传送出来。我们编写的程序是存放在硬盘中的,在硬盘中的程序是不会运行的,必须放进内存中才能运行,运行完毕后会清空内存。
Java 程序在运行时,需要在内存中分配空间。为了提高运算效率,就对空间进行了不同区域的划分,因为每一片区域都有特定的处理数据方式和内存管理方式。
4.4.2 Java虚拟机的内存划分
- 当编译时会生成class文件,这些class文件会保存在硬盘上,当运行时,class文件被加载到内存中,内存中要进行代码的执行,此时代码中的内容有进栈的也有进堆的,谁进栈,谁进堆,要看它属于哪一块,如果是局部变量就进栈,new出来的东西就进堆。一定要跑到内存中才能执行,如果在电脑中开了过多的应用程序,电脑就会卡。
4.4.3 一维数组在内存中的存储
一个一维数组的内存图
public class ArrayTest1 {
public static void main(String[] args) {
int[] arr = new int[5];
System.out.println(arr); // [I@1b6d3586
}
}
- main方法进入方法栈执行
- 创建数组,JVM会在堆内存中开辟空间,存储数组
- 数组在内存中会有自己的以十六进制表示的内存地址
- 当我们动态创建int类型的数组没有赋值之前其默认值为0
- JVM将数组的内存首地址赋值给引用数据类型变量arr
- 变量arr保存的是数组内存中的地址,而不是一个具体的数值,因此称为引用数据类型
public class Demo2 {
public static void main(String[] args) {
// int[] arr = new int[5] {11 , 22 , 33 , 44 , 55} ; // 错误的
int[] arr = new int[] {11 , 22 , 33 , 44 , 55};
// int[] arr1 ;
// arr1 = {1 , 2 , 3 , 4 , 5} ; // 错误的,简写形式声明和赋值必须在同一行
System.out.println(arr[3]);
System.out.println(arr);
}
}
- 控制台打印出来的
[I@1b6d3586
并不是数组的地址
arr中存储的是数组的首地址
,但是数组是引用数据类型,在打印arr时,会自动调用arr数组对象的toString()
方法,该方法默认实现的是对象类型名@该对象hashCode()值的十六进制
-
数组下标为什么从0开始?
- 因为数组内第一个元素距离首地址间隔0个单元格
两个一维数组的内存图
public class ArrayDemo1 {
public static void main(String[] args) {
int[] arr1 = new int[3] ;
int[] arr2 = new int[3] ;
System.out.println(arr1);
System.out.println(arr2);
arr1[1] = 5 ;
arr2[2] = 10 ;
for(int i = 0 ; i < arr1.length ; i ++) {
System.out.println("arr1[" + i + "] : " + arr1[i]);
}
System.out.println("-------------------");
for(int i = 0 ; i < arr2.length ; i ++) {
System.out.println("arr2[" + i + "] : " + arr2[i]);
}
}
}
内存图:
三个引用指向两个一维数组
public class ArrayDemo2 {
public static void main(String[] args) {
int[] arr1 = new int[3] ;
int[] arr2 = new int[4] ;
int[] arr3 = arr1 ;
System.out.println(arr1);
System.out.println(arr2);
System.out.println(arr3);
arr1[0] = 5 ;
arr1[2] = 15 ;
arr2[2] = 9 ;
arr3[0] = 7 ;
for(int i = 0 ; i < arr1.length ; i ++) {
System.out.println("arr1[" + i + "]" + arr1[i]);
}
System.out.println("-------------------");
for(int i = 0 ; i < arr2.length ; i ++) {
System.out.println("arr2[" + i + "]" + arr2[i]);
}
System.out.println("-------------------");
for(int i = 0 ; i < arr3.length ; i ++) {
System.out.println("arr3[" + i + "]" + arr3[i]);
}
}
}
4.5 数组的常见操作
4.5.1 一维数组的常见异常
ArrayIndexOutOfBoundsException
:数组索引越界异常
当我们对数组进行操作时,下标的指定超出[0 , arr.length-1]
的范围时,就会出现数组下标越界异常
举例:
public class ArrayDemo {
public static void main(String[] args) {
// 定义一个长度为5的整形数组
int[] arr = new int[5] ;
// 访问其第6个位置上的元素
System.out.println(arr[5]);
}
}
NullPointerException
:空指针异常
数组已经不再指向堆空间,仍去访问
举例:
public class ArrayDemo1 {
public static void main(String[] args) {
// 定义一个数组
int[] arr = new int[] {1 , 2 , 3} ;
System.out.println(arr[1]);
arr = null ;
System.out.println(arr[0]);
}
}
4.5.3 一维数组常见用法
- 数组统计:求总和、均值、统计偶数个数等
示例:
public class ArrayDemo2 {
public static void main(String[] args) {
int[] arr = {10 , 5 , 8 , 4 , 3} ;
// 求总和
int sum = 0 ;
for (int i = 0 ; i < arr.length ; i ++) {
sum += arr[i] ;
}
System.out.println("数组arr的总和为:" + sum);
int avg = 0 ;
// 求平均值
for (int i = 0 ; i < arr.length ; i ++) {
avg += arr[i] ;
}
avg /= arr.length ;
System.out.println("数组arr的平均值为:" + avg);
// 统计偶数的个数
int count = 0;
for (int i = 0 ; i < arr.length ; i ++) {
if(arr[i] % 2 == 0) {
count ++ ;
}
}
System.out.println("数组arr中的偶数个数为:" + count);
}
}
- 求一维数组中的最大值
public class ArrayDemo3 {
public static void main(String[] args) {
// 找出数组中的最大值
/*
思路:定义一个变量max,将arr[0]的值赋给它,让它分别给数组中的元素进行比较,
遇到比max小的元素,则max不变,继续比较下一个元素,
遇到比max大的元素,则将其值赋给max,最终得到最大值
*/
int[] arr = {88 , 52 , 20 , 1 , 4 , 66 , 99 , -7 , 55} ;
int max = arr[0] ;
for (int i = 0 ; i < arr.length ; i ++) {
if(max < arr[i]) {
max = arr[i] ;
}
}
System.out.println("数组arr中的最大值为:" + max);
}
}
- 数组元素的反转
public class ArrayDemo4 {
public static void main(String[] args) {
int[] arr = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10} ;
// 定义一个变量用于两个数组元素之间的交换
int temp ;
System.out.print("反转前的数组arr:");
for (int i = 0 ; i < arr.length ; i ++) {
System.out.print(arr[i] + " , ");
}
for (int i = 0 ; i < arr.length / 2 ; i ++) {
temp = arr[i] ;
arr[i] = arr[arr.length - i - 1] ;
arr[arr.length - i - 1] = temp ;
}
System.out.println();
System.out.print("反转后的数组arr:");
for (int i = 0 ; i < arr.length ; i ++) {
System.out.print(arr[i] + " , ");
}
}
}
4.5.4 二分法查找
- 使用二分法查找数组元素的前提是数组是
有序
的- 最小下标min,最大下标max,要查找元素的下标mid
- 原理:将要查找的数据与数组中间
mid=((0 + (arr.length - 1)) / 2)
索引值的元素做对比:如果要查找的元素大于当前数组元素,则最小下表改为mid+1,依次类推,直到找到要查找的元素;如果要查找的元素小于当前数组元素,则最大下表改为mid-1,直到找到要查找的元素。当min>max时,说明数组内不存在要查找元素
案例:在数组int[] arr = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10} ;
中查找元素6
public class ArrayDemo5 {
public static void main(String[] args) {
int[] arr = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10} ;
int result = search(arr , 6) ;
String str = result != -1 ? "数组中存在目标元素" : "数组中不存在目标元素" ;
System.out.println(str + ",其下标为" + search(arr , 6));
}
/**
* 使用二分法查找指定元素
* @param arr 形参 数组
* @param a 形参 要查找的元素
* @return 当在数组中查到目标元素时,返回数组下标;没有查到时,返回-1
*/
public static int search(int[] arr , int a) {
int min , mid , max ;
min = 0 ;
max = arr.length - 1 ;
mid = (min + max) / 2;
while (true) {
if(arr[mid] == a) {
break;
} else if(arr[mid] < a) {
min = mid + 1 ;
mid = (min + max) / 2;
} else {
max = mid - 1 ;
mid = (min + max) / 2 ;
}
if(min > max) {
return -1 ;
}
}
return mid ;
}
}
4.5.5 数组元素排序
时间复杂度、空间复杂度、稳定性
- 时间复杂度
- 一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。并且一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)。n称为问题的规模,当n不断变化时,时间频度T(n)也会不断变化。但有时我们想知道它变化时呈现什么规律。为此,我们引入时间复杂度概念。一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n))为算法的渐进时间复杂度,简称时间复杂度。
- 空间复杂度
- 类似于时间复杂度的讨论,一个算法的空间复杂度(Space Complexity)S(n)定义为该算法所耗费的存储空间,它也是问题规模n的函数。
- 稳定性
- 排序一定会设计到数组元素位置的交换。如果两个元素相等,无论它们原来是否相邻,在排序过程中,最后它们变的相邻,但是它们前后顺序并没有改变,就称为稳定的,否则就是不稳定的。
4.5.6 冒泡排序(Bubble Sort)
冒泡排序的思路:冒泡排序是用双重for循环来实现的,外层循环控制比较的轮数,内层循环控制比较的次数
拿数组arr [24, 69, 80, 57, 13] 举例
public class ArrayDemo6 {
public static void main(String[] args) {
int[] arr = new int[] {24, 69, 80, 57, 13} ;
arr = bubbleSort(arr) ;
for (int i = 0 ; i < arr.length ; i ++) {
System.out.println(arr[i]);
}
}
public static int[] bubbleSort(int[] arr) {
for (int i = 0 ; i < arr.length -1 ; i ++) {
int temp ;
for (int j = 0 ; j < arr.length - i - 1 ; j ++) {
if(arr[j] > arr[j + 1]) {
temp = arr[j] ;
arr[j] = arr[j + 1] ;
arr[j + 1] = temp ;
}
}
}
return arr ;
}
}
如下图所示,可以很好的表现出冒泡排序的全过程
4.5.7 直接选择排序
案例:对数组{24 , 69 , 80 , 57 , 13}进行选择排序
- 选择排序:从0索引开始依次和后面元素进行比较,小的往前放(从小到大排序小的放前面,从大到小排序小的放后面),第一轮排序结束后,最小值出现在了索引最小处
- 对于数组{24 , 69 , 80 , 57 , 13},
- 第一轮排序,arr[0]分别与arr[1]到arr[4]进行比较,比较四次
- 第二轮排序,arr[1]分别与arr[2]到arr[4]进行比较,比较三次
- 第三轮排序,arr[2]分别与arr[3]和arr[4]进行比较,比较两次
- 第四轮排序,arr[3]与arr[4]进行比较,比较一次
- 最终得到结果
代码如下:
package cn.pdsu.edu;
public class ArrayDemo7 {
public static void main(String[] args) {
int[] arr = {24, 69, 80, 57, 13} ;
directSelectionSort(arr) ;
for (int k = 0 ; k < arr.length ; k ++) {
System.out.print(arr[k] + " , ");
}
}
public static int[] directSelectionSort(int[] arr) {
for (int i = 0 ; i < arr.length - 1 ; i ++) {
int temp ;
for (int j = i ; j < arr.length - 1; j ++) {
if(arr[i] > arr[j + 1]) {
temp = arr[i] ;
arr[i] = arr[j + 1] ;
arr[j + 1] = temp ;
}
}
}
return arr ;
}
}
4.6 二维数组
4.6.1 二维数组概述
一个拉苹果的货车上可以拉很多袋苹果,而每一个袋子中又装了很多个苹果,这种情况我们就可以使用二维数组来表示。
- 二维数组实际上就是元素为一维数组的数组
4.6.2 二维数组的定义格式
二维数组格式一:动态初始化-规则二维数组
数据类型[][] 变量名 = new 数据类型[m][n] ;
如:int[][] arr = new int[3][2] ;
对于数据类型[][] 变量名 = new 数据类型[m][n] ;
m
表示这个二维数组有多少个一维数组,n
表示每个一维数组有多少个元素
如 int[][] arr = new int[3][2] ;
这个二维数组包含了三个一维数组,名称是arr[0]、arr[1]和arr[2]
每个一维数组有两个元素,可以通过arr[m][n]来获取
此种方法创建的二维数组行和列是确定的,且元素也都有其默认值
- 以下两种形式也可以定义二维数组,但不推荐
数据类型 数组名[][] = new 数据类型[m][n];
数据类型[] 数组名[] = new 数据类型[m][n];
案例:
public class ArrayDemo8 {
public static void main(String[] args) {
int[][] arr1 = new int[3][2] ;
int arr2[][] = new int[3][2] ;
int[] arr3[] = new int[3][2] ;
System.out.println(arr1);
System.out.println(arr1[1]);
System.out.println(arr1[1][1]);
System.out.println(arr2);
System.out.println(arr3);
}
}
- 二维数组内存图-格式一
二维数组格式二:动态初始化-不规则二维数组
数据类型[][] 变量名 = new 数据类型[m][];
如:int[][] arr = new int[3][];
对于数据类型[][] 变量名 = new 数据类型[m][];
m
表示这个二维数组有多少个一维数组,只定义出了一维数组的个数,没有给出一维数组的元素个数。也就是只确定了行数,但是每一行里现在存储的都是null
案例
public class ArrayDemo9 {
public static void main(String[] args) {
int[][] arr = new int[3][] ;
System.out.println(arr);
System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr[2]);
arr[0] = new int[2] ;
arr[1] = new int[3] ;
arr[2] = new int[5] ;
System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr[2]);
}
}
- 二维数组内存图-格式二
二维数组格式三:静态初始化
数据类型[][] 变量名 = new 数据类型[][] {{...} , {...} , {...}...}
如:int[][] arr = new int[][] {{1 , 2 , 3} , {4 , 5 , 6} , {7}} ;
对于 数据类型[][] 变量名 = new 数据类型[][] {{...} , {...} , {...}...}
右边new 数据类型[][]
中不能写数字,行和列由大括号{}
中的元素个数决定。简化版:数据类型[][] 变量名 = {{...} , {...} , {...}...};
,如int[][] arr = {{1 , 2} , {3 , 4 , 5} , {7 , 9}};
案例:
public class ArrayDemo10 {
public static void main(String[] args) {
int[][] arr = {{1 , 2 , 3} , {4 , 5} , {6 , 7 , 8}} ;
System.out.println(arr);
System.out.println(arr[0]);
System.out.println(arr[1][1]);
}
}
- 二维数组内存图-格式三
4.6.3 二维数组的使用
- 二维数组的长度(行数):
数组名.length
- 二维数组的某一行:
数组名[行下标]
,此时相当于获取一组数据,本质上是一个一维数组。二维数组行标的范围是[0 , 数组名.length - 1]
- 获取某一行的列数:
数组名[行下标].length
- 获取某一个元素:
数组名[行下标][列下标]
案例:
public class ArrayDemo11 {
public static void main(String[] args) {
int[][] arr = new int[][] {{1 , 2 , 3} , {4 , 5} , {6 , 7 , 8}} ;
System.out.println("数组长度:" + arr.length);
System.out.println(arr[1]);
System.out.println("第一行长度" + arr[0].length);
System.out.println("第二行长度" + arr[1].length);
System.out.println("第三行长度" + arr[2].length);
System.out.println("arr[0][1] = " + arr[0][1]);
}
}
- 二维数组遍历
public class ArrayDemo12 {
public static void main(String[] args) {
int[][] arr = new int[][] {{1 , 2 , 3} , {4 , 5} , {6 , 7 , 8}} ;
for (int i = 0 ; i < arr.length ; i ++) {
for (int j = 0 ; j < arr[i].length ; j ++) {
System.out.print(arr[i][j] + " ");
}
System.out.println();
}
}
}
- 二维数组求和
public class ArrayDemo13 {
public static void main(String[] args) {
int[][] arr = new int[][] {{1 , 2 , 3} , {4 , 5} , {6 , 7 , 8}} ;
int sum = 0 ;
for (int i = 0 ; i < arr.length ; i ++) {
for (int j = 0 ; j < arr[i].length ; j ++) {
sum += arr[i][j] ;
}
}
System.out.println("和为 " + sum);
}
}
4.6.4 二维数组内存图
二维数组的元素本质上是一堆一维数组
静态赋值
int[][] arr = {
{1 , 1} ,
{2 , 2 , 2} ,
{3 , 3 , 3 , 3} ,
{4 , 4 , 4 , 4 , 4} ,
};
动态赋值
public class ArrayDemo14 {
public static void main(String[] args) {
int[][] arr1 = new int[3][4] ;
for (int i = 0 ; i < arr1.length ; i ++) {
for (int j = 0 ; j < arr1[i].length ; j ++) {
arr1[i][j] = i + 1 ;
}
}
for (int i = 0 ; i < arr1.length ; i ++) {
for (int j = 0 ; j < arr1[i].length ; j ++) {
System.out.print(arr1[i][j] + " ") ;
}
System.out.println();
}
}
}
先声明二维数组的行数,再声明列数并赋值
public class ArrayDemo15 {
public static void main(String[] args) {
int[][] arr = new int[4][] ;
for (int i = 0 ; i < arr.length ; i ++) {
arr[i] = new int[i + 1];
}
for (int i = 0 ; i < arr.length ; i ++) {
for (int j = 0 ; j < arr[i].length ; j ++) {
arr[i][j] = i + 1 ;
}
}
for (int i = 0 ; i < arr.length ; i ++) {
for (int j = 0 ; j < arr[i].length ; j ++) {
System.out.print(arr[i][j] + " ") ;
}
System.out.println();
}
}
}