浅复制和深复制-以HashMap为例

2023-05-16

目录

1、简介

2、浅复制和深复制

2.1浅复制(shallow copy)

2.2深复制(deep copy)

3、常见实现方式

3.1【浅复制】

3.1.1使用HashMap的构造器

3.1.2使用clone()

3.1.3Map.put()

3.1.4Map.putAll()

3.1.5Java 8 Stream API

3.1.6Google Guava的copyOf

3.2【深复制】

3.2.1Apache Commons Lang【不建议使用】

3.2.2Json序列化和反序列化【一般推荐】

    Jackson:

             方式一:含参数TypeReference的方法

             方式二:使用含参数JavaType的方法

    Gson:需要使用泛型参数TypeToken

    fastjson:需要使用TypeReference指定泛型

3.2.3序列化和反序列化【推荐】

     以Protostuff为例:

3.3其它

4.总结


1、简介

探索HashMap的浅复制、浅拷贝(shallow copy)深复制、深拷贝(deep copy),以及各自的几种实现方式,包括JDK里的和引用第三方的。

2、浅复制和深复制

2.1浅复制(shallow copy)

浅复制的HashMap,是一个新的HashMap,拥有和原HashMap相同的key,相同的value

比如,我们创建一个员工类Employee:

public class Employee {
    private String name;

    private Address address;

    public String getName() {
        return name;
    }

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

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

}

一个Address类


public class Address {
    private String name;

    private String province;

    public String getName() {
        return name;
    }

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

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }
}

测试:

import org.junit.Test;

import java.util.HashMap;

public class TestHashMapCopy {
    private HashMap<String , Employee> originalMap= new HashMap();

    @Test
    public void testShallowCopy() {
        Employee emp1 = new Employee();
        emp1.setName("刘亦菲");
        originalMap.put("emp1", emp1);

        Employee emp2 = new Employee();
        emp2.setName("范冰冰");
        originalMap.put("emp2",emp2);

        HashMap<String ,Employee> shallowCopy = new HashMap<>(originalMap);
        assert shallowCopy != orignalMap;

//        System.out.println("orignalMap beforeChange" + JacksonJsonUtil.obj2JsonString(orignalMap));
//        System.out.println("shallowCopy beforeChange" + JacksonJsonUtil.obj2JsonString(shallowCopy));
        emp1.setName("关晓彤");
        Employee shallowCopyEmp2 = shallowCopy.get("emp2");
        shallowCopyEmp2.setName("emp2改名字");
//        System.out.println("orignalMap afterChange" + JacksonJsonUtil.obj2JsonString(orignalMap));
//        System.out.println("shallowCopy afterChange" + JacksonJsonUtil.obj2JsonString(shallowCopy));
        // assert结果是true,表示浅复制map里的emp1和原map里的emp1是同一个对象,
        // 无论是在浅复制map里更改emp1的属性,还是在原map里更改emp1的属性,对方都会跟着更改
        assert (shallowCopy.get("emp1")).equals(orignalMap.get("emp1"));
    }
}

测试结果:

浅复制,原map和复制map不是同一个对象,但是map里的value如果是引用类型的话,那么该引用类型是同一个对象,比如测试例子中的emp1.

更改了emp1里的属性之后,原map和浅复制map里的emp1属性都变了,会相互影响

2.2深复制(deep copy)

深复制的HashMap,是一个新的HashMap,并且深复制了所有的属性。它为所有的key、value、mappings创建了新的对象,

因此原map和深复制map更改了值之后,相互不影响

深复制的实现,比如Apache Commons Lang,但是要求map的所有key和value是实现了Serializable接口。

 

3、常见实现方式

3.1【浅复制】

3.1.1使用HashMap的构造器

HashMap(Map<? extends K,? extends V> m) :

HashMap<String ,Employee> shallowCopy = new HashMap<>(originalMap);

3.1.2使用clone()

HashMap<String ,Employee> shallowCopy = (HashMap<String, Employee>) originalMap.clone();

3.1.3Map.put()

HashMap<String ,Employee> shallowCopy = new HashMap<String , Employee>();
        Set<Map.Entry<String, Employee>> entries = originalMap.entrySet();
        for(Map.Entry<String, Employee> entry: entries) {
            shallowCopy.put(entry.getKey(), entry.getValue());
        }

3.1.4Map.putAll()

HashMap<String ,Employee> shallowCopy = new HashMap<String , Employee>();
        shallowCopy.putAll(originalMap);

3.1.5Java 8 Stream API

HashMap<String, Employee> shallowCopy = (HashMap<String, Employee>) originalMap.entrySet().stream().collect(
                // 阿里开发手册里集合处理第3条有写toMap的规范:要使用带有BinaryOperator的方法
                // Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue
                Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v2
                ));

