Map复制给新Map时,用 “=、clone、还是putAll”?论Map的深复制和浅复制__陈哈哈的博客-程序员宝宝

技术标签: Map复制  clone  深浅拷贝  好奇心害死Java人  

使用场景

在我们最初使用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>

上述的办法不行,使用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()方法
            }
        }
    }

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中的引用对象指针并没有改变
            }
        }
    }

测试用例

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

#最上面的两条是原始数据,使用了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;
    }


}

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_39390545/article/details/102717884

智能推荐

csc_matrix稀疏矩阵理解_WangN2的博客-程序员宝宝_csc矩阵

csc_matrix稀疏矩阵描述参考源稀疏矩阵的常规方式csc_matrixcsr_matrix描述Apollo轨迹规划中,横向轨迹优化使用的OSQP的二次规划求解器,其中通过调用csc_matrix()进行构建矩阵,即稀疏矩阵。度娘:在矩阵中,若数值为0的元素数目远远多于非0元素的数目,并且非0元素分布没有规律时,则称该矩阵为稀疏矩阵;与之相反,若非0元素数目占大多数时,则称该矩阵为稠密矩阵。定义非零元素的总数比上矩阵所有元素的总数为矩阵的稠密度。参考源大神们的解释,丰富而精彩:scipy c

Javascript中最常用的55个经典技巧 _huoshen8211的博客-程序员宝宝

1. oncontextmenu="window.event.returnValue=false" 将彻底屏蔽鼠标右键no 可用于Table2. 取消选取、防止复制3. onpaste="return false" 不准粘贴4. oncopy="return false;" oncut="return false;" 防止复制5. IE地址栏前换成自己的图标6.

react聊天组件:用antd和react-chat-element组装的聊天列表_livingsu的博客-程序员宝宝_react 聊天组件

效果图:安装库用到了antd design和github上的一个库:react-chat-element(1)antd design:安装:yarn add antd修改 src/App.css,在文件顶部引入 antd/dist/[email protected] '~antd/dist/antd.css';即可使用antd的组件(2)react-chat-element:github网址为:Detaysoft/react-chat-elements安装:npm install rea

计算机应用基础东师,2015年秋季东师《计算机应用基础》期末考核_weixin_39680380的博客-程序员宝宝

2015年秋季东师《计算机应用基础》期末考核期末作业考核《计算机应用基础》包括本科的各校各科新学期复习资料,可以联系屏幕右上的“文档贡献者”满分 100分一、计算题(每题10分,共20分)1.一个文件大小为30G,这个文件为多少MB、KB、B?需多少张3寸1.44MB的高密软盘进行存储?2.将二进制数1001.01转换对应的十进制数。二、简答题(每题10分,共50分)1.“幻灯片放映”菜单中“排练...

shell中日期循环的方式_angshanglu6099的博客-程序员宝宝

第一种# 这里的例子以周为循环!/bin/bashbegin_date="20160907"end_date="20170226"while [ "$begin_date" -le "$end_date" ];do year=${begin_date:0:4} week_of_year=$(date -d "$begin_date" +%W) ec...

随便推点

C++ vector的reserve和resize区别_qq_20853741的博客-程序员宝宝

C++ vector的reserve和resize区别 vector 的reserve增加了vector的capacity,但是它的size没有改变!而resize改变了vector的capacity同时也增加了它的size!原因如下: reserve是容器预留空间,但在空间内不真正创建元素对象,所以在没有添加新的对象之前,不能引用容器内的元素。加入新的元素时,要调用push_back()/insert()函数。 resize是改变容器的大小,且在创建对象,因此,调...

2020_HUASOJ_圣诞杯赛题题解_小新不想起床的博客-程序员宝宝_huasoj

比赛链接:2020_HUAS_ACM_圣诞杯赛题A,D题 (huas_zq)A:直接暴力#include&lt;iostream&gt;using namespace std;const int N=10010; int a[N],b[N];//c++的全局变量会自动初始化为0int n; int main(){ cin&gt;&gt;n; for(int i = 1;i&lt;=n;++i) cin&gt;&gt;a[i]; for(int i =

VC++创建和调用dll+静态调用和动态调用_oshan2012的博客-程序员宝宝_vc静态调用dll

在工程应用中,动态链接库(dll)的重要性和灵活性是不言而喻的,这里将介绍一种最基本的创建和调用dll的方法,下面是使用VC++6.0的实现过程: 1. 创建dll  如上图所示,选择Win32 Dynamic-Link Library,创建名为dllTest的空工程;在该工程项下分别添加.cpp和.h文件(其名称分别为dllTest.cpp和dllTest.h),在.h项下添加代码:extern...

python 通过web.py实现get和post接口_PgZHJ的博客-程序员宝宝

本文用于自我查看,所以只是将代码和简单解释记录下来:#!/usr/bin/python#-*-coding:utf8-*-import webimport sys,osFILE={ &quot;test&quot;:&quot;./test.txt&quot;}class fileRead(object): def GET(self,name): return self.read...

毕业前写了20万行代码,让我从成为同学眼里的面霸_小傅哥的博客-程序员宝宝_二十万行代码

作者:小傅哥博客:https://bugstack.cn沉淀、分享、成长,让自己和他人都能有所收获!????一、前言20万行代码写完,毕业了找一份工作不是问题!刚一毕业因为找不到工作,就得报名去参加Java培训的大有人在。并不是说参加培训就不好,只不过以你现在这个毕业的时间点参加,就会显得特别匆忙。因为你的压力既来自于培训还需要花家里一笔不小的费用,也有同班同学已经找到一份不错的工作开始赚钱的比对。大学四年其实有足够的时间让你学会编程,也能从一个较长时间的学习中,知道自己适合不适合做程序员。

C语言中全局变量的定义与调用_SEU-RC的博客-程序员宝宝_c语言全局变量不能被子函数调用

1. 谭浩强 书中对‘全局变量’的定义    在函数内定义的变量是局部变量,而在函数外定义的变量叫做外部变量,外部变量是全局变量。全局变量可以为本文件中其他函数所共用它的有效范围从定义变量的位置开始到本源文件结束。    建立全局变量的作用是增加了函数间数据联系的渠道。2.  谭浩强 书中对‘全局变量’的声明    用extern声明:       如果外部变量不在文件的开头定

推荐文章

热门文章

相关标签