对象拷贝性能对比分析

对象拷贝可分为浅拷贝和深拷贝,在开发过程中深拷贝不是随处可见,大部分是引用的赋值,也即是内存地址的引用。如果简单的类似Student studen1 = student0这样便认为复制了一份,这就大错特错了,有些时候你会莫名的发现studen1没有任何操作里面的属性却发生变化了,不用说一定是student0在某个时候被修改了,因为这两个对象引用的是一个地址的内容。开发过程中,因为嵌套过深,对象中转过多,着实需要小心。

真正的拷贝是完全复制一份,从而与原对象隔离开,保证了原对象的定格,从而在操作过程中不用担心原对象被修改,必要时可“一键还原”。

什么是浅拷贝?什么是深拷贝?

先了解一个基础知识:堆内存和栈内存。

针对变量,栈内存上分配一些基本类型的变量与对象的引用,而堆内存分配给真正的对象本身以及数组等,堆内存上的数据由栈内存上的相应变量引用,相当于栈中存储着堆内存中实际对象或数组的标记或别名(实际上是堆内存变量首地址)。这里包含两部分:一个是基本数据类型,一个是引用数据类型,浅拷贝和深拷贝就是在这个基础之上做的区分。

假如在拷贝这个对象的时候,只对基本数据类型进行了拷贝,而对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝。反之,在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量,则认为是深拷贝。

简单的说:不能拷贝完全的就是浅拷贝,能够完完全全复制一份的叫深拷贝

浅拷贝
浅拷贝
对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。

深拷贝
深拷贝
对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。

Android常见的拷贝方式

Android开发常见的拷贝方式总结大概有以下六种:

  1. 对象属性相互赋值
  2. Serializable
  3. Parcelable
  4. Gson
  5. Cloneable
  6. Mapstruct

对象的相互赋值

对象属性相互赋值这个是最普通也是操作最笨重的,就是两个对象所有属性相互赋值,从而实现对象的拷贝;注意前边说的是操作最笨重的,但是如果从性能和执行效率上考虑应该是最快的,因为免去了其他额外的开销直接从最基本入手。

    Student student = new Student();
    student.setAge(11);
    student.setName("小明");
    Classmate classmate = new Classmate();
    classmate.setAge(12);
    classmate.setName("张三");
    student.setClassmates(classmate);
    
    Student studen1 = new Student();
    studen1.setAge(student.getAge());
    studen1.setName(student.getName());
    studen1.setClassmates(student.getClassmate());

细心的同学看到上边有一个引用类型classmate,前边已经说过如果对引用数据类型只是进行了引用的传递,而没有真实的创建一个新的对象,则认为是浅拷贝。那么深拷贝应该如何操作呢?

    student1 = new Student();
    student1.setName(student.getName());
    student1.setAge(student.getAge());
    Classmate classmate1 = new Classmate();
    classmate1.setName(student.getClassmates().getName());
    classmate1.setAge(student.getClassmates().getAge());
    student1.setClassmates(classmate1);

如上把studen的Classmate各个属性值重新拼装成一个对象然后存入stuent1中,充分将student中的各个属性做一个拷贝过程,算是一个深拷贝。

Serializable

