Java中数组相关知识总结
有些地方和c中还是有区别的
一.数组基本用法
1.什么是数组
在Java中,数组也就是相同类型元素的集合,它可以让我们“批量”创建相同类型的变量。数组的内存是连续的。
2.创建数组
基本语法:
int[] array1={1,2,3,4,5};
int[] array2=new int[]{1,2,3,4,5};
int[] array3=new int[5] //没有进行初始化
在上述三种创建方法中要注意区分后两种的区别
后两者都有一个new,new会在堆内存里为数组分配一块空间来存储数据,此时的变量名是放在栈中的,变量名中存放的是堆中数组的首地址,称此变量名为引用
第一种虽然没有new,但效果和new了一个对象是一样的
示意图如下:
3.数组的使用
①获取长度&访问元素问题
代码示例:
int[] arr={1,2,3};
//获取数组长度
System.out.println("length="+ arr.length); //结果为3
//访问数组中的元素
System.out.println(arr[1]) //结果 2
System.out.println(arr[2]) //结果 3
arr[2]=100;
System.out.println(arr[2]) //结果 100
②数组下标越界问题
代码示例:
int[] arr={1,2,3};
System.out.println(arr[100]);
//执行结果
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException:100
at Test.main(Test.java:4)
抛出了异常,所以要注意数组下标越界问题
注意:
·–使用 数组名.length 能够获取到数组的长度,. 这个操作为成员访问操作符,length不是方法,是一个属性
·–使用[ ] 按下标取数据元素,要注意下标是从0开始计数的
·–使用[ ] 操作既能读取数据,也可以修改数据
·–下标访问操作不能超出有效范围[0,length-1],超出范围,会显示数组越界异常(运行期间)
·-- 当定义好的数组没有被初始化,如果是简单类型初始值为0,如果是引用类型,初始值为NULL
③数组遍历问题
代码示例1:
//使用for循环来遍历数组
public class Test {
public static void main(String[] args) {
int[] arr={1,2,3,4,5};
for (int i = 0; i <arr.length ; i++) {
System.out.println(arr[i]);
}
}
}
代码示例2:
//使用for-each来遍历数组
int[] arr={1,2,3,4,5};
for (int x:arr) {
System.out.println(x);
}
foreach用法:
for(表达式1(数组当中的变量值):表达式2(数组名)){ }
for和for-each的区别?
for循环是通过数组的下标访问数组元素。foreach不能够用下标去访问,它定义的变量是直接存放数组中的值的,foreach能够更方便的完成对数组的遍历,可以避免循环条件和更新语句的错误。
二.数组作为方法的参数
1.基本用法
我们将数组的遍历以方法的形式来写
代码如下:
public class Test {
public static void printArray(int[] arr1) { //arr1为形参
for (int x : arr1) {
System.out.println(x);
}
}
public static void main(String[] args) {
int[] arr = {1, 2, 3}; //arr为实参
printArray(arr);
}
}
在Java中传参数有按址传递和按引用传递(也算按值传递)按引用传递是将一个引用中的地址值给了另一个引用,两个引用所指空间是一样的。
如图所示:
2.理解引用类型(不作深究)
①参数传内置类型
代码示例:
public class Test {
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
我们发现形参的改变不影响实参num的值
②参数传数组类型
代码示例:
public class Test {
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[] arr1){
arr1[0]=10;
System.out.println("arr1[0]"+arr1[0]);
}
}
// 打印结果
arr1[0]=10;
arr[0]=10;
认真比较上述两个代码,一个第一个传递的是值,第二个传递的是引用。
两者区别如图:
什么是引用呢?
引用相当于一个“别名”,也可以理解为一个指针
创建一个引用也就是相当于在栈上创建了一个很小的变量,这个变量保存了一个整数,这个整数表示内存中的一个地址,由上述图可以看到,当new一个数组是,堆就分配一块空间给数组,之后用int[]来接受,相当于创建了一个int[]变量,这个变量名就是引用类型,里面保存了堆里数组的起始地址
注意:
① System.out.println(array);
//打印出来是数组地址经过哈希的一个值,并不是十六进制的地址
②int[] array=NULL;
System.out.println(array.length); //这里length是一个属性
//会出现空指针异常
③String str=“asdfg”;
System.out.println(str.length()); // 这里length是一个方法
//打印结果为5
在点号之前若为NULL,运行时都会出现空指针异常
3.简单了解JVM内存区域划分(重要)
①程序计数器:只是一个很小的空间,保存下一条执行的指令地址
②虚拟机栈:重点是存储局部变量表也可以是其他信息
③本地方法栈:与虚拟机栈作用类似,只不过保存的内容是Native方法的局部变量,在有些版本中,本地栈和虚拟机栈是连在一起的
④堆:JVM所管理的最大的内存区,使用new创建的对象都在堆上保存
⑤方法区:用于存储已经被虚拟机加载的类信息,静态变量,字节码文件信息等数据
⑥运行时常量池:方法区的一部分
Native方法
JVM是一个基于c++实现的程序,在Java程序执行过程中,本质上也需要调用c++提供的一些函数进行和操作系统底层进行一些交互
这里的Native方法就是指这些c++实现的,再由Java来调用的函数
想要了解更多JVM内存的,可以自己去深究一下
三.数组作为方法的返回值
代码示例(写一个方法,将数组中每个数都*2):
//直接修改原数组
public class Test {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
transform(arr);
printArray(arr);
}
public static void transform(int[] arr){
for (int i = 0; i <arr.length ; i++) {
arr[i]=arr[i]*2;
}
}
public static void printArray(int[] arr) {
for (int x : arr) {
System.out.println(x);
}
}
}
上述代码破坏了原有的数组,试试在方法内部创建一个新的数组,并由方法返回回来。
代码如下:
//返回一个新的数组再打印出来
public class Test {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
int[] show=transform(arr);
printArray(show);
}
public static int[] transform(int[] arr){
int[] ret=new int[arr.length];
for (int i = 0; i <arr.length ; i++) {
ret[i]=arr[i]*2;
}
return ret;
}
public static void printArray(int[] arr) {
for (int x : arr
) {
System.out.println(x);
}
}
}
由于数组是引用类型,返回的时候只是将这个数组的首地址返回给函数调用者,没有拷贝数组的内容,更加高效
四.数组其他知识
1.将数组以字符串形式输出
在这里有个Arrays,是Java当中操作数组的一个工具类,所以在使用前要导入包
import java.util.Arrays;
int[] arr={1,2,3,4,5,6};
String newArr=Arrays.toString(arr);
System.out.println(newArr);
//打印结果
[1,2,3,4,5,6]
2.数组拷贝方法
①System.arraycopy(参数表); 底层是native方法,运行速度快
②Arrays.copyOf(参数表); 这种内部调用了①的方法,所以时间慢
③array.clone(参数表);
④for循环进行拷贝
深拷贝和浅拷贝
对于数组当中是简单类型来讲,就是深拷贝
对于数组当中是引用类型来讲,就是浅拷贝
如果两个引用同时指向一个对象,那么通过修改当前对象的值后,另一个引用所指的值也会进行修改,这种就是浅拷贝
示意图理解:
我们可以看到深拷贝是直接将值拷贝到另一个新的数组中,改变新数组中的值,旧数组中的值并不会因此改变
如果是引用类型,拷贝的是地址,新旧指的是同一个对象,所以改变新的数组,旧的数组里面的值也就发生了改变,这样就是浅拷贝
五.二维数组
二维数组本质上也就是一维数组,只不过每个元素又是一个一维数组
1.二维数组的定义:
//规则二维数组的定义
int[][] array1={{1,2}{3,4}{5,6}};
int[][] array2=new int[][]{{1,2}{3,4}{5,6}};
int[][] array3=new int[3][4];
//不规则二维数组定义
int[][] array4={{1}{3,4}{5,6}};
int[][] array5=new int[][4]; //行可以省略,列不可以
2.二维数组示意图:
3.打印二维数组:
public class Test {
public static void main(String[] args) {
int[][] arr = {{1, 2}, {2, 3}, {3, 4}};
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("");
}
}
//还有一种打印方法
System.out.println(Arrays.deepToString(arr));
当然还有三维数组,四维数组等,只不过出现频率都很低