欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

探讨java深拷贝

程序员文章站 2024-03-08 17:21:22
本文将讨论以下4个问题     1. java cloneable接口实现深拷贝     2. java...

本文将讨论以下4个问题

    1. java cloneable接口实现深拷贝
    2. java 序列化实现深拷贝
    3. 号称最快的深拷贝二方库cloning源码分析
    4. 几种拷贝方式速度的比较

深拷贝的概念本文就不说了。在c++中实现深拷贝一般情况下重载赋值操作符 “=” 来实现同一个类的对象间的深拷贝,所以很自然的在java中我们也同样可以定义一个copy函数,在函数内部为对象的每一个属性作赋值操作。这种方式简单自然,但存在一个致命性的问题:如果有一天在类中新增加了一个需要深拷贝的属性,那么相应的copy函数也得进行修改,这种方法给类的可扩展性带来了极大的不方便。怎么解决这种问题,且看接下来的1、2、3章节的实现方式和4节的速度测试。
1. java cloneable接口实现深拷贝
这种方式,需要类实现colneable接口 clone 函数,在clone函数中调用super.clone。这种方式的深拷贝同样会带来另一个问题,如果类中有其他类的对象作为属性,则其他的类也需要重载并实现cloneable接口。来一个例子,在下例中complexdo中包含了simpledo对象,要实现complexdo深拷贝,则需要先实现simpledo的clone接口:

public class simpledo implements cloneable, serializable {
    private int x = 1;
    private string s = "simpledo";

    @override
    protected object clone() throws clonenotsupportedexception {
      simpledo newclass = (simpledo)super.clone();
      return newclass;
    }
  }

  public class complexdo implements cloneable, serializable {
    private int x = 1;
    private string s = "complex";
    private integer a = 123;
    private integer b = 1234;
    private integer c = 1334455;
    private string s2 = "hehehe";
    private string s3 = "hahahaha";
    private long id = 1233245l;
    private arraylist<simpledo> l = new arraylist<simpledo>();

    @override
    public object clone() throws clonenotsupportedexception {
      complexdo newclass = (complexdo) super.clone();
      newclass.l = new arraylist<simpledo>();
      for (simpledo simple : this.l) {
        newclass.l.add((simpledo) simple.clone());
      }
      return newclass;
    }
  }

需要注意的是很多文章说string类型的对象赋值操作符是深拷贝,但是其实在java中使用赋值操作符的都属于浅拷贝,但为什么这么明显的错误这么多的文章会非要说这个是深拷贝呢?我的理解是string、类型的属性都是基本类型,而且提供的方法只要是设计到内部数据的变动都会new一个新的对象出来。所以一个string的操作不会影响到其原先指向的内存。所以一般说string等基础类的赋值操作为深拷贝。
由于这个原因,在使用string字符串拼接的时候,需要开辟新的内存,所以很多人建议用stringbuilder来代替string来做拼接,因为stringbuilder只有在内置的char数组范围不够的时候才重新申请更大的内存(对于现代jvm,会对代码调优,string+string会被优化成stringbuilder.append的相类似的指令)。与拼接相对的裁剪,在string有个substring函数,当使用substring函数时,新string的内部char数组和原string是否相同?这个比较有意思,感兴趣的可以对比看看jdk1.6和jkd1.7的实现。
2. java 序列化实现深拷贝
这种方式的原理是利用java序列化,将一个对象序列化成二进制字节流,然后对该字节流反序列化赋值给一个对象。代码示例:

  public object seircopy(object src) {
    try {
      bytearrayoutputstream byteout = new bytearrayoutputstream();
      objectoutputstream out = new objectoutputstream(byteout);
      out.writeobject(src);

      bytearrayinputstream bytein = new bytearrayinputstream(byteout.tobytearray());
      objectinputstream in = new objectinputstream(bytein);
      object dest = in.readobject();
      return dest;
    } catch (exception e) {
      //do some error handler
      return null;
    }
 }

当然,也可以选用json等序列化的库来完成序列化,这种方式有效的规避了cloneabel接口的可扩展缺点,一个函数就可以基本上适用于所有的类.缺点是相对内存拷贝,序列化需要先将对象转换成二进制字节流,然后反序列化将该二进制字节流重新拷贝到一块对象内存,相对慢点。
3. 号称最快的深拷贝二方库cloning源码分析
在源码中,核心的处理逻辑在cloner类中,
分两条递归链路:

  • (1)deepclone->cloneinternal->fastclone->cloneinternal
  • (2)deepclone->cloneinternal->cloneobject->cloneinternal

在(1)中fastclone完成的是继承自ifastcloner接口类的对象,即都是些集合操作的拷贝;
在(2)中cloneobject完成的是通过反射机制拿到普通对象的每一个属性,然后对使用objenesis新生成对象的属性赋值。
这种方式可扩展性强,不仅可以依靠其现有的代码完成深拷贝,还可以自己定义一些克隆的方式和不需要克隆的类型,灵活性强。
4. 几种拷贝方式速度的比较
上述3中模式都可以完成深拷贝,那种拷贝的方式速度最快是我们所关心的。
先上测试代码:
 

  public void testclonecomplex() throws clonenotsupportedexception {
    final int copycount = 1;
    list<complexdo> complexdolist = new arraylist<complexdo>(copycount * 3);
    final complexdo complex = new complexdo();

    //调用二方库
    long start = system.currenttimemillis();
    for(int i = 0; i < copycount; ++i) {
      final complexdo deepclone = cloner.deepclone(complex);
      complexdolist.add(deepclone);
    }
    long end = system.currenttimemillis();
    system.out.println("deepclone cost time=" + (end-start));

    //调用cloneable接口实现的clone函数
    start = system.currenttimemillis();
    for(int i = 0; i < copycount; ++i) {
      final complexdo interfaceclone = (complexdo) complex.clone();
      complexdolist.add(interfaceclone);
    }
    end = system.currenttimemillis();
    system.out.println("interfaceclone cost time=" + (end-start));

    //序列化与反序列化生成新对象
    start = system.currenttimemillis();
    for(int i = 0; i < copycount; ++i) {
      final complexdo seirclone = seircopy(complex);
      complexdolist.add(seirclone);
    }
    end = system.currenttimemillis();
    system.out.println("seirclone cost time=" + (end-start));
  }


探讨java深拷贝

运行结果的单位为毫秒(此数据忽略不计算java热点和可能的gc)。

从这个表可以得出结论:

1、实现cloneable接口的拷贝是最快的,因为他只涉及到了内存拷贝,但是如果涉及的属性为普通对象比较多的时候写起来麻烦点
2、序列化/反序列化拷贝最慢
3、使用cloning库,由于使用了递归和反射机制相对cloneable接口实现的拷贝要慢,但比序列化方式要快。

以上就是本文的全部内容,希望对大家的学习有所帮助。