一丶数组的基础知识
1. 什么是数组?
数组就是一个相同类型数据的集合,在内存上也是连续的。
2.数组的初始化
- 静态初始化
数据类型[ ] 数组名称 = {初始化数据}
如:int[ ] array={1,2,3,4,5}; - 动态初始化
数据类型[ ] 数组名称 = new 数据类型 [ ] { 初始化数据 }
如:int[ ] array=new int[ ] {1,2,3,4,5}
或者不对数组进行初始化
数据类型[ ] 数组名称 = new 数据类型 [ 数组长度]
如:int[ ] array=new int[ 5];
3.访问数组中的元素
int int[] arr={1,2,3,4,5};
System.out.println(arr[1]); // 执行结果: 2
System.out.println(arr[0]); //执行结果: 1
arr[2] = 100;
System.out.println(arr[2]); //执行结果:100
从上面代码不难发现:
- 使用 [ ] 按下标取数组元素时,下标从 0 开始
- 使用 [ ] 操作既能读取数据, 也能修改数据
4.数组的长度问题
int[] arr = {1, 2, 3};
System.out.println(arr[100]);
// 执行结果 Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
at Test.main(Test.java4)
抛出了 java.lang.ArrayIndexOutOfBoundsException 异常. 使用数组一定要下标谨防越界.此外:
- 使用 arr.length 能够获取到数组的长度,这个操作为成员访问操作符
- 下标访问操作不能超出有效范围 [0, length - 1] , 如果超出有效范围, 会出现下标越界异常
5.遍历一个数组
所谓 “遍历” 就是指将数组中的所有元素都访问一遍, 不重不漏. 通常需要搭配循环语句.
- for循环遍历数组
int[] arr = {1, 2, 3};
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+" ");
}
// 执行结果 1 2 3
- for-each 遍历数组
for(表达式1:表达式2) 表达式1指数组中的变量,表达式2表示数组名
int[] arr = {1, 2, 3};
for (int x : arr) {
System.out.print(x);
}
// 执行结果 1 2 3
for循环和for-each循环的的不同:
for-each循环不能用下标区访问
6.数组的拷贝
数组的拷贝:
- System.arrayCopy() (被native所修饰的方法,拷贝速度最快)
- for循环一个一个拷贝
- Arrays.copyOf() (调用了 System.arrayCopy() 速度慢)
- 数组名.clone() 数组名:拷贝的数组
以上四种拷贝方式,对数组当中如果是简单类型来说,那么就是深拷贝。如果数组存放的是引用类型,那么这四种方式就是浅拷贝。什么是浅拷贝呢?如果是两个引用,同时指向一个对象,那么通过一个引用,修改当前对象的值,那么另一个引用也会收到影响。这种拷贝就叫做浅拷贝。
7.数组的性质
public static void main(String[] args) {
int num = 0;
func(num);
System.out.println("num = " + num);
}
public static void func(int x) {
x = 10;
System.out.println("x = " + x);
}
// 执行结果
x = 10
num = 0
public static void main(String[] args) {
int[] arr = {1, 2, 3};
func(arr);
System.out.println("arr[0] = " + arr[0]);
}
public static void func(int[] a) {
a[0] = 10;
System.out.println("a[0] = " + a[0]);
}
// 执行结果
a[0] = 10
arr[0] = 10
比较上面两段代码可以发现,前者修改形参 x 的值, 不影响实参的 num 值。
后者在函数内部修改数组内容, 函数外部也发生改变. 此时数组名 arr 是一个 “引用” . 当传参的时候, 是按照引用传参。
- 什么是引用呢?
引用相当于一个 “别名”, 也可以理解成一个指针.
创建一个引用只是相当于创建了一个很小的变量, 这个变量保存了一个整数, 这个整数表示内存中的一个地址.
比如后面一段代码中: int[ ] arr = new int[ ]{1, 2, 3} 内存布局如图:
当我们创建 new int[]{1, 2, 3} 的时候, 相当于创建了一块内存空间保存三个 int
接下来执行 int[] arr = new int[]{1, 2, 3} 相当于又创建了一个 int[] 变量, 这个变量是一个引用类型, 里 面只保存了一个整数(数组的起始内存地址)
接下来我们进行传参相当于 int[] a = arr , 内存布局如图
接下来我们修改 a[0] , 此时是根据 0x100 这样的地址找到对应的内存位置, 将值改成 100
此时已经将 0x100 地址的数据改成了 100 . 那么根据实参 arr 来获取数组内容 arr[0] , 本质上也是获取 0x100 地 址上的数据, 也是 100.
总结: 所谓的 “引用” 本质上只是存了一个地址. Java 将数组设定成引用类型, 这样的话后续进行数组参数传参, 其实只 是将数组的地址传入到函数形参中. 这样可以避免对整个数组的拷贝(数组可能比较长, 那么拷贝开销就会很大).
8.Arrays工具类的使用
Arrays.copyOfRange(); //拷贝部分数组
Arrays.equals(); //比较两个数组是否相同,返回true或false
Arrays.fill(); //填充数组
Arrays.sort(); //对数组进行排序
Array.toString() //将数组以字符串的方式打印
Array.deepToString() //返回指定数组的“深度内容”的字符串表示形式
二丶二维数组
1.什么是二维数组
二维数组本质上也就是一维数组, 只不过每个元素又是一个一维数组。
2.基本语法
初始化方法与一位数组相似
数据类型[ ][ ] 数组名称={初始化数据}
如:int arr[ ][ ] arr={{1,2,3},{4,5,6}} ’
数据类型[ ][ ] 数组名称 = new 数据类型[ ][ ] { 初始化数据 }
如:int arr[ ][ ] arr=new int[ ][ ]{{1,2,3},{4,5,6}} ;
数据类型[ ][ ] 数组名称 = new 数据类型[ 数组长度][数组长度]
如:int [ ][ ] arr=new int[3 ][3 ];
- 此外还有不规则的二位数组
int [ ][ ] arr=new int[ 3][ ];
int [ ][ ] arr={{1},{3,4},{5,6}};
3.打印一个二维数组
使用for循环来打印
int[][] arr = {{1, 2, 3, 4},{5, 6, 7, 8}, {9, 10, 11, 12}};
for (int row = 0; row < arr.length; row++) {
for(int col = 0; col < arr[row].length; col++) {
System.out.printf("%d\t", arr[row][col]);
}
System.out.println(""); }
}
//// 执行结果
1 2 3 4
5 6 7 8
9 10 11 12
三丶数组中的冒泡排序,二分查找等问题
1.冒泡排序
import java.util.Arrays;
public class TestDemo1 {
public static void main(String[] args) {
int[] array={6,2,7,4,14,5,9,8}; //初始化一个数组
maoPao(array);
System.out.println(Arrays.toString(array)); //打印排序完的数组
}
public static void maoPao(int[] a) {
for (int i = 0; i <a.length -1; i++) { //外循环,确定循环的次数
for (int j = 0; j <a.length-1-i ; j++) { //内循环,每次循环都将一个最大的数沉底
if(a[j]>a[j+1]) { //判断两个相邻元素的大小
int temp=a[j+1];
a[j+1]=a[j]; //大的值右移
a[j]=temp; //内层一轮循环之后,最大的数就沉底了
}
}
}
}
}
//执行结果为:
[2, 4, 5, 6, 7, 8, 9, 14]
2.二分查找
- 非递归写法
public class TestDemo1 {
public static void main(String[] args) {
int[] array={1,2,3,4,5,6,7,8,9};
System.out.println(binarySearch1(array,7));
}
public static int binarySearch1(int[] arr, int toFind) {
int left=0;
int right=arr.length-1;
while(left<=right) {
int mid=(left+right)/2;
if(arr[mid]>toFind) { //要查找元素在mid左边
right=mid-1;
}else if (arr[mid]==toFind) {
return mid;
}else { //要查找元素在mid右边
left=mid+1;
}
}
return -1;
}
}
- 递归写法
public class TestDemo1 {
public static void main(String[] args) {
int[] array={1,2,3,4,5,6,7,8,9};
System.out.println(binarySearch(array,1,0,8));
}
public static int binarySearch(int[] array,int key,int left,int right) { //传入的四个参数依次是:数组名,查找元素,数组左下标,数组右下标
if(left>right) {
return -1; //没有找到,返回-1
}
int mid=(left+right)/2;
if(array[mid]==key) {
return mid; //mid==key时,mid即为要找的值
}else if(array[mid]>key) { //mid>key,说明key在mid的左边
return binarySearch(array,key,left,mid-1); //在mid左边继调用此方法查找key
}else {
return binarySearch(array,key,mid+1,right);//在mid右边继续调用此方法查找key
}
}
}
//执行结果
0