文章所记载笔记内容均出自B站,尚硅谷讲师,宋红康老师视频!!!
JAVA数组
1.数组基本概念
- 数组名
- 下标
- 元素
- 长度
2.特点
- 数组作为应用数据类型,其引用放置于栈内存中,通过在堆内存开辟一条
连续的存储空间
来存储数据- 数组本身是引用数据类型,数组中的元素可以是
任何类型
- 初始化完成后,数组长度就确定了,不能更改,
数组length为一个final常量
- 数组的
toString()
方法输出的是地址- 数组元素的益处
(数组越界)
3.一维数组的使用
3.1.数组的声明与初始化
//静态初始化:变量的初始化与元素的赋值同时进行
double[] prices1 = new double[]{20.2,30.0,40.0};
//动态初始化:变量的初始化与元素的赋值分开进行
double[] prices2 = new double[3];
//其他正确的方式
int arr1[] = new int[4];
int[] arr2 = {1,2,3};
3.2.调用数组的指定元素
- 元素下标index默认从
0
开始,其主要是代表内存地址的偏移量,例如第一个元素偏移量为0,所得内存地址就为数组元素的初始地址。 - 元素下标的错误使用,系统会报出
数组越界
的错误
3.3.数组的属性:length
length
表示数组的长度,在数组初始化时就已经确定,不能更改。其类型为final修饰的int常量
3.4.数组的遍历
- 遍历超出length范围时会发生数组越界
for(int i = 0 ; i < arr2.length ; i++){
System.out.println(arr2[i]);
}
3.5.数组元素
的默认初始化值
- 整型数组元素的默认初始化值:0
- 浮点型数组元素的默认初始化值:0.0
- 字符型数组元素的默认初始化值:0
- boolean型数组元素的默认初始化值:false
- 引用数据类型元素数组的默认初始化值:NULL
int[] arr1 = new int[3];
System.out.println(arr1[2]);
double[] arr2 = new double[4];
System.out.println(arr2[0]);
char[] arr3 = new char[5];
System.out.println(arr3[3]);
boolean[] arr4 = new boolean[4];
System.out.println(arr4[3]);
String[] arr5 = new String[5];
System.out.println(arr5[4]);
3.6.一维数组的内存解析
3.6.1.Java
内存结构如何划分
区域名称 | 作用 |
---|---|
虚拟机栈 | 用于存储正在执行的每个Java方法的局部变量表等。局部变量表存放了编译期可知长度 的各种基本数据类型,引用对象,方法执行完自动释放。 |
堆内存 | 储存对象,new来创建的,都储存在堆内存当中。 |
方法区 | 储存已被虚拟机加载的类的信息,常量,静态变量,即时编译器编译后的代码等数据。 |
本地方法栈 | 当程序调用了native的本地方法(C的库) 时,本地方法执行期间的内存区域 |
程序计数器 | 程序计数器是CPU的寄存器,它包含每一个线程下一条要执行的指令地址。 |
3.6.2.一维数组的内存结构
-
与目前数组相关的内存结构:
以
int[] arr = new int[]{1,2,3}
为例- 虚拟机栈:用于存放方法中声明的变量
arr
- 堆 :用于存放数组的实体(即数组元素)
{1,2,3}
- 虚拟机栈:用于存放方法中声明的变量
4.二维数组的使用
从数组底层的运行机制来看,其实没有多维数组
4.1.数组的声明有初始化
//静态与动态
/*复习一维数组
* int[] arr = new int[]{0,1,2};
* int[] arr = new int[3];
*int[] arr2 = {1,2,3};
* */
int[][] arr1 = new int[][]{{1,1},{2,2},{3,3,}};
int[][] arr2 = new int[3][2];
int[][] arr2 = new int[3][];
int[][] arr1 = {{1,1},{2,2},{3,3,}};
4.2.数组元素的调用
System.out.println(arr1[1]);
//输出的结果为16进制地址
4.3.数组的长度
System.out.println(arr1.length);
//输出结果为3
//当然二维数组不止本身有长度,其所包含的一维数组也是有长度的
System.out.println(arr1[1].length);
//输出结果为2
4.4.遍历数组
//可以采用嵌套循环来完成
for(int i = 0;i<arr1.length;i++){
for(int j = 0;j<arr[i].length;j++){
System.out.print(arr[i][j]);
}
}
4.5.数组元素的默认初始化值
4.5.1.默认初始化方式一
(int[][] ar1 = new int[3][4])
- 外层元素,默认储存地址值
- 内层元素默认与声明类型一致的基础值
- 整型数组元素的默认初始化值:0
- 浮点型数组元素的默认初始化值:0.0
- 字符型数组元素的默认初始化值:0
- boolean型数组元素的默认初始化值:false
- 引用数据类型元素数组的默认初始化值:NULL
int[][] ar1 = new int[3][4]; System.out.println(ar1[2]);//[I@776ec8df System.out.println(ar1[1][2]);//0 boolean[][] ar3 = new boolean[3][4]; System.out.println(ar3[2]);//[Z@4eec7777 System.out.println(ar3[1][2]);//false String[][] ar4 = new String[3][4]; System.out.println(ar4[2]);//[Ljava.lang.String;@3b07d329 System.out.println(ar4[1][2]);//null
4.5.2.默认初始化方式二
(int[][] ar2 = new int[3][])
- 外层默认存储NULL值(内存元素未指定长度,系统不会开辟空间,内层没有地址,外层只能按引用数据类型的方式来处理,放置一个NULL值)
- 内层元素不存在,如果调用系统报错,空指针异常
int[][] ar4 = new int[3][]; System.out.println(ar4[2]);//null System.out.println(ar4[2][3]);//NullPointerException 空指针异常
4.6.二维数组内存解析
4.7.二维数组课后练习
4.7.1.遍历求和
//静态初始化
int[][] arr1 = new int[][]{{3,5,8},{12,9},{7,0,6,4}};
//动态初始化
int[][] arr2 = new int[3][];
arr2[0] = new int[]{3,5,8};
arr2[1] = new int[]{12,9};
arr2[2] = new int[]{7,0,6,4};
//使用嵌套for循环来遍历二维数组
int sum = 0;
for (int i = 0; i < arr1.length ; i++) {
for (int j = 0; j < arr1[i].length ; j++) {
sum+=arr1[i][j];
}
}
System.out.println(sum);
4.7.2.数组的赋值情况与处理方法
int[] x,y[];//表示 int[] x ; int[] y[]表示 === int[][] y; // x 为整形的一维数组 // y 为整形的二维数组
x[0] = y; //错误,数组为引用类型,x[0]与y类型不一样,不可直接赋值
y[0] = x; //正确,y[0]表示int型一维数组(y表示二维数组),x也是int型一维数组
y[0][0] = x; //错误,类型不一样
x[0][0] = y; //错误,x不是二维数组
y[0][0] = x[0]; //正确,y[0][0]二维数组的内层元素就是一维数组
x = y ; //错误
5.数组中常见算法与操作
5.1.数组特征值计算
//动态初始化创建数组
int[] arr = new int[10];
//for循环给数组赋值
for (int i = 0; i < arr.length; i++) {
arr[i] = (int)(Math.random()*90)+10;
System.out.print(arr[i]+"\t");
}
System.out.println();
//求最大值
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
max=arr[i]>max?arr[i]:max;
}
System.out.println(max);
//最小值
int min = arr[0];
for (int i = 0; i < arr.length; i++) {
min=arr[i]<min?arr[i]:min;
}
System.out.println(min);
//总和
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum+=arr[i];
}
System.out.println(sum);
//平均值
int avgsum = sum/arr.length;
System.out.println(avgsum);
5.2.数组的赋值与复制
- 创建一个长度为6 的int型数组,要求数组元素的值都在1-30之间,且都是随机赋值,同时要求各元素的值各不相同
int[] arr = new int[6];
for (int i = 0; i < 6; i++) {
arr[i] = (int)(Math.random()*30)+1;
for (int j = 0; j < i; j++) {
if(arr[i]==arr[j]){
i--;
break;
}
}
}
for (int i = 0; i < 6; i++) {
System.out.print(arr[i]+"\t");
}
- 直接将
array2=array1
这样做只会将数组 1 的地址值赋给数组 2- 数组2与数组1指向同一片内存空间
- 当改变数组 2 的值时,也是改变数组1 的值
- 如果需要完成值的复制,可以通过for循环将
array1[x] = array[y]
进行赋值来完成复制
5.3.数组元素的反转
方法一 : 新建一个数组来反转加复制
(倒着遍历)
int[] arr = {4,5,8,2,1,9};
int[] arr2 = new int[6];
for (int i = 0; i < 6; i++) {
arr2[i] = arr[5-i];
}
arr = arr1;
方法二 : 从两端往里走,一组一组反转
(长度为奇数与偶数两种情况在代码中都一样)
int[] arr = {4,5,8,2,1,9,7};
for (int i = 0; i < (int)((arr.length+1)/2) ; i++) {
int temp = 0;
temp = arr[i];
arr[i] = arr[arr.length-1-i];
arr[arr.length-1-i] = temp;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+"\t");
}
方法三 :设置两个指针作用的标志,分别从两端往里走,当
x=y || x>y
时停止
int[] arr = {4,5,8,2,1,9,7};
int x = 0;
int y = arr.length-1;
while (x<y){
int temp = 0;
temp = arr[x];
arr[x] = arr[y];
arr[y] = temp;
x++;
y--;
}
5.4.数组的扩容与缩容
因为数组一旦建立,其大小长度类型都不可改变,所以数组扩容与缩容原理相同,都是要新建一个数组,将老数组的值赋给新数组,同时将新数组的地址赋值给老数组,当老数组的内存不再被指定时,JAVA的
GC
机制会将老内存释放。
//数组扩容
int[] arr = new int[]{1,2,3,4,5};
//扩容一倍
int[] newArr = new int[arr.length*2];
//或int[] newArr = new int[arr.length << 1];
//将原来数组的元素复制到新数组
for (int i = 0; i < arr.length; i++) {
newArr[i] = arr[i];
}
arr = newArr;
5.5.数组的元素查找
5.5.1.线性查找
int[] arr = new int[]{21,55,45,66,5,4,8,11};
int target = 5;
//线性查找
for (int i = 0; i < arr.length; i++) {
if (arr[i] == target){
System.out.println("找到"+(i+1));
break;
}
if(i == arr.length-1) System.out.println("未找到");
}
5.5.2.二分法查找
使用二分法的要求:数组数值顺序排列
二分法概念解析
二分法程序流程解析
int[] arr2 = new int[]{1,3,5,7,9,11,22,32}
int h = 0;
int end = arr2.length-1;
int mid = (h+end)/2;
while(h <= end){
mid = (h + end)/2;
if (arr2[mid] == target){
break;
}else if (arr2[mid] > target){
end = mid;
}else {
h = mid;
}
}
System.out.println(arr2[mid]);
5.6.数组元素排序
5.6.1.冒泡排序
int[] arr = {34,35,4,8,45,12,22,32,54,66};
//冒泡排序
int temp = 0;
for (int i = 1; i < arr.length; i++) {
for (int j = 0; j < arr.length-i; j++) {
if (arr[j] > arr[j+1]){
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
5.6.2.快速排序
快速排序思想
//当end动的时候,head指向的值永远 <= arr[mid]
/*为什么要先从右边end开始往回走的问题 :
右边开始end所经过的值都是比mid大的,当他停下来的时候其指向位置要么小于mid,要么指向与head相同(
这时候end指向的值等于head等于mid,这也是为啥head最初指向要与mid相同)直接表示排序完成
左边开始的话,head停下来有两种情况 :
第一种----安全的 : head遇到比mid大的值,停下等待与end交换
第二种----危险的 : head一直往右对比,知道遇到end指向的值停止,但是此时并没有对end指向的值与mid比大小
程序中需要的就是当head与mid相遇时交换位置。
*/
public static void quickSort(int[] arr , int head , int end){
//作为递归的出口
//为什么是判断是 >= : 当end指针从后往前畅通无阻,一直遇到head时,head,end都是0,这时候参与递归的i-1作为end值就会比head小
if(head >= end) {
System.out.println("11111");
return;
}
//第一步 : 给mid,i,j赋值
int i = head;
int j = end;
int mid = head;
int temp = 0;
while (i < j){
//第二步 : end从后往前找出小于arr[mid]的元素(或者相遇的情况下也要停下来,相遇的点一定小于arr[mid])
while(arr[j] >= arr[mid] && i != j) j--;
//第三步 : head从前往后走,找出大于arr[mid]的元素
while(arr[i] <= arr[mid] && i != j) i++;
//第四步 : 先判断i==j?如果相等直接与mid进行交换,如果不等就让i与j交换
if (i == j){
temp = arr[i];
arr[i] = arr[mid];
arr[mid] = temp;
break;
}else {
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
quickSort(arr,head,i-1);
quickSort(arr,i+1,end);
}