3.1.6Google Guava的copyOf

Map<String, Employee> shallowCopy = ImmutableMap.copyOf(originalMap);

3.2【深复制】

主要是序列化、反序列化方式

3.2.1Apache Commons Lang【不建议使用】

但是要求map的所有key和value都实现了Serializable接口,包括对象里的对象属性也需要

public class Employee implements Serializable {
public class Address implements Serializable {
HashMap<String, Employee> deepCopy = SerializationUtils.clone(originalMap);

3.2.2Json序列化和反序列化【一般推荐】

常见的序列化工具,Jackson、Gson、fastjson等,但是要注意反序列化为Map的时候,需要指定对应的泛型

  •     Jackson:

可以使用Jackson的工具类,封装下列的方法

             方式一:含参数TypeReference的方法

        ObjectMapper objectMapper = new ObjectMapper();
        String originalJsonStr = objectMapper.writeValueAsString(originalMap);
        HashMap<String, Employee> deepCopy = new HashMap<>();
        deepCopy = objectMapper.readValue(originalJsonStr, new TypeReference<HashMap<String, Employee>>() {
        });

             方式二:使用含参数JavaType的方法


        ObjectMapper objectMapper = new ObjectMapper();
        String originalJsonStr = objectMapper.writeValueAsString(originalMap);
        JavaType javaType = objectMapper.getTypeFactory().constructParametricType(HashMap.class, String.class, Employee.class);
        HashMap<String, Employee> deepCopy = objectMapper.readValue(originalJsonStr, javaType);
  •     Gson:需要使用泛型参数TypeToken

        // Gson序列化,需要使用泛型参数TypeToken
        Gson gson = new Gson();
        String json = gson.toJson(originalMap);
        HashMap<String, Employee> deepCopy = new HashMap<>();
        // 注意,gson的jsonStr转集合,需要使用泛型参数TypeToken,才能准确获取到需要的对象
        deepCopy = gson.fromJson(json, new TypeToken<HashMap<String, Employee>>(){}.getType());
  •     fastjson:需要使用TypeReference指定泛型

        // fastJson序列化,注意要使用TypeReference指定泛型
        String originalJsonStr = JSON.toJSONString(originalMap);
        HashMap<String, Employee> deepCopy = JSON.parseObject(originalJsonStr,new TypeReference<HashMap<String, Employee>>(){});

3.2.3序列化和反序列化【推荐】

常用的序列化框架:

JDK Serializable、FST、Kryo、Protobuf、Thrift、Hession和Avro、Protostuff,3.7.2里提到的也是,只是是序列化成json结构

序列化框架的详情可以看下另一篇博客【待发布】

     以Protostuff为例:

因为Protostuff常规上只支持序列化、反序列化Bean对象,不支持List、Map,因此这里建了一个包装类Wrapper:

注:List、Map,也可以使用MessageCollectionSchema,参考:How do I serialize list or map collection objects? · Issue #233 · protostuff/protostuff · GitHub

public class SerializeWrapper {

	private Object obj = null;

	public Object getObj() {
		return obj;
	}

	public void setObj(Object obj) {
		this.obj = obj;
	}
}

        Schema<SerializeWrapper> schema = RuntimeSchema.getSchema(SerializeWrapper.class);
        SerializeWrapper SerializeWrapper = new SerializeWrapper();
        SerializeWrapper.setObj(originalMap);

        LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
        final byte[] protostuff;
        try{
            protostuff = ProtostuffIOUtil.toByteArray(SerializeWrapper,schema,buffer);
        } finally {
            buffer.clear();
        }
        ProtostuffIOUtil.mergeFrom(protostuff, SerializeWrapper, schema);
        HashMap<String, Employee> deepCopy = (HashMap<String, Employee>) SerializeWrapper.getObj();

3.3其它

还有其它的框架可以实现,比如HuTool等

4.总结

浅复制的话,上面的浅复制方法都可以选择;深复制的话,推荐使用Protostuff等序列化工具,最好不需要对象实现序列化接口,或者需要转换成json格式。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

浅复制和深复制-以HashMap为例 的相关文章

  • 如何使用stdext::hash_map?

    我想看一个如何正确重写 stdext hash compare 的简单示例 以便为我自己的用户定义类型定义新的哈希函数和比较运算符 我正在使用 Visual C 2008 这就是你可以做到的 class MyClass Hasher con
  • 检查和删除 Java HashMap 中的元素

