今天在做项目的时候遇到了个问题,发票多打的时候想保存虚拟发票到数据库表中(钱在第一张发票中,但是由于数目过长要打印两张发票,所以第二张发票除了发票号码和金额为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 没有影响
本文详细解析了Java中使用Map进行深拷贝时,导致数据库唯一键冲突的原因,并通过源码分析揭示了HashMap内部实现机制。提供了解决方案,避免在数据库操作中出现此类问题。

2938

被折叠的 条评论
为什么被折叠?



