LinkedHashMap按索引与性能访问

时间:2018-05-16 22:44:31

标签: java performance java-8 java-9 linkedhashmap

我想讨论特定集合LinkedHashMap的一些性能,以满足特定需求以及Java 8或9新功能如何帮助实现这一目标。

假设我有以下LinkedHashMap:

private Map<Product, Item> items = new LinkedHashMap<>();

使用默认构造函数意味着此Map在迭代时遵循插入顺序。

- EDITED-- 这里要明确的是,我理解Maps不是索引访问的正确数据结构,碰巧这个类实际上需要两个删除方法,一个是Product,一个是正确的方式,这是关键,另一个是位置或索引,这是不常见的,这是我对绩效的关注。顺便说一下,这不是我的要求。

我必须按索引实现 removeItem()方法。对于那些不了解的人,LinkedHashMap 不会有某种map.get(index);方法可用。

所以我将列出几个解决方案:

解决方案1:

public boolean removeItem(int position) {
    List<Product> orderedList = new ArrayList<>(items.keySet());
    Product key = orderedList.get(position);

    return items.remove(key) != null;
}

解决方案2:

public boolean removeItem(int position) {
    int counter = 0;
    Product key = null; //assuming there's no null keys

    for(Map.Entry<Product, Item> entry: items.entrySet() ){
        if( counter == position ){
            key = entry.getKey();
            break;
        }
        counter++;
    }

    return items.remove(key) != null;
}

关于这两种解决方案的考虑。

S1:我知道ArrayLists有快速的迭代和访问,所以我认为这里的问题是正在创建一个全新的集合,所以如果我有一个庞大的集合,内存将会受到影响

S2:我知道LinkedHashMap迭代比HashMap快,但没有ArrayList快,所以我相信如果我们有一个巨大的集合,这里的迭代时间会受到影响,但不是记忆。

考虑到所有这些,并且我的考虑是正确的,我可以说两种解决方案都有O(n)复杂性吗?

使用Java 8或9的最新功能,在性能方面是否有更好的解决方案?

干杯!

2 个答案:

答案 0 :(得分:4)

As said by Stephen C,时间复杂度是相同的,因为在任何一种情况下,你都有一个线性迭代,但效率仍然不同,因为第二个变量只会迭代到指定的元素,而不是创建一个完整的副本。

您可以通过在找到条目后不执行其他查找来进一步优化此操作。要使用指向Map中实际位置的指针,您必须明确使用其Iterator

public boolean removeItem(int position) {
    if(position >= items.size()) return false;
    Iterator<?> it=items.values().iterator();
    for(int counter = 0; counter < position; counter++) it.next();
    boolean result = it.next() != null;
    it.remove();
    return result;
}

如果密钥映射到false,则遵循原始代码返回null的逻辑。如果您在地图中没有null个值,则可以简化逻辑:

public boolean removeItem(int position) {
    if(position >= items.size()) return false;
    Iterator<?> it=items.entrySet().iterator();
    for(int counter = 0; counter <= position; counter++) it.next();
    it.remove();
    return true;
}

您可以使用Stream API检索特定元素,但后续的remove操作需要查找,这会降低在已经具有对该位置的引用的迭代器上调用remove的效率。大多数实现的地图。

public boolean removeItem(int position) {

    if(position >= items.size() || position < 0)
        return false;

    Product key = items.keySet().stream()
        .skip(position)
        .findFirst()
        .get();

    items.remove(key);
    return true;
}

答案 1 :(得分:2)

  

考虑到所有这些,并且我的考虑是正确的,我可以说两种解决方案都有O(n)复杂性吗?

是。平均复杂度是相同的 在第一个解决方案中,new ArrayList<>(entrySet)步骤为O(N)

在第二个解决方案中,循环为O(N)

尽管最佳案例复杂性存在差异。在第一个解决方案中,您始终复制整个列表。在第二个解决方案中,您只需根据需要进行迭代。所以最好的情况是它可以停止迭代第一个元素。

但在两种情况下,虽然平均复杂度为O(N),但我的直觉是第二种解决方案将是最快的。 (如果对您很重要,请将其标记为......)

  

使用Java 8或9的最新功能,在性能方面是否有更好的解决方案?

Java 8和Java 9不提供任何性能改进。

如果你想要更好的O(N)平均复杂度,你将需要一个不同的数据结构。

另一点需要注意的是,索引Map的入口集通常不是一件有用的事情。每当从集合中删除条目时,其他条目的某些的索引值都会更改 ....

难以有效模仿这种“不稳定”的索引行为。如果您想要稳定的行为,那么您可以使用HashMap<K,V>扩充主LinkedHashMap<K,V> / HashMap<Integer,K>,用于位置查找/插入/检索。但即使这有点尴尬......考虑如果您需要在位置ii + 1的条目之间插入新条目会发生什么。