Map复制给新Map时,用 “=、clone、还是putAll”?论Map的深复制和浅复制

举报
_陈哈哈 发表于 2022/01/23 00:14:29 2022/01/23
【摘要】 目录 使用场景尝试过的办法1. “=”赋值2. 使用.putAll()方法3. 使用.clone()方法 测试用例测试用例源码 使用场景 在我们最初使用map复制开发业务代码时,通常会...

使用场景

在我们最初使用map复制开发业务代码时,通常会踩到深浅复制(拷贝)这个坑里,比如我,在Map复制时
(如:Map<String, String> new_Map = old_Map) 出现过以下两类问题:

1.使用Map<String, String> new_Map = old_Map 操作,当修改new_Map属性后,old_Map属性也跟着变了,但我并没有修改过old_Map
2.由于Map中的value值不仅有基本数据类型,还有引用数据类型,所以当我修改引用类型属性后,new_Map和old_Map的引用变量值都发生变化;(如你的value都是基本类型,就不涉及深浅拷贝的问题)

尝试过的办法

1. “=”赋值

新建一个Map,然后使用“=”直接赋值,这样只是复制了old_Map的引用,和old_Map仍使用同一个内存区域,所以,在修改new_Map的时候,old_Map的值同样会发生变化。

<Map<String, String> new_Map = old_Map>

  
 
  • 1

上述的办法不行,使用Map本身提供的方法,网上大都说putAll()和clone()方法就是深拷贝,但是实际使用后,发现前后Map中的引用对象还是都被改变了;这里就是开头说到的,这两个方法只能修改基本数据类型的,如果是引用类型不行,这两个方法是浅拷贝!

来,让我们一起跟一下源码↓↓↓

2. 使用.putAll()方法

创建一个新的Map结构,使用putAll()方法把原先的Map添加到新的Map中,但是发现修改了副本的Map之后,原先的Map中数据也被修改了;(源码如下)

	public void putAll(Map<? extends K, ? extends V> m) {
        putMapEntries(m, true); // 调用了putMapEntries方法
    }
	
	final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        int s = m.size();
        if (s > 0) {
            if (table == null) { // pre-size
                float ft = ((float)s / loadFactor) + 1.0F;
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
                if (t > threshold)
                    threshold = tableSizeFor(t);
            }
            else if (s > threshold)
                resize();
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                putVal(hash(key), key, value, false, evict); // 循环调用了value,但value中的引用对象指针并没有改变。
                // 扩展:map.put("key","value")的put()也是调用了putVal()方法
            }
        }
    }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

3. 使用.clone()方法

HashMap自带了一个clone()方法,但是,它的源码中注释说明了也只是一种浅复制(拷贝):(源码如下)

	@Override
    public Object clone() {
        HashMap<K,V> result;
        try {
            result = (HashMap<K,V>)super.clone();
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
        result.reinitialize(); // 清空map
        result.putMapEntries(this, false);  // 可见,和putAll调用了同一个接口,
        return result;
    }
	
	
	    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        int s = m.size();
        if (s > 0) {
            if (table == null) { // pre-size
                float ft = ((float)s / loadFactor) + 1.0F;
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
                if (t > threshold)
                    threshold = tableSizeFor(t);
            }
            else if (s > threshold)
                resize();
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                putVal(hash(key), key, value, false, evict); // 同上,循环调用了“value”,value中的引用对象指针并没有改变
            }
        }
    }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

测试用例

List<Integer> list = new ArrayList<Integer>();
list.add(100);
list.add(200);

HashMap<String,Object> old_map = new HashMap<String,Object>();
old_map.put("name", "蔡虚坤");//放基本类型数据
old_map.put("list", list);//放对象

HashMap<String,Object> new_map = new HashMap<String,Object>();

new_map.putAll(old_map);
System.out.println("----基础数据展示-----");
System.out.println("old: " + old_map);
System.out.println("new: " + new_map);
System.out.println("----更改基本数据类型的数据-----");
old_map.put("name", "娘炮");
System.out.println("old: " + old_map);
System.out.println("new: " + new_map);
System.out.println("----更改引用类型的数据-----");
list.add(300);
System.out.println("old: " + old_map);
System.out.println("new: " + new_map);
System.out.println("----使用序列化进行深拷贝 自定义Clone方法-----");
new_map = myClone(old_map); // myClone() 方法源码在下方 ↓↓
list.add(400);
System.out.println("old: " + old_map);
System.out.println("new: " + new_map);

输出结果:
Connected to the target VM, address: '127.0.0.1:58242', transport: 'socket'
----基础数据展示-----
old: {name=蔡虚坤, list=[100, 200]}
new: {name=蔡虚坤, list=[100, 200]}
----更改基本数据类型的数据-----
old: {name=娘炮, list=[100, 200]}
new: {name=蔡虚坤, list=[100, 200]}
----更改引用类型的数据-----
old: {name=娘炮, list=[100, 200, 300]}
new: {name=蔡虚坤, list=[100, 200, 300]}
----使用序列化进行深拷贝-----
old: {name=娘炮, list=[100, 200, 300, 400]}
new: {name=娘炮, list=[100, 200, 300]}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

#最上面的两条是原始数据,使用了putAll方法拷贝了一个新的new_map对象,
#中间两条,是修改old_map对象的基本数据类型的时候,并没有影响到new_map对象。
#但是看倒数第二组,更改引用数据类型的时候,发现new_map的值也变化了,所以putAll并没有对old_map产生深拷贝。
#最后面是使用序列化的方式,发现,更改引用类型的数据的时候,new_map对象并没有发生变化,所以产生了深拷贝。(下方提供自定义clone方法源码)
#上述的工具类,可以实现对象的深拷贝,不仅限于HashMap,前提是实现了Serlizeable接口。

测试用例源码

package com.softsec.demo;

import java.io.*;
import java.util.*;

public class demoMap implements Cloneable{

    public static void main(String[] srag) {
        List<Integer> list = new ArrayList<Integer>();
        list.add(100);
        list.add(200);

        HashMap<String,Object> old_map = new HashMap<String,Object>();
        old_map.put("name", "蔡虚坤");//放基本类型数据
        old_map.put("list", list);//放对象

        HashMap<String,Object> new_map = new HashMap<String,Object>();

        new_map.putAll(old_map);
        System.out.println("----基础数据展示-----");
        System.out.println("old:" + old_map);
        System.out.println("new:" + new_map);
        System.out.println("----更改基本数据类型的数据-----");
        old_map.put("name", "娘炮");
        System.out.println("old:" + old_map);
        System.out.println("new:" + new_map);
        System.out.println("----更改引用类型的数据-----");
        list.add(300);
        System.out.println("old:" + old_map);
        System.out.println("new:" + new_map);
        System.out.println("----使用序列化进行深拷贝 自定义Clone方法-----");
        new_map = myClone(old_map);
        list.add(400);
        System.out.println("old:" + old_map);
        System.out.println("new:" + new_map);
    }

    /**
     * 自定义clone方法(对象必须是实现了Serializable接口)
     *
     */
    public static <T extends Serializable> T myClone(T obj) {
        T clonedObj = null;
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(obj);
            oos.close();

            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            clonedObj = (T) ois.readObject();
            ois.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return clonedObj;
    }


}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

文章来源: chensj.blog.csdn.net,作者:_陈哈哈,版权归原作者所有,如需转载,请联系作者。

原文链接:chensj.blog.csdn.net/article/details/102717884

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。