杀人无形的深浅拷贝

/ 0评 / 0

前言

今天在项目中使用集合类List的时候发生了一个异常:java.util.ConcurrentModificationException,so,百度一番,此异常的大致意思是在遍历一个list的时候,这个list被修改了,wft???看到这个表示一脸的黑人问号,先说说我的使用场景,有一个很长的list保存所有数据,然后将此list使用List.subList()方法分割成相同长度的多个小list进行使用,当我更新数据的时候,就出现了这个异常,检查发现,是在遍历总list的时候同时在刷新分割出来的小list,所以出现了异常。

深拷贝/浅拷贝

如果学过C语言,那么很容易理解这两个概念,深拷贝就是将指定地址的数据拷贝出来,浅拷贝只是将需要拷贝的数据的地址复制了一份而已。

举个栗子:

在你桌面上有一个txt文件,然后你对他创建了一个快捷方式,就是浅拷贝,你直接复制了这个txt,就是深拷贝,两者的区别就是,通过浅拷贝,就是公用一个数据(点击快捷方式打开的txt文件还是原来的那个),你可能无形中改变了原来的数据,深拷贝就是自己有了一个完全相同数据,改变这个不会影响原来的数据。浅拷贝比较快,深拷贝比较慢,自己类比大文件的复制和创建快捷方式的速度。

再举个栗子

上面都是理论的,要想理解,必须read the fucking sourcecode!!!

Person person = new Person("xiaoming", 21);
// 浅拷贝,perso2相当于person的快捷方式
Person person2 = person;
// 深拷贝,拷贝数据,产生一个一模一样的数据
Person person3 = new Person(person.getName(), person.getAge());
System.out.println("原始数据:");
System.out.println(person.toString());
System.out.println(person2.toString());
System.out.println(person3.toString());
person2.setName("lisi");
System.out.println("修改后的数据:");
System.out.println(person.toString());
System.out.println(person2.toString());
System.out.println(person3.toString());

output:
原始数据:
name = xiaoming,age = 21,hashCode = 1119866108
name = xiaoming,age = 21,hashCode = 1119866108
name = xiaoming,age = 21,hashCode = 8490467
修改后的数据:
name = lisi,age = 21,hashCode = 1119866108
name = lisi,age = 21,hashCode = 1119866108
name = xiaoming,age = 21,hashCode = 8490467

可以看到,浅拷贝的"内存地址"(java的对象的hashCode类似于内存地址)和原始数据相同,而且修改了会影响原来的数据!!!

最后补上我们的实体类。

public class Person {

    private String name;

    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return String.format("name = %s,age = %d,hashCode = %s", name, age, this.hashCode());
    }
}

探究下List的subList操作是不是浅拷贝

下面是测试代码以及输出结果,可以很清楚看到,subList是浅拷贝!!!!关于list的其他方法大家可以自行测试,其实大多数都是浅拷贝(为了速度).

List<Person> persons = new ArrayList<>();
for (int i = 0; i < 10; i++) {
    Person person = new Person("name:" + i, 10 + i);
    persons.add(person);
}
// sunList操作
List<Person> subPersons = persons.subList(0, 6);
System.out.println("浅拷贝:");
System.out.println("persons[0] = " + persons.get(0).toString());
System.out.println("subPersons[0] = " + subPersons.get(0).toString());

try {
    // 深拷贝
    List<Person> deepCopy = ListUtil.deepCopy(persons);
    System.out.println("深拷贝:");
    System.out.println("persons[0] = " + persons.get(0).toString());
    System.out.println("deepCopy[0] = " + deepCopy.get(0).toString());
} catch (ClassNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

output:
浅拷贝:
persons[0] = name = name:0,age = 10,hashCode = 1784423493
subPersons[0] = name = name:0,age = 10,hashCode = 1784423493
深拷贝:
persons[0] = name = name:0,age = 10,hashCode = 1784423493
deepCopy[0] = name = name:0,age = 10,hashCode = 1838605207

深拷贝方法

由于java里面没有C语言中的memcpy函数,所以深拷贝只能通过对象的序列化和反序列化来进行,代码如下。需要注意的是List<T>里面的数据必须都能序列化.(实现Serializable接口即可),注意:此方法效率比较低,对于要求快速处理,需短时间多次拷贝的慎用!!!!

public class ListUtil {

    /**
     * 深拷贝数组
     * 
     * @param src src里面的对象必须可以序列化
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static <T> List<T> deepCopy(List<T> src) throws IOException, ClassNotFoundException {
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(byteOut);
        out.writeObject(src);

        ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
        ObjectInputStream in = new ObjectInputStream(byteIn);
        @SuppressWarnings("unchecked")
        List<T> dest = (List<T>) in.readObject();
        return dest;
    }
}

发表评论

您的电子邮箱地址不会被公开。