关于Java中的HashMap的深浅拷贝的测试与几点思考
0、前言
工作忙起来后,许久不看算法,竟然DFA敏感词算法都要看好一阵才能理解。。。真是和三阶魔方还原手法一样,田园将芜,非常可惜啊。
在DFA算法中,第一步是需要理解它的数据结构,在此基础上,涉及到一些Hashmap的赋值。这里的赋值非常有趣,三个Hashmap翻来覆去赋值,就解决了敏感词表的初始化。
里面都是属于下文中的Hashmap“浅拷贝”,那么究竟Java中的Hashmap有哪些拷贝方法呢?
1、测试代码
HashMap hm_source = new HashMap(); HashMap hm_clone = new HashMap(); hm_source.put("1", "1"); // hashmap deep clone method 1 hm_clone = (HashMap)hm_source.clone(); // hashmap deep clone method 2 hm_clone.putAll(hm_source);// hashmap shadow clone // hm_b = hm_a; hm_source.put("2", "2"); System.out.println("hm_source增加元素后,hm_source:"+hm_source); System.out.println("hm_source增加元素后,hm_clone:"+hm_clone); System.out.println("是否指向同一内存地址:"+(hm_source==hm_clone)); System.out.println("第一个元素是否指向同一内存地址:"+(hm_source.get(1)==hm_clone.get(1)));
上面介绍了两种Hashmap深拷贝的方法,分别是hashmap.clone()和hashmap.putAll(hm_source),效果一样,输出如下:
hm_source增加元素后,hm_source:{1=1, 2=2} hm_source增加元素后,hm_clone:{1=1} 是否指向同一内存地址:false 第一个元素是否指向同一内存地址:true
那么浅拷贝呢?(代码中注释的那段,直接等号=赋值),输出如下:
hm_source增加元素后,hm_source:{1=1, 2=2} hm_source增加元素后,hm_clone:{1=1, 2=2} 是否指向同一内存地址:true 第一个元素是否指向同一内存地址:true
2、输出解析
不难发现,深浅拷贝确实如其名,
深拷贝:两个Hashmap对象似乎彻底无关,互相增加修改元素后,都不影响对方;
浅拷贝:两个Hashmap对象就是“软链接ln”,互相牵制,你改动了,我也跟着变。
3、上述猜想是否正确?
我党的思想路线是实事求是,预想剖析根本区别,大家可以看看JDK clone函数的源码。
但是从现象我们得到的初步结论有几点:
- 浅拷贝的两个对象使用的内存地址相同,深拷贝的对象地址“另立门户”;
- 深拷贝的两个对象也不完全“彻底无关”,仅仅是复制了元素的引用;
关于结论3.2,我也是在 HashMap的clone方法 博文中学习到了,里面使用的Hashmap元素是Bean类型的,深拷贝下的元素修改,也会“打断骨头连着筋”地让两个Hashmap同时更新。
但是仅限于“元素修改”,如若“元素增删”,那两个Hashmap对象就“翻脸不认人”了,不会同步更新。
4、hashmap.clone()
JDK是个难得的学习材料,源码还是要读的。现在也粘贴过来,做一些记录。
/** * Returns a shallow copy of this <tt>HashMap</tt> instance: the keys and * values themselves are not cloned. * 【咱们中文叫“深拷贝”,老外美其名曰“拷贝一份实例的'浅拷贝'”,更加严谨】 * @return a shallow copy of this map */ @SuppressWarnings("unchecked") @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(); result.putMapEntries(this, false); return result; }
/** * Implements Map.putAll and Map constructor * * @param m the map * @param evict false when initially constructing this map, else * true (relayed to method afterNodeInsertion). */ 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方法里面我初步扫了一下,也未涉及Hashmap instance对象的新建,是一些Hashmap结构中的Node的新建】 putVal(hash(key), key, value, false, evict); } } }
以代码最终解释权由JDK1.8.x所有。