    我正在尝试使用 Java 中的 HashMap 检查并删除元素 它的键是我创建的称为 ClusterKey 的类型 它的值是我创建的称为 ClusterValue 的类型 这是导致问题的代码 ClusterKey ck new Cluste
  • 同构弦

    给定两个字符串 s 和 t 确定它们是否同构 如果 s 中的字符可以替换得到 t 则两个字符串是同构的 所有出现的字符都必须替换为另一个字符 同时保留字符的顺序 任何两个字符都不能映射到同一个字符 但一个字符可以映射到其自身 例如 给定 e
  • Java 中 null 对象的 hashcode 必须是什么?

    根据此评论post https stackoverflow com questions 11184041 how is hashcode calculated for null object hascode of null objects
  • 当我使用computeIfAbsent计算斐波那契数时,hashmap size()返回错误的值

    我有以下代码 import java math BigInteger import java util HashMap import java util Map public class DynamicFib private static
  • 根据键名从 HashMap 获取字符串值

    我有一个HashMap有各种键和值 我怎样才能得到一个值 我在地图上有一把钥匙叫my code 它应该包含一个字符串 我怎样才能得到它而不必遍历地图 到目前为止我已经 HashMap newMap new HashMap paramMap
  • 当客户端读取 HashMap 时如何刷新 HashMap

    我有一个静电HashMap在服务器启动时初始化 客户端在登录时从该地图初始化其数据 现在我需要刷新这张地图 但是客户端可以同时登录并从这张地图中获取数据 当他们阅读时 我可以更改如下所示的地图参考吗 我不能使用synchronized因为它
  • Java HashMap.clear()和remove()内存有效吗?

    考虑以下HashMap clear code Removes all of the mappings from this map The map will be empty after this call returns public vo
  • 为什么Java中的Set数据结构内部使用Map?

    我想知道为什么HashSet http www docjar com html api java util HashSet java html uses HashMap TreeSet uses TreeMap and LinkedHash
  • 最坏情况时间复杂度 put/get HashMap

    当 Hashmap 的键的哈希码始终相等时 它的最坏情况时间复杂度是多少 根据我的理解 由于每个键都有相同的哈希码 因此它总是会进入同一个存储桶并循环遍历它以检查 equals 方法 因此对于 get 和 put 时间复杂度应该是 O n
  • unordered_map线程安全

    我正在使用 boost thread 库将单线程程序更改为多线程程序 该程序使用 unordered map 作为 hasp map 进行查找 我的问题是 某一时刻 许多线程将进行写入 而另一时刻 许多线程将进行读取 但不会同时进行读取和写
  • Java、HashMap 和使用字符串作为键 - 字符串值是否会存储两次?

    如果我有一个如下所示的 HashMap HashMap
  • 如何按整数值对哈希图进行排序[重复]

    这个问题在这里已经有答案了 HashMap
  • c++ pthread - 如何使地图访问线程安全?

    我有一个映射作为成员变量和多个访问该映射的线程 读写访问 现在我必须确保只有一个线程可以访问该地图 但我该如何点呢 最好的解决方案是什么 Boost 包含一些用于共享访问的很好的锁实现 看看文档 http www boost org doc
  • 按顺序范围循环映射

    我正在寻找一种确定的方法来范围Go map为了 Go 规范 https golang org ref spec For statements陈述如下 映射的迭代顺序未指定 并且不保证从一次迭代到下一次迭代的顺序相同 如果在迭代过程中删除尚未
  • 如何将 HashMap> 存储在列表中?

    我的哈希图将字符串存储为键 将数组列表存储为值 现在 我需要将其嵌入到列表中 也就是说 它将采用以下形式 List
  • 同步不经常更新的哈希图的最佳方式

    我有一个在应用程序中使用的 HashMap 数据是在应用程序初始加载期间从数据库填充的 然后它始终只是读取并且从不更新 会有多个线程不断地读取数据 由于数据永远不会更新 因此我们目前不使用任何同步 仅使用 HashMap 我们现在定义的方式
  • Java 中的 HashMap 和 Map 对象有什么区别?

    我创建的以下地图之间有什么区别 在另一个问题中 人们似乎可以互换使用它们来回答 我想知道它们是否 如何不同 HashMap
  • ConcurrentHashMap 内部是如何工作的?

    我正在阅读有关 Java 并发性的 Oracle 官方文档 我想知道Collection由返回 public static
  • HashMap 值需要不可变吗?

    我知道 HashMap 中的键需要是不可变的 或者至少确保它们的哈希码 hashCode 不会改变或与另一个具有不同状态的对象发生冲突 但是 HashMap中存储的值是否需要与上面相同 为什么或者为什么不 这个想法是能够改变值 例如在其上调

随机推荐