day04_数组
数组
学习目标:
1. jvm内存图入门
2. 一维数组的使用
3. 二维数组的使用
4. 数组的内存结构
5. 数组中常见算法
6. 数组中常见的异常
一、JVM内存图入门
java程序运行在jvm上,jvm内存主要分为五块,结构如下:
每块内存负责的职责如下:
- Java虚拟机栈(Java Virtual Machine Stacks):描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame),栈帧中存储着局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,会对应一个栈帧在虚拟机栈中入栈到出栈的过程。与程序计数器一样,Java虚拟机栈也是线程私有的。
局部变量和引用地址都是在栈内存中。
-
堆:此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
-
方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区存放的数据只有一份
-
本地方法栈(Native Method Stack):与Java虚拟机栈作用很相似,它们的区别在于虚拟机栈为虚拟机执行Java方法(即字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。例如调用本地c/c++的方法。
-
程序计数器:程序计数器是记录当前线程所执行的指令行数。
二、数组的概述
2.1 为什么需要数组
为什么要有数组?在开发中,我们常常需要存取很多相同类型的数据,用变量的方式需要定义很多变量,不方便管理。所以引入数组的概念,一次存取多个相同类型的数据。数组有两个一定,一个类型一定,一个是大小一定。
2.2 数组的基本概念
数组中存在着一些重要的概念,如下所示:
- 数组名:数组的名称,数组内存的首地址
- 下标名(索引):数组元素的序号索引,从0开始
- 元素:数组中存放的内容
- 长度 :数组的长度
三、一维数组的使用
3.1 数组的定义
一维数组的定义语法如下:
示例:
/**
* 数组定义
*/
public class ArrayDemo1 {
public static void main(String[] args) {
//定义数组
int[] arr1;
String [] arr2;
}
}
3.2 数组的初始化
数组动态初始化语法:
示例:
/**
* 数组定义,以及动态初始化
*/
public class ArrayDemo1 {
public static void main(String[] args) {
//定义数组
int[] arr1;
//数组动态初始化
arr1 = new int[4];
arr1[0] = 10;
arr1[1] = 20;
arr1[2] = 30;
arr1[3] = 40;
//取值
System.out.println(arr1[0]);
System.out.println(arr1[1]);
System.out.println(arr1[2]);
System.out.println(arr1[3]);
}
}
数组静态初始化语法:
示例:
/*
数组的定义与静态初始化
*/
public class ArrayDemo2 {
public static void main(String[] args) {
//数组的声明
// String[] arr = new String[]{"乔峰","段誉","杨过"};
//简写
String[] arr = {"乔峰","段誉","杨过"};
//取值
System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr[2]);
}
}
3.3 数组元素的引用
引用数组元素内容需要注意以下几点:
- 定义并用运算符new为之分配空间后,才可以引用数组中的每个元素;
- 数组元素的引用方式:数组名[数组元素下标]
- 数组元素下标可以是整型常量或整型表达式。如a[3] , b[i] , c[6*i];
- 数组元素下标从0开始;长度为n的数组合法下标取值范围: 0 —>n-1;如int a[]=new int[3]; 可引用的数组元素为a[0]、a[1]、a[2]
- 每个数组都有一个属性length指明它的长度,例如:a.length 指明数组a的长 度(元素个数) 。数组一旦初始化,其长度是不可变的
示例1:数组元素的引用
/**
* 数组元素的引用
*/
public class ArrayDemo3 {
public static void main(String[] args) {
//数组的定义
int[] arr = {1,3,5,7,9};
//查看数组的长度
System.out.println(arr.length);
// 查看数组内容
System.out.println(arr[0]);
System.out.println(arr[1]);
System.out.println(arr[2]);
System.out.println(arr[3]);
System.out.println(arr[4]);
// 数组下标超过最大下标会越界,报异常ArrayIndexOutOfBoundsException
System.out.println(arr[5]);
}
}
示例2:数组的循环赋值与取值
/**
* 数组的循环赋值与取值
*/
public class ArrayDemo4 {
public static void main(String[] args) {
//定义数组
int[] arr = new int[5];
//循环赋值
for (int i = 0; i < arr.length; i++) {
arr[i] = i*5;
}
//循环取值
for (int i = 0; i < arr.length ; i++) {
System.out.println(arr[i]);
}
}
}
3.4 数组的注意事项
数组在使用过程中需要注意以下几点:
- 数组本身是引用数据类型,而数组中的元素可以是任何数据类型,包括基本数据类型和引用数据类型。
- 创建数组对象会在内存中开辟一整块连续的空间,而数组名中引用的是这块连续空间的首地址。
- 数组的长度一旦确定,就不能修改。
- 数组的分类:
- 按照维度:一维数组、二维数组、三维数组、…
- 按照元素的数据类型分:基本数据类型元素的数组、引用数据类型元素的数组(即对象数组)
3.5 forEach的应用
forEach可以遍历数组或集合容器中的数据,语法如下:
示例:
/**
* forEach遍历
*/
public class ArrayDemo5 {
public static void main(String[] args) {
//数组定义
int[] arr = {1,3,5,7,9};
//数组遍历
for(int num : arr){
System.out.println(num);
}
}
}
3.6 数组元素默认值
当数组元素没有赋值时,是存在默认值的,默认值如下:
3.7 一维数组内存图
3.8 小结
- 数组的基本使用
- 数据类型可以是基本类型或引用类型
- 数组类型固定长度不变
- 数组默认值
- 数组的内存图
四、一维数组练习题
4.1 数组赋值练习
将班级所有同学名字录入到一个一维数组中 。
参考答案:
/**
* 将班级所有同学名字录入到一个一维数组中
*/
public class ArrayTest1 {
public static void main(String[] args) {
//1.创建大小为5的String类型数组
String[] names = new String[5];
//2.创建扫描仪对象
Scanner input = new Scanner(System.in);
for(int i=0;i<names.length;i++) {
//3.提示信息
System.out.println("请输入第"+(i+1)+"个同学名字");
//4.接收客户的输入信息
String name = input.nextLine();
//5.将名字存入数组中
names[i] = name;
}
System.out.println("学生信息如下----------");
for(int i=0;i<names.length;i++) {
System.out.println(names[i]);
}
}
}
4.2 求数组值练习
已知一个一维数组如下int[] arr = {1,3,11,5,7,9};求出所有元素的最大值,最小值,和值,平均值,并输出出来。
参考答案:
/**
* 已知一个一维数组如下int[] arr = {1,3,11,5,7,9};求出所有元素的最大值,最小值,和值,平均值,并输出出来。
*/
public class ArrayTest2 {
public static void main(String[] args) {
//定义数组
int[] arr = {1,3,11,5,7,9};
//假设0下标是最大值 0下标是最小值
int max = arr[0];
int min = arr[0];
int sum = 0;
int avg = 0;
//2.判断最大值和最小值
for(int i=0;i<arr.length;i++) {
if(max<arr[i]) {
max = arr[i];
}
if(min>arr[i]) {
min = arr[i];
}
sum+=arr[i];
}
avg = sum/arr.length;
System.out.println("最大值"+max);
System.out.println("最小值"+min);
System.out.println("和是"+sum);
System.out.println("平均值是"+avg);
}
}
4.3 数组的复制
已知数组int[] arr1 = {1,3,5,7,9} , int[] arr2; 将数组arr1的内容复制到数组arr2中
参考答案:
/**
* 已知数组int[] arr1 = {1,3,5,7,9} , int[] arr2; 将数组arr1的内容复制到数组arr2中
*/
public class ArrayTest3 {
public static void main(String[] args) {
//1.定义数组
int[] arr1,arr2;
//2.初始化
arr1 = new int[]{1,3,5,7,9};
//3.打印数组arr1
for(int i=0;i<arr1.length;i++) {
System.out.println(arr1[i]);
}
//4.数组复制操作
arr2 = new int[arr1.length];
for(int i=0;i<arr1.length;i++) {
arr2[i] = arr1[i];
}
}
}
注意:切不可 arr2 = arr1 这不是复制数组内容,这是复制数组内存地址;arr2和arr1会指向同一块堆内存空间,改变arr2的同时也会改变arr1,改变arr1的同时也会改变arr2
4.4 数组的反转
已知一个数组如下String[] arr = new String[]{“AA”,”BB”,”CC”,”DD”,”EE”};,反转arr数组得到如下结果String[] arr = new String[]{“EE”,”DD”,”CC”,”BB”,”AA”};
参考答案:
/**
* 已知一个数组如下String[] arr = new String[]{"AA","BB","CC","DD","EE"};,
* 反转arr数组得到如下结果String[] arr = new String[]{"EE","DD","CC","BB","AA"};
*/
public class ArrayTest4 {
public static void main(String[] args) {
String[] arr = new String[]{"AA","BB","CC","DD","EE"};
for(int i=0;i<arr.length/2;i++) {
String str = arr[i];
arr[i] = arr[arr.length-i-1];
arr[arr.length-i-1] = str;
}
for(int i=0;i<arr.length;i++) {
System.out.println(arr[i]);
}
}
}
五、二维数组的使用
如果说可以把一维数组当成几何中的线性图形, 那么二维数组就相当于是一个表格。就像下面图一样:
对于二维数组的理解,我们可以理解为一维数组的数组。既数组0下标是一个一维数组,1下标又是一个一维数组数组…。
5.1 二维数组的动态初始化
二维数组存在两种定义方式,第一种定义方式如下:
int[][]arr = new int[4][3]
; 定义了名为arr的二维数组,二维数组中有4个一维数组 每一个一维数组中有3个元素- 一维数组分别为arr[0], arr[1], arr[2] ,arr[3]
- 给第一个一维数组0脚标位赋值为1写法是:arr
[0][0]
= 1; - arr 数组的长度 arr.length;
- arr[0].length 代表arr数组的0下标一维数组的长度。
示例:
/**
* 二维数组动态初始化1
*/
public class ArrayDemo7 {
public static void main(String[] args) {
//1.动态定义二维数组方式1
/*两种理解方式 y x
* 方式1 图形化理解 4 行 3 列 二维数组 int[][] arr = new int[4][3];
* 方式2 有 4 个 一维数组 每个一维数组可以存放3个数
*/
int[][] arr = new int[4][3];
//2.获得 4 个一维数组 arr[0] arr[1] arr[2] arr[3]
//打印 第0个数组
System.out.print(arr[0][0]);
System.out.print(arr[0][1]);
System.out.println(arr[0][2]);
//打印 第1个数组
System.out.print(arr[1][0]);
System.out.print(arr[1][1]);
System.out.println(arr[1][2]);
//打印 第2个数组
System.out.print(arr[2][0]);
System.out.print(arr[2][1]);
System.out.println(arr[2][2]);
//打印 第3个数组
System.out.print(arr[3][0]);
System.out.print(arr[3][1]);
System.out.println(arr[3][2]);
//3.查看二维数组长度
System.out.println(arr.length);
//4. 查看二维数组0下标长度
System.out.println(arr[0].length);
//5. 查看二维数组1标长度
System.out.println(arr[1].length);
//6. 查看二维数组2下标长度
System.out.println(arr[2].length);
//6. 查看二维数组3下标长度
System.out.println(arr[3].length);
}
}
第二种定义方式如下:
int[][] arr = new int[3][]
; 二维数组中有3个一维数组。 每个一维数组都是默认初始化值null (注意:区别于格式1)- 可以对这个三个一维数组分别进行初始化 arr[0] = new int[3]; arr[1] = new int[2]; arr[2] = new int[1];
注: int[][]arr = new int[][3];
这种写法是错误的
示例:
public class ArrayDemo8 {
public static void main(String[] args) {
//1.动态初始化2
/**
* 图形化理解 定义 4行 图形 几列待定
* 定义 4 个 一维数组 一维数组内容 没有初始化
*/
int[][] arr = new int[4][];
//2.定义arr[0] 一维数
arr[0] = new int[1];
//3.定义arr[1] 一维数
arr[1] = new int[2];
//4.定义arr[2] 一维数
arr[2] = new int[3];
//5.定义arr[3] 一维数
arr[3] = new int[4];
//6.查看 数组长度
System.out.println(arr.length);
//7.查看0下标数组长度
System.out.println(arr[0].length);
//8.查看1下标数组长度
System.out.println(arr[1].length);
//9.查看2下标数组长度
System.out.println(arr[2].length);
//10.查看3下标数组长度
System.out.println(arr[3].length);
}
}
5.2 二维数组的静态初始化
int[][] arr = new int[][]{{1},{1,2},{1,2,3}};
定义一个名称为arr的二维数组,二维数组中有三个一维数组
每一个一维数组中具体元素也都已初始化 第一个一维数组 arr[0] = {1}; 第二个一维数组 arr[1] = {1,2}; 第三个一维数组 arr[2] = {1,2,3};
以上代码也可以简写为:int[][]
arr = {{1},{1,2},{1,2,3}};
示例:
public class ArrayDemo9 {
public static void main(String[] args) {
//1.二维数组静态初始化
int[][] arr = new int[][] {
{1,1,1},
{2,2,2},
{3,3,3}
};
//2.简化
int[][] arr2 = {
{1},
{2,2},
{3,3,3}
};
}
}
5.3 二维数组的遍历
二维数组循环赋值与遍历过程如下
public class ArrayDemo10 {
public static void main(String[] args) {
//1.矩形
int[][] arr1 = new int[4][3];
//2.循环赋值
for(int y=0;y<arr1.length;y++) {
for(int x=0;x<arr1[y].length;x++) {
arr1[y][x] = 1;
}
}
//3.循环遍历
for(int y=0;y<arr1.length;y++) {
for(int x=0;x<arr1[y].length;x++) {
System.out.print(arr1[y][x]+" ");
}
System.out.println();
}
//2.直角三角形练习
String[][] arr2 = new String[4][];
//3.循环赋值
for(int y=0;y<arr2.length;y++) {
//4.定义一维数组
arr2[y] = new String[y+1];
for(int x=0;x<arr2[y].length;x++) {
arr2[y][x] = "*";
}
}
//4.打印二维数组
for(int y=0;y<arr2.length;y++) {
for(int x=0;x<arr2[y].length;x++) {
System.out.print(arr2[y][x]);
}
System.out.println();
}
}
}
5.4 小结
-
二维数组的动态初始化
-
二维数组的静态初始化
-
二维数组的遍历
六、二维数组练习题
6.1 杨辉三角
使用二维数组打印一个10行的杨辉三角
参考答案:
public class ArrayDemo11 {
public static void main(String[] args) {
//1.定义杨辉三角二维数组
int[][] arr = new int[10][];
//2.循环初始化 二维数组
for(int y=0;y<arr.length;y++) {
arr[y] = new int[y+1];
//3.给每一行的第一个值和最后一个值赋值为 1
arr[y][0] = arr[y][y] = 1;
//4.给其他位置赋值
//if(y>1) {
for(int x=1;x<arr[y].length-1;x++) {
arr[y][x] = arr[y-1][x-1]+arr[y-1][x];
}
//}
}
//3.遍历
for(int y=0;y<arr.length;y++) {
for(int x=0;x<arr[y].length;x++) {
System.out.print(arr[y][x]+" ");
}
System.out.println();
}
}
}
七、数组中常见算法
7.1 二分查找算法
已知数组int[] arr = {1,3,5,7,9,11,13};通过二分查找法查找数组中是否包含元素3,如果包含,元素3的下标是多少?注意(只有排好序的数组才能使用二分查找法)
分析过程如下:
另一种情况,当查找元素大于中间元素middle过程如下,这里以查找13为例
参考代码:
/**
* 数组元素二分查找法,前提:已排序的数组
*/
public class BinarySearchArray {
public static void main(String[] args) {
//定义数组
int[] arr = {1,3,5,7,9,11,13};
// 定义查找目标数
int target = 3;
//开始下标
int head = 0;
//结束下标
int end = arr.length-1;
//标记是否找到目标元素
boolean tag = true;
//二分查找
while(head<=end){
int middle = (head+end)/2;
if(arr[middle]==target){
System.out.println("查找到元素:"+target+",下标:"+middle);
tag = false;
break;
}else if(arr[middle]>target){
end=middle-1;
} else {
head=middle+1;
}
}
if(tag){
System.out.println("没有找到目标元素"+target);
}
}
}
7.2 数组的排序介绍(其余排序,由于算法过于复杂,后期统一讲)
排序:是计算机程序设计中的一项重要操作,是指对数组或集合中的元素进行按照大小或字母排序。
排序的算法有很多很多种,这里以冒泡排序、选择排序、插入排序为例进行讲解。
7.3 冒泡排序
冒泡排序的设计思想:从0下标开始比较相邻元素,通过交换下标元素位置,把大的元素放后面,小的元素放前面, 比较完一轮完成最后一个元素是最大的,以此类推,经过n轮比较完成排序的过程。
代码实现分析
- 一共比较几轮?
- 每轮比较几次?
参考答案
/**
* 冒泡排序
*/
public class SortDemo1 {
public static void main(String[] args) {
//定义数组
int[] arr = {1,11,9,3,7,5};
//排序
for(int i=0;i<arr.length-1;i++){ //如果有6个数,比较5轮就可以了,剩下一个一定是最小的
// 因为是j和j+1比较所以j<arr.length-1,又因为每比较一次,就少比较一个数,
// 所以j<arr.length-1-i;
for(int j=0;j<arr.length-1-i;j++){
// 比较相邻元素,交换相邻下标位置
if(arr[j]>arr[j+1]){
int t = arr[j];
arr[j] = arr[j+1];
arr[j+1] = t;
}
}
}
//打印数组
for(int i=0;i<arr.length;i++){
System.out.println(arr[i]);
}
}
}
7.4 选择排序
选择排序的思路是:
- 将0下标和后面所有元素比,如果0下标不是最小元素,就交换位置,比较完一轮0下标就是最小的元素
- 以此类推将1下标元素和后面所有元素比较,如果1下标不是最小元素,就交换位置,比较完一轮1下标就第二小的元素
- 依此类推,完成整个数组的排序
代码实现分析
- 一共比较几轮?
- 每轮比较几次?
参考答案:
/**
* 选择排序
*/
public class SortDemo2 {
public static void main(String[] args) {
//定义数组
int[] arr = {1,11,9,3,7,5};
//选择排序
for(int i=0;i<arr.length-1;i++){ //如果6个数,比较5轮即可,剩下最后一个一定是最大的
//让0下标i和后面所有下标比较;所以开始是i+1 结束到最大下标
for(int j=i+1;j<arr.length;j++){
if(arr[i]>arr[j]){
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
}
//打印数组
for(int i=0;i<arr.length;i++){
System.out.println(arr[i]);
}
}
}
7.5 插入排序
插入排序的设计思路是:
- 从第一个元素开始,该元素可以认为已经被排序
- 取出下一个元素,在已经排序的元素序列中从后向前扫描
- 如果该元素(已排序)大于新元素,将该元素移到下一位置
- 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
- 将新元素插入到该位置后
- 重复步骤2~5
参考答案:
/**
* 插入排序
*/
public class SortDemo3 {
public static void main(String[] args) {
//定义数组
int[] arr = {1,11,9,3,7,5};
//插入排序
for(int index = 1;index<arr.length;index++){
int t = arr[index];
int leftIndex = index-1;
while(leftIndex>=0 && arr[leftIndex]>t){
arr[leftIndex+1] = arr[leftIndex];
leftIndex--;
}
arr[leftIndex+1] = t;
}
//打印数组
for(int i=0;i<arr.length;i++){
System.out.println(arr[i]);
}
}
}
7.6 小结
-
二分查找
-
冒泡排序
-
选择排序
-
插入排序
八、数组中常见的异常
数组中的常见异常:
- 数组角标越界的异常:ArrayIndexOutOfBoundsExcetion
- 空指针异常:NullPointerException
九、总结
-
一维数组的使用
-
二维数组的使用
-
一维数组的常见算法:查找和排序。