基本数据类型 和 引用数据类型存储
基本数据类型有哪些,number,string,boolean,null,undefined五类。
引用数据类型(Object类)有常规名值对的无序对象{a:1},数组[1,2,3],以及函数等。
这两类数据存储分别是这样的:
a.基本类型–名值存储在栈内存中,例如int a=1;
当你b=a复制时,栈内存会新开辟一个内存,例如这样:
所以当你此时修改a=2,对b并不会造成影响,因为此时的b已自食其力,翅膀硬了,不受a的影响了。
当然,int a=1,b=a;虽然b不受a影响,但这也算不上深拷贝,因为深拷贝本身只针对较为复杂的Object类型数据。
b.引用数据类型–名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值,我们以上面浅拷贝的例子画个图:
当b=a进行拷贝时,其实复制的是a的引用地址,而并非堆里面的值。
而当我们a[0]=1时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受了影响,这就是所谓的浅拷贝了。
那,要是在堆内存中也开辟一个新的内存专门为b存放值,就像基本类型那样,起步就达到深拷贝的效果了。
数组拷贝
数组拷贝即为数组复制。首先,可以分为浅拷贝和深拷贝。
浅拷贝是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。
深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。
浅拷贝是按位拷贝对象:
①对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。
②对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
目前在Java中数据拷贝提供了如下方式:
1. for 循环(浅拷贝)
2. Object.clone() 克隆拷贝 (浅拷贝)
3. System.arraycopy() (浅拷贝)
4. Arrays.copyOf() (浅拷贝)
for 循环实现数组拷贝
class TestArray {
private int val = 10;
public void setVal(int val) { this.val = val; }
public int getVal() { return this.val; }
}
public class Arraysort {
public static void main(String[] args) {
TestArray[] barry1 = new TestArray[4];
barry1[0] = new TestArray();
barry1[1] = new TestArray();
barry1[2] = new TestArray();
barry1[3] = new TestArray();
TestArray[] barry2 = new TestArray[4];
for (int i = 0; i < barry1.length; i++) {
barry2[i] = barry1[i];
}
for (int i = 0; i < barry1.length; i++) {
System.out.print(barry1[i].getVal() + " ");
}
System.out.println();
for (int i = 0; i < barry2.length; i++) {
System.out.print(barry2[i].getVal() + " ");
}
System.out.println();
barry2[0].setVal(100000);
System.out.println("===============");
for (int i = 0; i < barry1.length; i++) {
System.out.print(barry1[i].getVal() + " ");
}
System.out.println();
for (int i = 0; i < barry2.length; i++) {
System.out.print(barry2[i].getVal() + " ");
}
}
}
10 10 10 10
10 10 10 10
===============
100000 10 10 10
100000 10 10 10 //浅拷贝
由上可得,for循环是浅拷贝,产生一个新的对象,两个引用指向同一个对象,这里即是引用类型拷贝内存地址,如果有一个对象改变了地址,会影响到另外一个对象,代码灵活,但效率低,速度相对较慢。
克隆拷贝 Object.clone()
在Java语言中,数组是引用类型。如果有两个数组变量a1和a2,则语句“a2 = a1;”将数组变量a1的引用传递给另一个数组a2;如果数组a2发生变化,则数组a1也发生变化。对于引用类型来说,它是浅拷贝;对简单类型来说,它是深拷贝。
引用类型:
package kkee;
import java.util.Arrays;
public class Arraysort {
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6,7,8,9};
int[] barry = array.clone();
System.out.println(Arrays.toString(array));
System.out.println(Arrays.toString(barry));
barry[0] = 1000;
System.out.println("=================");
System.out.println(Arrays.toString(array));
System.out.println(Arrays.toString(barry));
}
}
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
=================
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1000, 2, 3, 4, 5, 6, 7, 8, 9] //深拷贝
class TestArray {
private int val = 10;
public void setVal(int val) { this.val = val; }
public int getVal() { return this.val; }
}
public class Arraysort {
public static void main(String[] args) {
TestArray[] t1 = new TestArray[4];
t1[0] = new TestArray();
t1[1] = new TestArray();
t1[2] = new TestArray();
t1[3] = new TestArray();
TestArray[] t2 = t1.clone();//t2[0]
for(int i = 0;i < t1.length;i++) {
System.out.print(t1[i].getVal()+" ");
}
System.out.println();
for(int i = 0;i < t2.length;i++) {
System.out.print(t2[i].getVal()+" ");
}
System.out.println();
t2[0].setVal(100000);
System.out.println("===============");
for(int i = 0;i < t1.length;i++) {
System.out.print(t1[i].getVal()+" ");
}
System.out.println();
for(int i = 0;i < t2.length;i++) {
System.out.print(t2[i].getVal()+" ");
}
}
10 10 10 10
10 10 10 10
===============
100000 10 10 10
100000 10 10 10 //浅拷贝
查看clone()的源代码可知:
protected native Object clone() throws CloneNotSupportedException;
它的关键字是native(本地方法),返回类型是Object,所以赋值时要发生强制转换,并且也是由底层的C/C++语言实现的。
Object类是类结构的根类,其中有一个方法为protected Object clone() throws CloneNotSupportedException,这个方法就是进行的浅拷贝。有了这个浅拷贝模板,我们可以通过调用clone()方法来实现对象的浅拷贝。
但是需要注意:
1、Object类虽然有这个方法,但是这个方法是受保护的(被protected修饰),所以我们无法直接使用。因为protected关键字只允许同一个包内的类和它的子类调用,所以我们声明一个object类时,肯定不是同一个包内,所以就不能去调用它。
2、使用clone方法的类必须实现Cloneable接口,否则会抛出异常CloneNotSupportedException。对于这两点,我们的解决方法是,在要使用clone方法的类中重写clone()方法,通过super.clone()调用Object类中的原clone方法。
要调用这个方法,就需要我们写一个类,然后声明实现cloneable接口就好了,重写clone()方法:
@Override
public Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return super.clone();
}
这里需要是,为了能够在不同包内去调用这个方法,我们需要把这个权限升级为public。现在我们就可以调用这个类的clone()方法去拷贝我们的类了。总得来说clone()就是浅拷贝。s
System.arraycopy()
System.arraycopy(src,srcPos,dest,destPos,length)是System类提供的一个静态方法,可以用它来实现数组之间的赋值。查看源代码:
public static native void arraycopy(Object src, int srcPos, Object dest, int desPos, int length)
//参数含义:(原数组, 原数组的开始位置, 目标数组, 目标数组的开始位置, 拷贝个数)
它是浅拷贝,也就是说对于非基本类型而言,它拷贝的是对象的引用,而不是去新建一个新的对象。通过它的代码我们可以看到,这个方法不是用java语言写的,而是底层用c或者c++实现的,因而速度会比较快。
public class Arraysort {
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6,7,8,9};
int[] brray = new int[array.length];
System.arraycopy(array,0,brray,0,array.length);
System.out.println(Arrays.toString(array));
System.out.println(Arrays.toString(brray));
brray[0] = 1000;
System.out.println("=================");
System.out.println(Arrays.toString(array));
System.out.println(Arrays.toString(brray));
}
}
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
=================
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1000, 2, 3, 4, 5, 6, 7, 8, 9]
class TestArray {
private int val = 10;
public void setVal(int val) { this.val = val; }
public int getVal() { return this.val; }
}
public class Arraysort {
public static void main(String[] args) {
TestArray[] t1 = new TestArray[4];
t1[0] = new TestArray();
t1[1] = new TestArray();
t1[2] = new TestArray();
t1[3] = new TestArray();
TestArray[] t2 = new TestArray[4];
System.arraycopy(t1,0,t2,0,t1.length);
for(int i = 0;i < t1.length;i++) {
System.out.print(t1[i].getVal()+" ");
}
System.out.println();
for(int i = 0;i < t2.length;i++) {
System.out.print(t2[i].getVal()+" ");
}
System.out.println();
t2[0].setVal(100000);
System.out.println("===============");
for(int i = 0;i < t1.length;i++) {
System.out.print(t1[i].getVal()+" ");
}
System.out.println();
for(int i = 0;i < t2.length;i++) {
System.out.print(t2[i].getVal()+" ");
}
}
10 10 10 10
10 10 10 10
===============
100000 10 10 10
100000 10 10 10
Arrays.copyOf()
Arrays.copyOf底层其实也是用的System.arraycopy ,所以效率自然低于System.arraycpoy(),所以它也是浅拷贝。
源码如下:
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked") //源数组,拷贝的个数
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
public class Arraysort {
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6,7,8,9};
int[] brray = Arrays.copyOf(array,array.length);
System.out.println(Arrays.toString(array));
System.out.println(Arrays.toString(brray));
brray[0] = 1000;
System.out.println("=================");
System.out.println(Arrays.toString(array));
System.out.println(Arrays.toString(brray));
}
}
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
=================
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1000, 2, 3, 4, 5, 6, 7, 8, 9]
Arrays.copyOfRange():
复制数组的指定范围内容。源代码:
public static <T,U> T[] copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType) {
int newLength = to - from;
if (newLength < 0)
throw new IllegalArgumentException(from + " > " + to);
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, from, copy, 0,
Math.min(original.length - from, newLength));
return copy;
}
copyOfRange(数组名,起始索引,结束索引)从起始索引(包括)到结束索引(不包括)。结果为相同数据类型的数组。