public static void main(String[] args) {
//定义和初始化数组的3种方法
int[] arr1 = new int[10]; //只分配了内存,内容默认为10个0
int[] arr2 = new int[]{1,2,3,4};
int[] arr3 = {1,2,3,4};
}
只有第一种动态初始化的方式,[] 中括号里面有数字。
动态初始化:在创建数组时,直接指定数组中元素的个数。
静态初始化:在创建数组时不直接指定数据元素格式,而时将具体的数据内容进行指定。
故一个是数组个数指定(数组内容为默认值),一个是数组内容指定(数组个数为内容的个数)。
静态初始化虽然没有指定数组的长度,但是编译器在编译时会根据{ }中元素的个数来确定数组的长度。,静态初始化可以简写,省去后面的new T[]。
//实现一个方法 printArray, 以数组为参数, 循环访问数组中的每个元素, 打印每个元素的值.
public static void printArray(int[] arr) {
//方法1
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]);
}
System.out.println();
//方法2
for (int x:
arr) {
System.out.print(x);
}
System.out.println();
//方法3
String ret = Arrays.toString(arr);
System.out.println(ret);
}
public static void main(String[] args) {
//创建的数组,并且赋初始值
//创建一个 int 类型的数组, 元素个数为 10, 并把每个元素依次设置为 1 - 10
int[] array = new int[10];
for (int i = 0; i < array.length; i++) {
array[i] = i + 1;
}
// for (int x: array) {
// System.out.print(x + " ");
// }
printArray(array);
}
}
JVM的内存分布
内存是一段连续的存储空间,主要用来存储程序运行时数据的,如果对内存中存储的数据不加区分的随意存储,那么对内存的管理会非常麻烦。
JVM对所使用的内存按照功能的不同进行了划分:(5块区域)
虚拟机栈、本地方法栈、堆、方法区、程序计数器
基本类型变量和引用类型变量的区别
基本数据类型创建的变量,称为基本变量,该变量空间中直接存放的是其所对应的值。
引用数据类型创建的变量,一般称为对象的引用,其空间中存放的是对象所在空间的地址(在堆中的地址)。
局部变量的内存在栈上开辟,对象在堆上,引用变量其实就是一个变量来存地址。
引用变量并不直接存储对象本身,存储的是对象在堆中空间的起始地址,通过该地址,引用变量便可以去操作对象。
Java中局部变量在使用的过程中必须初始化,如下a是局部变量,在a没有进行初始化的情况下输出打印a,会报错。
null在Java中表示空引用,不指向任何对象,表示一个无效的内存位置,不能对这个内存进行任何的读写操作,一旦尝试读写等操作(.length也不可以),就会抛出NullPointerException空指针异常。
public class Test {
public static void main(String[] args) {
int[] arr = null;
int len = arr.length;
int a = arr[0]; //不指向任何对象,哪来的0下标?
}
}
在Java中没有约定null和0号地址的内存的关系,但是在C语言中null指0号地址(受保护的地址),注意Java中null就是代表引用不指向任何对象,不能进行任何读写操作。
public static void main(String[] args) {
int[] array = new int[]{1,2,3,4};
int[] array2 = array;
array2[1] = 100;
System.out.println(Arrays.toString(array));
System.out.println(Arrays.toString(array2));
}
array和array2指向同一个对象,通过array2修改1号位置的值,array再次访问那个值是被修改之后的。
public static void fun1(int[] array) {
array = new int[]{6,7,8};
}
public static void fun2(int[] array) {
array[0] = 9;
}
public static void main(String[] args) {
int[] array = {1,2,3,4};
fun1(array);
//fun2(array);
System.out.println(Arrays.toString(array));
}
调用fun1运行结果
调用fun2运行结果
可以发现fun1并没有影响实参,而fun2影响了实参,具体的调用过程如图:
fun1:
fun2:
所谓的“引用”本质上只是存了一个地址,Java将数组设定成引用类型,这样的话后续进行数组参数传参,其实只是将数组的地址传入到了函数的形参中,这样可以避免堆整个数组的拷贝,如果数组可能比较长,那么拷贝开销就会很大。
public static int[] fun10(){
int[] array = {1,2,3,4};
return array;
}
public static void main(String[] args) {
//数组作为函数的返回值
int[] ret = fun10();
System.out.println(Arrays.toString(ret));
//打印数组的很好用的方法,用Arrays类中toString方法打印数组
}
public static int[] grow(int[] array) {
for (int i = 0; i < array.length; i++) {
array[i] = array[i] * 2;
}
return array;
}
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
int[] array1 = grow(array);
System.out.println(Arrays.toString(array));
System.out.println(Arrays.toString(array1));
}
也可以改写成不要返回值,因为此时形参指向的数组对象和实参指向的是同一个。
public static void grow(int[] array) {
for (int i = 0; i < array.length; i++) {
array[i] = array[i] * 2;
}
// return array;
}
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
// int[] array1 = grow(array);
grow(array);
System.out.println(Arrays.toString(array));
// System.out.println(Arrays.toString(array1));
}
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
System.out.println(Arrays.toString(array));
}
使用这个方法打印数组很方便,Java中提供了java.util.Arrays。
那么编写myToString实现 Arrays.toString()
public static String myToString(int[] array) {
if(array == null) {
return null;
}
String s = "[";
for (int i = 0; i < array.length; i++) {
s += array[i];
if (i != array.length - 1){
s += ",";
}
}
s += "]";
return s;
}
public static void main(String[] args) {
int[] array = {1,2,3,4,5};
int[] array2 = null;
String ret = myToString(array);
String ret2 = myToString(array2);
System.out.println(ret);
System.out.println(ret2);
//System.out.println(Arrays.toString(array));
}
注意得判断数组为null的情况,不然如果为null,使用.length会抛出空指针异常。
public static void main(String[] args) {
int[] array = {1,2,3,4};
int[] newArray = array;
}
上述代码不叫拷贝,因为只有1个数组对象,两个引用指向的是同一个数组对象,并没有完成拷贝。
ctrl c —> ctrl v,有个一模一样的这叫拷贝。现在只是把他的值给了他而已,对象只有1个。
array和newArray引用的是同一个数组,因此此后通过newArray修改空间中内容之后,array也可以看到修改的结果。
2.数组拷贝的几种方法:①循环,②Arrays.copyOf(2个参数),③System.arraycopy(5个参数),④copyOfRange(3个参数),clone()。
①通过for循环进行拷贝
public static void main(String[] args) {
int[] array = {1,2,3,4};
int[] array2 = new int[array.length];
//不能初始化为null,否则后续的 array2[i]会报错,也不能不初始化,因为局部变量必须初始化
for (int i = 0; i < array.length; i++) {
array2[i] = array[i];
}
array2[0] = 199;
System.out.println(Arrays.toString(array));
System.out.println(Arrays.toString(array2));
}
输出结果:
array2是一块独立的空间,改变array2不能影响array。
②通过Arrays调用copyOf进行拷贝
public static void main(String[] args) {
int[] array = {1,2,3,4};
int[] array2 = Arrays.copyOf(array,array.length);
array2[0] = 199;
System.out.println(Arrays.toString(array));
System.out.println(Arrays.toString(array2));
}
运行结果:
copyOf扩容:第二个参数代表新的数组长度,可以通过其进行扩容:array.length*2,扩出来的为默认值
public static void main(String[] args) {
int[] array = {1,2,3,4};
int[] array2 = Arrays.copyOf(array,array.length*2);
array2[0] = 199;
System.out.println(Arrays.toString(array));
System.out.println(Arrays.toString(array2));
}
运行结果:原来是4个,拷贝之后是8个,扩容了。
copyOf的源码:2个参数,第一个参数代表原始数组,第二个参数代表新的长度,返回值是一个数组
public static int[] copyOf(int[] original, int newLength) {
int[] copy = new int[newLength]; //先申请了一个长度
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
通过源码可以看见,copyOf是通过System.arraycopy方法来进行扩容的,即coyOf的底层用的System.arraycopy方法。
arraycopy方法的源码:这个方法连花括号都没有{},什么都没有,但前面有个native修饰
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
native方法:叫做本地方法,特点:底层是由C/C++代码实现的(速度快),JVM实际上就是由C/C++代码写的,这个方法是看不到具体的实现的。
5个参数:src代表从哪里拷贝,srcPos从拷贝数组的哪个位置开始拷贝,dest:拷贝到哪里,destPos从拷贝数组拷贝到哪个位置,length拷贝的长度
③通过System.arraycopy实现拷贝,可以指定范围拷贝,支持部分拷贝,可以指定区间进行拷贝
public static void main(String[] args) {
//System.arraycopy
int[] array = {1,2,3,5};
int[] ret = new int[array.length];
System.arraycopy(array,0,ret,0,array.length);
System.out.println(Arrays.toString(ret));
ret[0] = 199;
System.out.println(Arrays.toString(array));
System.out.println(Arrays.toString(ret));
}
部分拷贝:从源数组的1号下标开始拷贝,此时拷贝的长度需要-1,否则就会出现数组越界异常。
public static void main(String[] args) {
//System.arraycopy
int[] array = {1,2,3,5};
int[] ret = new int[array.length];
System.arraycopy(array,1,ret,0,array.length-1);
System.out.println(Arrays.toString(ret));
ret[0] = 199;
System.out.println(Arrays.toString(array));
System.out.println(Arrays.toString(ret));
}
运行结果:
④copyOfRange()实现部分拷贝,注意是左闭右开
public static void main(String[] args) {
//Arrays.copyOfRange()
int[] array = {1,2,3,5};
int[] ret = Arrays.copyOfRange(array,0,3);
System.out.println(Arrays.toString(ret));
}
运行结果:from 0 to 3 ,不包含3下标
⑤clone,数组名.clone
克隆:产生一个副本,注意是数组名点出来的
public static void main(String[] args) {
int[] array1 = {1,2,3,4};
int[] array2 = array1.clone();
System.out.println(Arrays.toString(array1));
System.out.println(Arrays.toString(array2));
array2[0] = 199;
System.out.println(Arrays.toString(array1));
System.out.println(Arrays.toString(array2));
}
运行结果:
3.深拷贝和浅拷贝
深拷贝:array2[0]改变,不影响array1[0]的值
浅拷贝:array2[0]改变,影响array1[0]的值
深拷贝:
如果array2[0]的值改成99,array1[0]的值还是1不会改成99
浅拷贝:
在如上图的情况下,也可以实现深拷贝,比如array2不拷贝地址值,而是拷贝地址所指向的对象,这样就会形成新的地址,此时就是深拷贝了。(说明拷贝引用类型也可以实现深拷贝)
拷贝完成之后,修改拷贝对象的值,会不会影响原来对象的值?会影响就是浅拷贝,不会影响就是深拷贝。
深浅拷贝和是不是for循环、copyOf、clone拷贝无关,与代码的实现方式有关。
public static void main(String[] args) {
int[] array1 = {1,2,3,4};
int[] array2 = {1,2,3,4};
System.out.println(array1 == array2);
System.out.println(Arrays.equals(array1,array2));
}
运行结果:
分析:
array1 和 array2 虽然值是一样的,但是这是2个不同的对象,在堆里有两块内存,例如一块存的0x11,一块存的0x15。所以不能通过等号来比较,而通过Arrays的equals方法来比较对象里面存的每个值一样不一样。
public static void main(String[] args) {
int[] array = new int[10];
//动态初始化,现在这10个位置的元素都是10
//那么现在要把10个位置的元素都方成-1
Arrays.fill(array,-1);
System.out.println(Arrays.toString(array));
}
运行结果:
Arrays.fill源代码:
public static void fill(int[] a, int val) {
for (int i = 0, len = a.length; i < len; i++)
a[i] = val;
}
可以看到是他帮我们用for循环写好了,然后我们直接调用就可以了。
②局部赋值:
多2个参数,即4个参数,from to
从1位置到3位置,左闭右开
public static void main(String[] args) {
int[] array = new int[10];
Arrays.fill(array,1,3,-1);
System.out.println(Arrays.toString(array));
}
运行结果:
public static void main(String[] args) {
//二维数组的定义
int[][] array1 = new int[][]{{1,2,3},{4,5,6}};
int[][] array2 = {{1,2,3},{4,5,6}};
int[][] array3 = new int[2][3]; //动态初始化,全为默认值
System.out.println(Arrays.deepToString(array1));
System.out.println(Arrays.deepToString(array2));
System.out.println(Arrays.deepToString(array3));
}
其中第三种方法: int[][] array3 = new int[2][3];
,在C语言中定义二维数组可以省略行但是列不能省,在Java中可以省略列,但不能省略行。
public static void main(String[] args) {
int[][] array = new int[2][];
System.out.println(Arrays.deepToString(array));
}
运行结果:
通过运行结果可以分析得到,如果省略那么值为null
public static void main(String[] args) {
//打印二维数组的三种方式
int[][] array = {{1,2,3},{4,5,6}};
//1.for循环打印
for (int i = 0; i < array.length; i++) {
for (int j = 0; j < array[i].length; j++) {
System.out.print(array[i][j] + " ");
}
System.out.println();
}
System.out.println("============");
//2.for-each打印
for (int[] ret:array) {
for (int x: ret) {
System.out.print(x + " ");
}
System.out.println();
}
System.out.println("============");
//3.调用Arrays里的deepToString方法
System.out.println(Arrays.deepToString(array));
}
运行结果:
public static void main(String[] args) {
//不规则二维数组
int[][] array = new int[2][];
array[0] = new int[]{1,2,3};
array[1] = new int[]{6,7,8,9};
System.out.println(Arrays.deepToString(array));
}
运行结果:
分析: