Map的keySet()和entrySet()的性能注意事项

时间:2021-12-25 19:36:54

All,

所有,

Can anyone please let me know exactly what are the performance issues between the 2? The site : CodeRanch provides a brief overview of the internal calls that would be needed when using keySet() and get(). But it would be great if anyone can provide exact details about the flow when keySet() and get() methods are used. This would help me understand the performance issues better.

任何人都可以让我知道2之间的性能问题究竟是什么?站点:CodeRanch简要概述了使用keySet()和get()时所需的内部调用。但是,如果有人能够在使用keySet()和get()方法时提供有关流的确切细节,那将会很棒。这有助于我更好地理解性能问题。

3 个解决方案

#1


64  

First of all, this depends entirely on which type of Map you're using. But since the JavaRanch thread talks about HashMap, I'll assume that that's the implementation you're referring to. And lets assume also that you're talking about the standard API implementation from Sun/Oracle.

首先,这完全取决于您使用的是哪种类型的地图。但是由于JavaRanch线程谈论HashMap,我将假设这是你所指的实现。我们还假设您正在讨论Sun / Oracle的标准API实现。

Secondly, if you're concerned about performance when iterating through your hash map, I suggest you have a look at LinkedHashMap. From the docs:

其次,如果您在迭代哈希映射时关注性能,我建议您查看LinkedHashMap。来自文档:

Iteration over the collection-views of a LinkedHashMap requires time proportional to the size of the map, regardless of its capacity. Iteration over a HashMap is likely to be more expensive, requiring time proportional to its capacity.

对LinkedHashMap的集合视图进行迭代需要与映射大小成比例的时间,无论其容量如何。对HashMap的迭代可能更昂贵,需要与其容量成比例的时间。

HashMap.entrySet()

The source-code for this implementation is available. The implementation basically just returns a new HashMap.EntrySet. A class which looks like this:

可以使用此实现的源代码。实现基本上只返回一个新的HashMap.EntrySet。一个看起来像这样的类:

private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
    public Iterator<Map.Entry<K,V>> iterator() {
        return newEntryIterator(); // returns a HashIterator...
    }
    // ...
}

and a HashIterator looks like

和HashIterator看起来像

private abstract class HashIterator<E> implements Iterator<E> {
    Entry<K,V> next;    // next entry to return
    int expectedModCount;   // For fast-fail
    int index;      // current slot
    Entry<K,V> current; // current entry

    HashIterator() {
        expectedModCount = modCount;
        if (size > 0) { // advance to first entry
            Entry[] t = table;
            while (index < t.length && (next = t[index++]) == null)
                ;
        }
    }

    final Entry<K,V> nextEntry() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        Entry<K,V> e = next;
        if (e == null)
            throw new NoSuchElementException();

        if ((next = e.next) == null) {
            Entry[] t = table;
            while (index < t.length && (next = t[index++]) == null)
                ;
        }
    current = e;
        return e;
    }

    // ...
}

So there you have it... Thats the code dictating what will happen when you iterate through an entrySet. It walks through the entire array which is as long as the maps capacity.

所以你有它...这就是代码,它规定了迭代entrySet时会发生什么。它遍历整个阵列,这与地图容量一样长。

HashMap.keySet() and .get()

Here you first need to get hold of the set of keys. This takes time proportional to the capacity of the map (as opposed to size for the LinkedHashMap). After this is done, you call get() once for each key. Sure, in the average case, with a good hashCode-implementation this takes constant time. However, it will inevitably require lots of .hashCode and .equals calls, which will obviously take more time than just doing a entry.value() call.

在这里,您首先需要掌握一组键。这需要时间与地图的容量成比例(与LinkedHashMap的大小相反)。完成此操作后,为每个键调用get()一次。当然,在一般情况下,使用一个好的hashCode实现,这需要恒定的时间。但是,它将不可避免地需要大量的.hashCode和.equals调用,这显然比仅仅执行entry.value()调用需要更多的时间。

#2


65  

The most common case where using entrySet is preferable over keySet is when you are iterating through all of the key/value pairs in a Map.

使用entrySet优先于keySet的最常见情况是迭代Map中的所有键/值对。

This is more efficient:

这更有效:

for (Map.Entry entry : map.entrySet()) {
    Object key = entry.getKey();
    Object value = entry.getValue();
}

than:

比:

for (Object key : map.keySet()) {
    Object value = map.get(key);
}

Because in the second case, for every key in the keySet the map.get() method is called, which - in the case of a HashMap - requires that the hashCode() and equals() methods of the key object be evaluated in order to find the associated value*. In the first case that extra work is eliminated.

因为在第二种情况下,对于keySet中的每个键,都会调用map.get()方法,在HashMap的情况下,需要按顺序计算key对象的hashCode()和equals()方法。找到相关的值*。在第一种情况下,额外的工作被消除。

Edit: This is even worse if you consider a TreeMap, where a call to get is O(log2(n)), i.e. the comparator for will may need to run log2(n) times (n = size of the Map) before finding the associated value.

编辑:如果你考虑一个TreeMap,更糟糕的是,对get的调用是O(log2(n)),即will的比较器可能需要运行log2(n)次(n = Map的大小)才能找到相关的价值。

*Some Map implementations have internal optimisations that check the objects' identity before the hashCode() and equals() are called.

*一些Map实现具有内部优化,在调用hashCode()和equals()之前检查对象的身份。

#3


13  

Here is the link to an article comparing the performance of entrySet(), keySet() and values(), and advice regarding when to use each approach.

