前言
今天在项目中使用集合类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; } }