我想讨论特定集合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的最新功能,在性能方面是否有更好的解决方案?
干杯!
答案 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>
,用于位置查找/插入/检索。但即使这有点尴尬......考虑如果您需要在位置i
和i + 1
的条目之间插入新条目会发生什么。