以下是一篇文章的链接,该文章比较了entrySet(),keySet()和values()的性能,以及有关何时使用每种方法的建议。

Apparently the use of keySet() is faster (besides being more convenient) than entrySet() as long as you don't need to Map.get() the values.

显然,只要您不需要Map.get()值,keySet()的使用比entrySet(更快(除了更方便)。

#1


64  

First of all, this depends entirely on which type of Map you're using. But since the JavaRanch thread talks about HashMap, I'll assume that that's the implementation you're referring to. And lets assume also that you're talking about the standard API implementation from Sun/Oracle.

首先,这完全取决于您使用的是哪种类型的地图。但是由于JavaRanch线程谈论HashMap,我将假设这是你所指的实现。我们还假设您正在讨论Sun / Oracle的标准API实现。

Secondly, if you're concerned about performance when iterating through your hash map, I suggest you have a look at LinkedHashMap. From the docs:

其次,如果您在迭代哈希映射时关注性能,我建议您查看LinkedHashMap。来自文档:

Iteration over the collection-views of a LinkedHashMap requires time proportional to the size of the map, regardless of its capacity. Iteration over a HashMap is likely to be more expensive, requiring time proportional to its capacity.

对LinkedHashMap的集合视图进行迭代需要与映射大小成比例的时间,无论其容量如何。对HashMap的迭代可能更昂贵,需要与其容量成比例的时间。

HashMap.entrySet()

The source-code for this implementation is available. The implementation basically just returns a new HashMap.EntrySet. A class which looks like this:

可以使用此实现的源代码。实现基本上只返回一个新的HashMap.EntrySet。一个看起来像这样的类:

private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
    public Iterator<Map.Entry<K,V>> iterator() {
        return newEntryIterator(); // returns a HashIterator...
    }
    // ...
}

and a HashIterator looks like

和HashIterator看起来像

private abstract class HashIterator<E> implements Iterator<E> {
    Entry<K,V> next;    // next entry to return
    int expectedModCount;   // For fast-fail
    int index;      // current slot
    Entry<K,V> current; // current entry

    HashIterator() {
        expectedModCount = modCount;
        if (size > 0) { // advance to first entry
            Entry[] t = table;
            while (index < t.length && (next = t[index++]) == null)
                ;
        }
    }

    final Entry<K,V> nextEntry() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        Entry<K,V> e = next;
        if (e == null)
            throw new NoSuchElementException();

        if ((next = e.next) == null) {
            Entry[] t = table;
            while (index < t.length && (next = t[index++]) == null)
                ;
        }
    current = e;
        return e;
    }

    // ...
}

So there you have it... Thats the code dictating what will happen when you iterate through an entrySet. It walks through the entire array which is as long as the maps capacity.

所以你有它...这就是代码,它规定了迭代entrySet时会发生什么。它遍历整个阵列,这与地图容量一样长。

HashMap.keySet() and .get()

Here you first need to get hold of the set of keys. This takes time proportional to the capacity of the map (as opposed to size for the LinkedHashMap). After this is done, you call get() once for each key. Sure, in the average case, with a good hashCode-implementation this takes constant time. However, it will inevitably require lots of .hashCode and .equals calls, which will obviously take more time than just doing a entry.value() call.

在这里,您首先需要掌握一组键。这需要时间与地图的容量成比例(与LinkedHashMap的大小相反)。完成此操作后,为每个键调用get()一次。当然,在一般情况下,使用一个好的hashCode实现,这需要恒定的时间。但是,它将不可避免地需要大量的.hashCode和.equals调用,这显然比仅仅执行entry.value()调用需要更多的时间。

#2


65  

The most common case where using entrySet is preferable over keySet is when you are iterating through all of the key/value pairs in a Map.

使用entrySet优先于keySet的最常见情况是迭代Map中的所有键/值对。

This is more efficient:

这更有效:

for (Map.Entry entry : map.entrySet()) {
    Object key = entry.getKey();
    Object value = entry.getValue();
}

than:

比:

for (Object key : map.keySet()) {
    Object value = map.get(key);
}

Because in the second case, for every key in the keySet the map.get() method is called, which - in the case of a HashMap - requires that the hashCode() and equals() methods of the key object be evaluated in order to find the associated value*. In the first case that extra work is eliminated.

因为在第二种情况下,对于keySet中的每个键,都会调用map.get()方法,在HashMap的情况下,需要按顺序计算key对象的hashCode()和equals()方法。找到相关的值*。在第一种情况下,额外的工作被消除。

Edit: This is even worse if you consider a TreeMap, where a call to get is O(log2(n)), i.e. the comparator for will may need to run log2(n) times (n = size of the Map) before finding the associated value.

编辑:如果你考虑一个TreeMap,更糟糕的是,对get的调用是O(log2(n)),即will的比较器可能需要运行log2(n)次(n = Map的大小)才能找到相关的价值。

*Some Map implementations have internal optimisations that check the objects' identity before the hashCode() and equals() are called.

*一些Map实现具有内部优化,在调用hashCode()和equals()之前检查对象的身份。

#3


13  

Here is the link to an article comparing the performance of entrySet(), keySet() and values(), and advice regarding when to use each approach.

以下是一篇文章的链接,该文章比较了entrySet(),keySet()和values()的性能,以及有关何时使用每种方法的建议。

Apparently the use of keySet() is faster (besides being more convenient) than entrySet() as long as you don't need to Map.get() the values.

显然,只要您不需要Map.get()值,keySet()的使用比entrySet(更快(除了更方便)。