游戏中大对象的深拷贝
2019-10-30 本文已影响0人
小圣996
一篇诗,一斗酒,一曲长歌,一剑天涯 --李白
在游戏开发过程中,有时会遇到这样的一种情况,就是需要深拷贝一个对象。比如卡牌回合制的战斗,入场即计算所有战斗过程的那种,有这样一种情形:己方阵营6个英雄在一个副本中需打3波战斗,每波战斗需以上波战斗剩余单位剩余血量参加,即每波战斗所有战斗单位信息不重置,这样需发送3场战报给前端,而前端播放战报时,又需要显示每场战斗的初始信息,比如单位数量,剩余血量等,这时就可能需要在战斗初始化时把阵营拷贝一份以在战报中告诉前端阵营的初始信息。而这样的拷贝是需要深拷贝的。
浅拷贝:浅拷贝创建一个新对象时,如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址指向的值,就会影响到另一个对象。
深拷贝:深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
通常来说,深拷贝有三种方式:
- 实现Clonable接口,并覆写clone()方法,此方法繁琐并容易出错,且需要注意的是,如果只是单纯调用父类的clone()方法,其实也是浅拷贝,这种方法的本质上是把引用类型的对象一一拆解成基本类型赋值,它是效率最高的;
- 用阿里巴巴的fastjson从Object转成json,然后转回object,需要注意的是,它的类要有无参构造函数,它的本质上是用反射完成的;
- 利用很多jar包工具里的序列化和反序列化,如JDK自带的序列化和反序列化,hessian2的序列化和反序列等,它们的本质是先转成二进制流再转成相应对象,此外还有很多,这些工具不容易出错,但需注意效率选择;
以下是测试用例,比如需拷贝回合制战斗中的阵营:
Camp.java
public class Camp implements Serializable {
public ArrayList<Fighter> fighters;//上阵英雄单位
public Camp(){}
public Camp(ArrayList<Fighter> fighters){
this.fighters = fighters;
}
@Override
public String toString() {
return "Camp [fighters=" + fighters + "]";
}
}
Fighter.java
public class Fighter implements Serializable {
public long id;// 唯一标示,如果是怪物,从配置表读取标示通常是int类型,需要转成long,保持和角色一致类型
public int templateId;// 模板ID
public int grid;// 格子
public int level;// 英雄等级-伤害计算基本伤害用到
public int carrer;// 英雄职业-伤害计算中职业伤害和战斗目标选择中用到
public HashMap<Integer, Long> properties = new HashMap<>(); // 属性集合
public HashMap<Integer, Long> bakProperties = new HashMap<>(); // 属性备份集合
public FightSkill baseSkill; //普攻
public FightSkill activeSkill; //大招
public ArrayList<FightSkill> passiveSkillList = new ArrayList<>();//被动列表
public Fighter(){}
public Fighter(long id, int templateId, int grid, int level, int carrer, HashMap<Integer, Long> properties,
FightSkill baseSkill, FightSkill activeSkill, ArrayList<FightSkill> passiveSkillList){
this.id = id;
this.templateId = templateId;
this.grid = grid;
this.level = level;
this.carrer = carrer;
this.properties = properties;
this.bakProperties.putAll(properties);
this.baseSkill = baseSkill;
this.activeSkill = activeSkill;
this.passiveSkillList = passiveSkillList;
}
@Override
public String toString() {
return "Fighter [id=" + id + ", templateId=" + templateId + ", grid="
+ grid + ", level=" + level + ", carrer=" + carrer
+ ", properties=" + properties + ", bakProperties="
+ bakProperties + ", baseSkill=" + baseSkill + ", activeSkill="
+ activeSkill + ", passiveSkillList=" + passiveSkillList + "]";
}
}
FightSkill.java
public class FightSkill implements Serializable {
public int id; //技能id或额外效果id
public int limitTimes;//技能释放次数
public int fireTimes;//已施放次数
public FightSkill(){}
public FightSkill(int id, int limitTimes, int fireTimes){
this.id = id;
this.limitTimes = limitTimes;
this.fireTimes = fireTimes;
}
@Override
public String toString() {
return "FightSkill [id=" + id + ", limitTimes=" + limitTimes + ", fireTimes=" + fireTimes + "]";
}
}
Main.java
public class Main {
public static void main(String[] args) {
HashMap<Integer, Long> properties = new HashMap<Integer, Long>();
properties.put(100, 10000L);
properties.put(101, 10000L);
properties.put(102, 10000L);
properties.put(103, 10000L);
properties.put(104, 10000L);
FightSkill fightSkill1 = new FightSkill(1, 0, 0);
FightSkill fightSkill2 = new FightSkill(2, 0, 0);
ArrayList<FightSkill> passiveSkillList = new ArrayList<>();
FightSkill fightSkill3 = new FightSkill(3, 0, 0);
FightSkill fightSkill4 = new FightSkill(4, 0, 0);
FightSkill fightSkill5 = new FightSkill(5, 0, 0);
passiveSkillList.addAll(Arrays.asList(fightSkill3, fightSkill4, fightSkill5));
Fighter fighter1 = new Fighter(1L, 11, 1, 100, 1, properties, fightSkill1, fightSkill2, passiveSkillList);
Fighter fighter2 = new Fighter(2L, 12, 2, 100, 1, properties, fightSkill1, fightSkill2, passiveSkillList);
Fighter fighter3 = new Fighter(3L, 13, 3, 100, 1, properties, fightSkill1, fightSkill2, passiveSkillList);
Fighter fighter4 = new Fighter(4L, 14, 4, 100, 1, properties, fightSkill1, fightSkill2, passiveSkillList);
Fighter fighter5 = new Fighter(5L, 15, 5, 100, 1, properties, fightSkill1, fightSkill2, passiveSkillList);
Fighter fighter6 = new Fighter(6L, 16, 6, 100, 1, properties, fightSkill1, fightSkill2, passiveSkillList);
ArrayList<Fighter> fighters = new ArrayList<>(Arrays.asList(fighter1, fighter2, fighter3, fighter4, fighter5, fighter6));
Camp camp = new Camp(fighters);
String json = SerializationUtils.jsonSerialize(camp);
Camp clone0 = (Camp)SerializationUtils.jsonDeserialize(json, Camp.class);
Camp clone1 = SerializationUtils.clone(camp);
Camp clone2 = SerializationUtils.clone2(camp);
System.out.println("camp:"+camp.hashCode()+"|" + camp);
System.out.println("clone0:"+clone0.hashCode()+"|" + clone0);
System.out.println("clone1:"+clone1.hashCode()+"|" + clone1);
System.out.println("clone2:"+clone2.hashCode()+"|" + clone2);
long befor0 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
String jsonStr = SerializationUtils.jsonSerialize(camp);
Camp clonex = (Camp)SerializationUtils.jsonDeserialize(jsonStr, Camp.class);
}
long after0 = System.currentTimeMillis();
System.out.println("Json 序列化深拷贝100000次耗时:"+(after0 - befor0));
long befor1 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
SerializationUtils.clone(camp);
}
long after1 = System.currentTimeMillis();
System.out.println("JDK 序列化深拷贝100000次耗时:"+(after1 - befor1));
long befor2 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
SerializationUtils.clone2(camp);
}
long after2 = System.currentTimeMillis();
System.out.println("Hessian 序列化深拷贝100000次耗时:"+(after2 - befor2));
}
}
这种情况要是采用第一种实现Clonable接口方法,就需手写很多东西,容易出错,所以最好采用第2,3种方法,现在看fastjson,JDK,Hessian2 序列化和反序列化的实现,如下:
SerializationUtils.java
/**
* 对象序列化工具
*
*/
public class SerializationUtils {
/**
* json序列化
*
* @param object
* @return
*/
public static <T> String jsonSerialize(T object) {
return JSON.toJSONString(object);
}
/**
* json反序列化
*
* @param text
* @param clazz
* @return
*/
public static <T> T jsonDeserialize(String text, Class<T> clazz) {
return JSON.parseObject(text, clazz);
}
/**
* jdk序列化
*
* @param obj
* @return
*/
public static <T extends Serializable> byte[] serialize(T obj) {
ByteArrayOutputStream baos = null;
ObjectOutputStream oos = null;
try {
baos = new ByteArrayOutputStream(1024);
oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
oos.flush();
byte[] array = baos.toByteArray();
return array;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
if (oos != null) {
oos.close();
}
if (baos != null) {
baos.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
/**
* jdk反序列化
*
* @param bytes
* @return
*/
public static <T extends Serializable> T deserialize(byte[] bytes) {
ByteArrayInputStream bais = null;
ObjectInputStream ois = null;
try {
bais = new ByteArrayInputStream(bytes);
ois = new ObjectInputStream(bais);
T obj = (T) ois.readObject();
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
if (bais != null) {
bais.close();
}
if (ois != null) {
ois.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
/**
* hessian2序列化
*
* @param obj
* @return
*/
public static <T extends Serializable> byte[] serialize2(T obj) {
ByteArrayOutputStream baos = null;
Hessian2Output h2o = null;
try {
baos = new ByteArrayOutputStream(1024);
h2o = new Hessian2Output(baos);
h2o.writeObject(obj);
h2o.flush();
byte[] array = baos.toByteArray();
return array;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
if (h2o != null) {
h2o.close();
}
if (baos != null) {
baos.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
/**
* hessian2反序列化
*
* @param bytes
* @return
*/
public static <T extends Serializable> T deserialize2(byte[] bytes) {
ByteArrayInputStream bais = null;
Hessian2Input h2i = null;
try {
bais = new ByteArrayInputStream(bytes);
h2i = new Hessian2Input(bais);
T obj = (T) h2i.readObject();
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
if (bais != null) {
bais.close();
}
if (h2i != null) {
h2i.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
/**
* jdk深度克隆对象,必须保证被克隆对象所有的字段全部实现了可序列化接口
*
* @param obj
* @return
*/
public static <T extends Serializable> T clone(T obj) {
return deserialize(serialize(obj));
}
/**
* hessian2深度克隆对象,必须保证被克隆对象所有的字段全部实现了可序列化接口
*
* @param obj
* @return
*/
public static <T extends Serializable> T clone2(T obj) {
return deserialize2(serialize2(obj));
}
}
最后得测试结果为:
三种序列化方式对比.png
可见,hessian2的序列化效率还是挺高的,游戏中可以采用这种方式序列化大对象。