Map的赋值和putAll的区别

本文详细解析了Java中使用Map进行深拷贝时,导致数据库唯一键冲突的原因,并通过源码分析揭示了HashMap内部实现机制。提供了解决方案,避免在数据库操作中出现此类问题。

今天在做项目的时候遇到了个问题,发票多打的时候想保存虚拟发票到数据库表中(钱在第一张发票中,但是由于数目过长要打印两张发票,所以第二张发票除了发票号码和金额为0外其他都相同)。想复制Map的时候会把需要复制的HashMap 的value值覆盖掉,导致保存到数据库中时候报违反唯一键的错误。FPHM是主键,代码已简化
看代码:

Map<String, Object> MS_MZXX = new HashMap<String, Object>();
MS_MZXX.put("FPHM", "2278305428");
System.out.println("hashcode:"+MS_MZXX.hashCode());
 saveMZXX(MZXX);//保存到数据库中
Map<String, Object> MS_MZXX1 = new HashMap();
  //看这里赋值
MS_MZXX1=MS_MZXX; //①
MS_MZXX1.put("FPHM", "2278305431");
saveMZXX(MZXX1)  //这步保存的时候会报错
//事务提交
session.getTransaction().commit();
通过跟踪打印可知,在 修改FPHM的时候把第一张发票的FPHM也改掉,导致保存时出错
System.out.println("after:"+MS_MZXX.hashcode());
System.out.println(MS_MZXX);
System.out.println(MS_MZXX1);

out:
hashcode:-384379348
after:-2014500392
mzxx:{FPHM=2278305431}
MS_MZXX1:{FPHM=2278305431}

由于代码数据量大 报错不会报在指定位置。排查了好久才发现
MS_MZXX1=MS_MZXX 这只是把MS_MZXX1和指向MS_MZXX同一个内存空间
在改动MS_MZXX1 时候其实是改动指针所指向内存块(或值)。HashCode不是内存地址,是根据排列算出来的一个值,相当于对象的身份证,但绝不是内存地址,大家一定要注意这个误区。但是从这可以看出,MS_MZXX对象已经不是以前的那个了

如果把①替换成

MS_MZXX1.putAll(MS_MZXX)

结果变成:
out:
hashcode:-384379348
after:-384379348
mzxx:{FPHM=2278305428}
MS_MZXX1:{FPHM=2278305431}
为什么呢?看一下Java提供的源代码和解释:

/**
     * Copies all of the mappings from the specified map to this map.
     * These mappings will replace any mappings that this map had for
     * any of the keys currently in the specified map.
     *
     * @param m mappings to be stored in this map
     * @throws NullPointerException if the specified map is null
     */
    public void putAll(Map<? extends K, ? extends V> m) {
        int numKeysToBeAdded = m.size();
        if (numKeysToBeAdded == 0)
            return;

        if (table == EMPTY_TABLE) {
            inflateTable((int) Math.max(numKeysToBeAdded * loadFactor, threshold));
        }

        /*
         * Expand the map if the map if the number of mappings to be added
         * is greater than or equal to threshold.  This is conservative; the
         * obvious condition is (m.size() + size) >= threshold, but this
         * condition could result in a map with twice the appropriate capacity,
         * if the keys to be added overlap with the keys already in this map.
         * By using the conservative calculation, we subject ourself
         * to at most one extra resize.
         */
        if (numKeysToBeAdded > threshold) {
            int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
            if (targetCapacity > MAXIMUM_CAPACITY)
                targetCapacity = MAXIMUM_CAPACITY;
            int newCapacity = table.length;
            while (newCapacity < targetCapacity)
                newCapacity <<= 1;
            if (newCapacity > table.length)
                resize(newCapacity);
        }

        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            ***put(e.getKey(), e.getValue());***
    }

看一下put方法:

/**
     * Associates the specified value with the specified key in this map.
     * If the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     *         (A <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with <tt>key</tt>.)
     */
    public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

再往下,看一下hash 键的生成:

/**
     * Retrieve object hash code and applies a supplemental hash function to the
     * result hash, which defends against poor quality hash functions.  This is
     * critical because HashMap uses power-of-two length hash tables, that
     * otherwise encounter collisions for hashCodes that do not differ
     * in lower bits. Note: Null keys always map to hash 0, thus index 0.
     */
    final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

注意这句话:if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
会重新生成一个hash值,在addEntry(hash, key, value, i); 时候会新建一个实体类也就是在内存空间中重新分配一个内存 此时MS_MZXX1就会指向这个内存空间了。
对原来的hashMap 没有影响

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值