繁体   English   中英

字符串 IdentityHashMap 与 HashMap 性能

[英]String IdentityHashMap vs HashMap performance

Identity HashMap 是 Java 中的特殊实现,它比较对象引用而不是equals()并且还使用identityHashCode()代替hashCode() 此外,它使用linear-probe hash table而不是Entry list

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

这是否意味着如果调整正确,对于String键, IdentifyHashMap通常会更快?

看这个例子:

public class Dictionary {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader("/usr/share/dict/words"));

        String line;
        ArrayList<String> list = new ArrayList<String>();

        while ((line = br.readLine()) != null) {
            list.add(line);
        }
        System.out.println("list.size() = " + list.size());
        Map<String, Integer> iMap = new IdentityHashMap<>(list.size());
        Map<String, Integer> hashMap = new HashMap<>(list.size());

        long iMapTime = 0, hashMapTime = 0;

        long time;
        for (int i = 0; i < list.size(); i++) {
            time = System.currentTimeMillis();
            iMap.put(list.get(i), i);
            time = System.currentTimeMillis() - time;
            iMapTime += time;
            time = System.currentTimeMillis();
            hashMap.put(list.get(i), i);
            time = System.currentTimeMillis() - time;
            hashMapTime += time;
        }

        System.out.println("iMapTime = " + iMapTime + " hashMapTime = " + hashMapTime);
    }

}

尝试了非常基本的性能检查。 我正在阅读字典单词(235K)并推入这两个地图。 它打印:

list.size() = 235886
iMapTime = 101 hashMapTime = 617 

我认为这是一个很好的改进,可以忽略,除非我在这里做错了。

您将在 IdentityHashMap 上看到明显更快的性能,但是这需要付出巨大的代价。

您必须绝对确保永远不会将具有相同值但不同身份的对象添加到地图中。

现在和未来都很难保证这一点,而且很多人做出了错误的假设。

例如

String t1 = "test";
String t2 = "test";

t1==t2将返回真。

String t1 = "test";
String t2 = new String("test");

t1==t2将返回 false。

总的来说,我的建议是,除非您绝对需要提高性能并确切地知道自己在做什么并严重锁定和评论对类的访问,否则通过使用 IdentityHashMap,您将面临难以追踪错误的巨大风险未来。

IdentityHashMap<String,?>工作?

要使IdentityHashMap<String,?>为任意字符串工作,您必须将您put()的键和传递给get()潜在键都设置为String.intern() get() (或使用等效机制。)

注意:与@m3th0dman 的回答中所述不同,您不需要intern()值。

无论哪种方式,实习字符串最终都需要在某种已经实习字符串的哈希表中查找它。 因此,除非您出于某种其他原因不得不实习字符串(因此已经支付了费用),否则您不会从中获得多少实际的性能提升。

那么为什么测试表明你可以呢?

您的测试不切实际的地方在于,您保留与put()一起使用的键的确切列表,并按列表顺序逐一遍历它们。 注意(同样可以通过将元素插入LinkedHashMap并在其条目集上简单地调用iterator()来实现。

那么IdentityHashMap什么意义呢?

在某些情况下,可以保证(或实际上保证)对象标识与equals()相同。 想象一下,例如尝试实现您自己的ThreadLocal类,您可能会编写如下内容:

public final class ThreadLocal<T> {
   private final IdentityHashMap<Thread,T> valueMap;
   ...
   public T get() {
       return valueMap.get( Thread.currentThread() );
   }
}

因为你知道线程除了身份之外没有平等的概念。 如果您的地图键是枚举值等,情况也是如此。

从技术上讲,您可以执行以下操作以确保您拥有相同的字符串表示实例:

public class StringIdentityHashMap extends IdentityHashMap<String, String>
{
    @Override
    public String put(String key, String value)
    {
        return super.put(key.intern(), value.intern());
    }

    @Override
    public void putAll(Map<? extends String, ? extends String> m)
    {
        m.entrySet().forEach(entry -> put(entry.getKey().intern(), entry.getValue().intern()));
    }

    @Override 
    public String get(Object key)
    {
        if (!(key instanceof String)) {
            throw new IllegalArgumentException();
        }
        return super.get(((String) key).intern());
    }

    //implement the rest of the methods in the same way
}

但这对您没有多大帮助,因为intern()调用equals()以确保给定的String存在于 String 池中,因此您最终会获得典型HashMap的性能。

但是,这只会帮助您改善内存而不是 CPU。 没有办法获得更好的 CPU 使用率并确保您的程序是正确的(不可能使用一些可能会改变的 JVM 内部知识),因为字符串可以在字符串池中或不在字符串池中,并且您无法知道它们是否在没有(不隐式)调用equals()

有趣的是,IdentityHashMap 可能会更慢。 我使用 Class 对象作为键,并且使用 HashMap 比 IdentityHashMap 提高了约 50% 的性能。

IdentityHashMap 和 HashMap 在内部是不同的,所以如果你的键的 equals() 方法真的很快,HashMap 似乎更好。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM