Java 在HashMap中访问隐藏的getEntry(对象键)

Java 在HashMap中访问隐藏的getEntry(对象键),java,map,Java,Map,我有一个和前面讨论过的问题类似的问题,但实际应用性更强 例如,我有一个映射,我有一个函数,给它一个键,如果映射的整数值为负数,则将NULL放入映射: Map<String, Integer> map = new HashMap<String, Integer>(); public void nullifyIfNegative(String key) { Integer value = map.get(key); if (value != null &a

我有一个和前面讨论过的问题类似的问题,但实际应用性更强

例如,我有一个
映射
,我有一个函数,给它一个键,如果映射的整数值为负数,则将
NULL
放入映射:

Map<String, Integer> map = new HashMap<String, Integer>();

public void nullifyIfNegative(String key) {
    Integer value = map.get(key);

    if (value != null && value.intValue() < 0) {
        map.put(key, null);
    }
}
当您想要操纵不可变对象(可以是映射值)时,同样的情况也会发生:

  • Map
    :我想在字符串值中添加一些内容
  • Map
    :我想在数组中插入一个数字
所以这种情况很普遍。解决方案,可能有效,但不适用于我:

  • 反思。很好,但是我不能为了这个好的特性而牺牲性能
  • 使用(它至少有
    受保护的getEntry()
    方法),但不幸的是,commons集合不支持泛型
  • 使用,但此库(AFAIK)已过时(与Apache的最新库版本不同步),并且(关键的)在central maven存储库中不可用
  • 使用值包装器,这意味着“使值可变”(例如,使用可变整数[e.g.
    org.apache.commons.lang.mutable.MutableInt
    ],或者使用集合而不是数组)。这种解决方案会导致内存丢失,我希望避免这种情况
  • 尝试使用自定义类实现扩展
    java.util.HashMap
    (应该在
    java.util
    包中)并将其放入(因为
    java.lang.ClassLoader
    将拒绝将其加载到
    类定义类(字符串名,字节[]b,int off,int len)
    ,请参阅源代码),但是我不想给JDK打补丁,而且似乎可以支持的包列表中没有
    java.util
类似的问题已经提出,但我想知道,社区的意见是什么,以及考虑到最大的记忆和性能效率,可以采取什么样的解决方法


如果你同意,这是很好的和受益的功能,请投票这个错误

从逻辑上讲,您是对的,单个getEntry将为您节省一次哈希查找。作为一个实际问题,除非您有一个特定的用例,您有理由担心性能影响(这似乎不太可能,散列查找很常见,O(1),并且经过了很好的优化),否则您担心的可能是微不足道的

你为什么不写一个测试呢?创建一个包含数千万个对象的哈希表,或者比应用程序可能创建的对象大一个数量级的任何对象,并在大约一百万次迭代中平均get()的时间(提示:这将是一个非常小的数字)


您所做的一个更大的问题是同步。您应该知道,如果对映射进行条件更改,即使使用的是同步映射,也可能会遇到问题,因为您必须锁定对覆盖get()和set()操作范围的密钥的访问

不太好,但可以使用轻量级对象保存对实际值的引用,以避免第二次查找

HashMap<String, String[]> map = ...;

// append value to the current value of key
String key = "key";
String value = "value";

// I use an array to hold a reference - even uglier than the whole idea itself ;)
String[] ref = new String[1]; // lightweigt object
String[] prev = map.put(key, ref);
ref[0] = (prev != null) ? prev[0] + value : value;
HashMap=。。。;
//将值附加到键的当前值
字符串key=“key”;
String value=“value”;
//我使用数组来保存引用-甚至比整个想法本身更丑陋;)
String[]ref=新字符串[1];//lightweigt对象
字符串[]prev=map.put(key,ref);
参考[0]=(上一个!=空)?上一个[0]+值:值;

不过,我不会太担心散列查找性能(很好地指出了原因)。特别是对于字符串键,我不会太担心
hashCode()
,因为它的结果是缓存的。您可能会担心
equals()
,因为每次查找可能会多次调用它。但对于短字符串(通常用作键),这也可以忽略不计。

此方案不会带来性能提升,因为平均情况下Map的性能为O(1)。但在这种情况下启用对原始条目的访问将引发另一个问题。可以更改输入项中的键(即使只能通过反射),从而打破内部数组的顺序。

@sfussenegger感谢您的回答,但创建对象包装对我来说不是一个好选择。我希望在内存影响最小的情况下实现最大性能。@dma_k如果我没记错的话,长度为1的数组对内存的影响是4字节(而包装器对象对每个实例的影响是12字节)。因此(ab)使用这样的数组与使用C语言中的指针几乎是一样的——而且你不会认为指针效率低下,对吗?;)我希望数组的大小为
4[=length]+4[=int\u size]*length(数组)+8字节对齐
(我可能错了)。因此,
int[1]
将分配8个字节,
int[2]
-16字节:)@dma_k显然,我没有正确地记住-4*长度是一个很好的经验法则,对于这种数组来说非常失败:)实际上字符串[1]是16字节(8字节对象头,4字节长度,1*4字节内容)。由于包装器对象需要相同的功能,因此它将是更好的(自然的)选择。我仍然不认为这是一个主要的内存影响-最多是一个小的性能权衡;)+感谢你的努力。我在这里找到了答案:@al wolf Well,Map.Entry没有
setKey()
,所以你没有办法破坏东西。@Steve谢谢你的答案,但我需要真正的突破性答案来将我的问题标记为已回答:)没有太大的需求来编写测试,因为在非常大的地图上会有一些损失(这就是我的情况)@Steve关于同步问题,我同意你的看法。在我的案例中,我无法从多个线程访问此映射。是的,当另一个线程在您
get
值之后删除了条目时,“经典”get/put方法存在同步问题。然后,当前线程将转世该值,而不知道有人已将其删除。因此,使用
g的方法
HashMap<String, String[]> map = ...;

// append value to the current value of key
String key = "key";
String value = "value";

// I use an array to hold a reference - even uglier than the whole idea itself ;)
String[] ref = new String[1]; // lightweigt object
String[] prev = map.put(key, ref);
ref[0] = (prev != null) ? prev[0] + value : value;