Serializable实现拷贝过程原理是将对象序列化成二进制流到本地硬盘,然后在反序列化成对象,因此整个过程不存在某个引用类型共同指向问题,因此整个拷贝过程都属于深拷贝。需要注意的是拷贝的对象包括其内部引用类型的属性对象都要实现Serializable接口。

    /**
     * 对象拷贝,传入和返回的T必须是serializable类型。
     * 将对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝。
     */
    public static <T extends Serializable> T sCopy(T object) {
        try {
            //将对象写到流里
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(object);
            //从流里读出对象
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            return (T) ois.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

Parcelable

Parcelable实现拷贝过程的原理和Serializable如出一辙,Parcelable和Serializable的区别是一个序列化时的状态的保存,前者仅仅是保存在内存中,后者保存在硬盘中,因此前者性能更快,这也是android序列化所推荐的。因此Parcelable的拷贝过程也是深拷贝,所拷贝的对象以及引用类型的属性对象都要实现Parcelable接口。

    /**
     * 对象拷贝,传入的对象是Parcelable类型。
     */
    public static <T extends Parcelable> T pCopy(T object) {
        Parcel parcel = null;
        try {
            parcel = Parcel.obtain();
            parcel.writeParcelable(object, 0);
            parcel.setDataPosition(0);
            return parcel.readParcelable(object.getClass().getClassLoader());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (parcel != null) {
                parcel.recycle();
            }
        }
        return null;
    }

Gson

Gson拷贝的原理同样是序列化和反序列的过程,和Serializable是类似的,只不过Gson仅仅是序列化成json格式,并且不保存对象的相关方法和状态(反序列化时需要指定相应的对象类型),因此相比Serializable性能更优。使用时需要Gson相关依赖。

    /**
     * Gson copy
     */
    public static <T, S> T gCopy(S source, Class<T> targetType) {
        return sGson.fromJson(sGson.toJson(source), targetType);
    }

Cloneable

Cloneable拷贝是java对象固有的功能,每个实体类中父类Object中都有一个clone方法,专门用于本类的拷贝工作的。采用Cloneable拷贝需要实体类中实现Cloneable接口,并且要重写Object中的clone方法。

public class Student implements Cloneable {
    private String name;
    private int age;
    private Classmate classmates;

    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;
    }

    public Classmate getClassmates() {
        return classmates;
    }

    public void setClassmates(Classmate classmates) {
        this.classmates = classmates;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Student student = (Student) super.clone();
        student.classmates = (Classmate) this.classmates.clone();
        return student;
    }
}

注意观察Student中引用型属性对象classmates,重写的clone方法中如果仅仅是对Studen对象进行clone,而不对classmates进行clone的话则属于浅拷贝。这里我们需要深拷贝,所以需要针对Student中的所有引用型属性对象做一个clone(这里指classmates)。依次类推Classmate同样实现Cloneable,同样需要重写clone方法。

public class Classmate implements Cloneable {
    private String name;
    private int 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
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Classmate实体类中没有引用型属性对象了,所以重写的clone方法里面仅仅返回父类的clone()方法即可。

Mapstruct

MapStruct是一种类型安全的bean映射类生成java注释处理器。
我们要做的就是定义一个映射器接口,声明必需的映射方法。在编译的过程中,MapStruct会生成此接口的实现。该实现使用纯java方法调用的源和目标对象之间的映射,MapStruct节省了时间,通过生成代码完成繁琐和容易出错的代码逻辑。这里巧妙借助一下其映射功能实现类的拷贝,其拷贝原理和对象属性相互赋值相同。

android中使用Mapstruct需要在build.gradle中引入Mapstruct的相关依赖:

//模型映射
implementation 'org.mapstruct:mapstruct:1.2.0.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.2.0.Final'

使用也很简单,只需要自定义一个Mapstruct接口,在编译时会根绝注解自动生成相关代码方法。

@Mapper
public interface MapStructCopy {
    MapStructCopy INSTANCE = Mappers.getMapper(MapStructCopy.class);
    
    Student copy(Student t);
    Classmate copy(Classmate c);
    
}

这里需要注意的是,如果仅仅定义Student的映射方法,则同样属于浅拷贝,因为Classmate属于Student中的一个引用型对象属性,所以需要在自定义接口中定义一个属于Classmate的映射方法;来覆盖Student中属性映射。接下来就可以对Student对象拷贝了。

Student student = MapStructCopy.INSTANCE.copy(student);

性能对比

下边通过一个例子来对比以上各个拷贝的性能做一个分析对比。

假设这里有一个Student类,包含名字、年龄、同学Classmate(名字,年龄),这里做两个功能:

  1. 分别对上边的各个方法拷贝一万次,观察各个耗时。
  2. 修改拷贝对象属性值,查看原对象属性值是否变化,来验证是深拷贝还是浅拷贝。
public class Student implements Serializable, Parcelable, Cloneable {
    private String name;
    private int age;
    private Classmate classmates;

    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;
    }

    public Classmate getClassmates() {
        return classmates;
    }

    public void setClassmates(Classmate classmates) {
        this.classmates = classmates;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.name);
        dest.writeInt(this.age);
        dest.writeParcelable(this.classmates, flags);
    }

    public Student() {
    }

    protected Student(Parcel in) {
        this.name = in.readString();
        this.age = in.readInt();
        this.classmates = in.readParcelable(Classmate.class.getClassLoader());
    }

    public static final Parcelable.Creator<Student> CREATOR = new Parcelable.Creator<Student>() {
        @Override
        public Student createFromParcel(Parcel source) {
            return new Student(source);
        }

        @Override
        public Student[] newArray(int size) {
            return new Student[size];
        }
    };

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Student student = (Student) super.clone();
        student.classmates = (Classmate) this.classmates.clone();
        return student;
    }
}
public class Classmate implements Serializable, Parcelable, Cloneable {
    private String name;
    private int 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 int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.name);
        dest.writeInt(this.age);
    }

    public Classmate() {
    }

    protected Classmate(Parcel in) {
        this.name = in.readString();
        this.age = in.readInt();
    }

    public static final Parcelable.Creator<Classmate> CREATOR = new Parcelable.Creator<Classmate>() {
        @Override
        public Classmate createFromParcel(Parcel source) {
            return new Classmate(source);
        }

        @Override
        public Classmate[] newArray(int size) {
            return new Classmate[size];
        }
    };

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        test();
    }

    private void test() {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                Student student = new Student();
                student.setAge(11);
                student.setName("小明");
                Classmate classmate = new Classmate();
                classmate.setAge(12);
                classmate.setName("张三");
                student.setClassmates(classmate);

                Student student1 = null;
                long last;
                int count = 10000;

                Log.e("TAG","+++++++++++++++++开始");

                try {
                    last = System.currentTimeMillis();
                    for (int i = 0; i < count; i++) {
                        student1 = new Student();
                        student1.setName(student.getName());
                        student1.setAge(student.getAge());
                        Classmate classmate1 = new Classmate();
                        classmate1.setName(student.getClassmates().getName());
                        classmate1.setAge(student.getClassmates().getAge());
                        student1.setClassmates(classmate1);
                    }
                    log("Normal", last, student1, student);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                try {
                    last = System.currentTimeMillis();
                    for (int i = 0; i < count; i++) {
                        student1 = CopyUtil.sCopy(student);
                    }
                    log("Serializable", last, student1, student);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                try {
                    last = System.currentTimeMillis();
                    for (int i = 0; i < count; i++) {
                        student1 = CopyUtil.pCopy(student);
                    }
                    log("Parcelable", last, student1, student);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                try {
                    last = System.currentTimeMillis();
                    for (int i = 0; i < count; i++) {
                        student1 = CopyUtil.gCopy(student, Student.class);
                    }
                    log("Gson", last, student1, student);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                try {
                    last = System.currentTimeMillis();
                    for (int i = 0; i < count; i++) {
                        student1 = (Student) student.clone();
                    }
                    log("Cloneable", last, student1, student);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                try {
                    last = System.currentTimeMillis();
                    for (int i = 0; i < count; i++) {
                        student1 = MapStructCopy.INSTANCE.copy(student);
                    }
                    log("MapStruct", last, student1, student);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private void log(String tag, long lastTime, Student targetBean, Student resouceBean) {
        Log.e("TAG", "+++++" + tag+"+++++");
        Log.e("TAG", "耗时:" + (System.currentTimeMillis() - lastTime)
                + "  student hash:" + (targetBean.hashCode() == resouceBean.hashCode())
                + "  classmate hash:" + (targetBean.getClassmates().hashCode() == resouceBean.getClassmates().hashCode()));

        Log.e("TAG", "student1 name:" + targetBean.getName()+"  classmate1 name:" + targetBean.getClassmates().getName());
        targetBean.setName("王五");
        targetBean.getClassmates().setName("赵六");
        Log.e("TAG", "student name:" + resouceBean.getName()+"  classmate name:" + resouceBean.getClassmates().getName());
    }
}

打印结果如下:
对比结果
根据打印结果可以得出一个性能对比队列:

Cloneable > Normal = MapStruct > parcelable > Serializable = Gson

这个结果也是合乎常理的,前面已经着重针对每种拷贝原理做了分析,不用说凡是内存上操作的肯定优于硬盘上操作的。

如果根据操作麻烦程度可以得到一个对比队列:

Gson > Serializable > MapStruct > Cloneable > Parcelable > Normal

根据以上对比,项目开发过程中我个人认为,如果仅仅是简单的拷贝不必在意于性能多消耗那么一点点可以采用Gson,如果项目中的Model都实现了Serializable,也可以采用Serializable序列化形式;如果要兼顾性能和易用性,推荐Cloneable。

知识扩展

文中提到MapStruct是一种类型安全的bean映射类生成java注释处理器,而文中仅仅是利用很简单的映射来实现拷贝功能,其实MapStruct功能很强大,下边就举几个简单实用场景:

两个对象的映射

@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface OrderInfoMapper {
    OrderInfoMapper INSTANCE = Mappers.getMapper(OrderInfoMapper.class);

    @Mappings({
        @Mapping(source = "feeDesc", target = "feeTypeDesc"),
        @Mapping(source = "origAmount", target = "originAmount"),
        @Mapping(source = "confirmationNo", target = "orderNo")
    })
    PriceInfo dbToOrderPriceInfo(OrderPriceOffineDb orderOffineDb);
}

这里将OrderPriceOffineDb中的字段’feeDesc’、‘origAmount’、‘confirmationNo’字段赋值给PriceInfo的’feeTypeDesc’、‘originAmount’、'orderNo’这三个字段,其他这两个对象中字段相同的相互赋值,不同的根据MapStruct处理器策略做相应处理。

多个对象的映射

@Mapper
public interface AddressMapper {
 
    @Mappings({
        @Mapping(source = "person.description", target = "description"),
        @Mapping(source = "address.houseNo", target = "houseNumber")
    })
    DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
}

这里映射来源有两个分别是Person、Address,分别将Person对象中description字段和Address对象中houseNo字段的值分别赋值给DeliveryAddressDto中description、houseNumber。

如果多个源对象定义具有相同名称的属性,则必须使用@Mapping注解指定从中检索属性的source参数,如示例中的description属性所示。如果不解决这种歧义,将会引发错误。对于在给定源对象中仅存在一次的属性,可以选择指定源参数的名称,因为它可以自动确定。

MapStruct映射策略

正如上边两个对象的映射一样,所采用的映射策略是unmappedTargetPolicy = ReportingPolicy.IGNORE,假如目标映射对象有字段未被映射将被忽略,如果没有这个策略指定则会给出响应的提示警告。

下边给出MapStruct常用的映射策略:
MapStruct常用的映射策略

其他

  • MapStruct GitHub 访问地址 : https://github.com/mapstruct/mapstruct/
  • 使用例子 : https://github.com/mapstruct/mapstruct-examples
  • MapStrcut与其它工具对比以及使用说明! http://www.tuicool.com/articles/uiIRjai
  • MapStruct 1.1.0.Final中文参考指南 https://blog.csdn.net/YoshinoNanjo/article/details/81363285
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值