我很清楚使用外部索引(LinkedList
循环)迭代for
的成本。查看ListIterator
返回的LinkedList#listIterator
的源代码,我注意到它通过跟踪当前使用的节点来显着加快进程。
但是,我最近遇到this question,它基本上是同时迭代两个或多个列表,但需要这样做,同时跟踪将值传输到数组的索引。在我看来,这使得迭代器的使用略微冗余并且更容易出现人为错误,因为在循环和调用每个next
方法之前,每个迭代器都需要单独的声明。这就是我试图避免使用迭代器循环组合的原因。以下是该问题的可能解决方案:
List<Integer> listOne = new ArrayList<>();
List<Integer> listTwo = new ArrayList<>();
int[] combined = new int[(listOne.size() < listTwo.size() ? listOne.size() : listTwo.size())];
for (int i = 0; i < combined.length; i++) {
combined[i] = listOne.get(i) + listTwo.get(i);
}
这适用于ArrayList
,但LinkedList
的操作速度相当慢。
一种可能的解决方案是使用ArrayList
的转换构造函数来获取LinkedList
的所有引用:
//convert linkedlists to arraylists
ArrayList<Integer> arrayListOne = new ArrayList<>(listOne);
ArrayList<Integer> arrayListTwo = new ArrayList<>(listTwo);
//iterate with an efficient get() operation
for (int i = 0; i < combined.length; i++) {
combined[i] = listOne.get(i) + listTwo.get(i);
}
因为这只会调用每个LinkedList
的迭代器一次,然后使用效率更高的ArrayList#get
方法,这是一个可行的解决方案吗?转换的开销是否会抵消效率提升?这种方法还有其他缺点吗?
答案 0 :(得分:4)
[...]同时迭代两个或多个列表,但需要这样做,同时跟踪索引以将值传输到数组,从而阻止使用迭代器。
仅仅因为你也需要一个索引,并不意味着你不能使用Iterator
,所以“阻止使用迭代器”是一个完全不正确的断言。
你只是做一个简单的三向并行迭代(2个迭代器和1个索引):
List<Integer> listOne = new LinkedList<>();
List<Integer> listTwo = new LinkedList<>();
int[] combined = new int[Math.min(listOne.size(), listTwo.size())];
Iterator<Integer> iterOne = listOne.iterator();
Iterator<Integer> iterTwo = listTwo.iterator();
for (int i = 0; i < combined.length; i++) {
combined[i] = iterOne.next() + iterTwo.next();
}
更新 (回答具体问题)
因为这只会调用每个
LinkedList
的迭代器一次,然后使用更高效的ArrayList#get方法,这是一个可行的解决方案吗?
是的,这绝对是一个更可行的解决方案。随着列表变大,get(index)
LinkedList
的指数响应时间会使get()
成为一个非常糟糕的解决方案。
转换的开销是否会抵消效率提升?
没有。即使在较小的列表大小上,get(index)
上LinkedList
的顺序搜索性能也会远远超过复制列表时的性能损失。
此方法还有其他缺点吗?
首先复制列表会增加内存需求,并需要额外的(不必要的)迭代数据。
更新 (以回应问题中的更改)
[...]在我看来,这使得迭代器的使用略显多余,更容易出现人为错误
并行使用多个迭代器并不是多余的。
此外,所有编程都容易出现人为错误。您通常应该使用最合适/正确的算法,而不是考虑由于复杂性增加导致的潜在编程错误中的(非常轻微)增加。当然,如果一个算法非常复杂,而另一个算法很简单,那么您可能希望使用简单算法,如果复杂算法的改进不值得。但有一个原因是没有人使用bubble sort,即使它非常简单:性能非常糟糕。在您的情况下,并行迭代的复杂性是微不足道的。
比较多个并行迭代器的使用,而不是复制到ArrayList
,这是冗余?复制到ArrayList
是因为你最终迭代数据两次,你需要更多的内存。
并行迭代是解决您问题的最佳方案。它使用所提供的List
的预期迭代机制,而不知道列表的特征。按索引迭代List
本质上是错误的。列表(和其他集合)应始终由提供的Iterator
(或ListIterator
或Spliterator
)进行迭代。
另请注意,并行迭代有时是唯一的选择,例如在merge-sort中,你没有以相同的速度迭代这两个输入。
答案 1 :(得分:1)
我知道这不是特定问题的答案,但我觉得你可以从这条信息中受益。
从Java 1.6开始,有一种新类型的集合,称为ArrayDeque
,它具有像数组一样的快速随机访问,但在最后也有快速添加/删除。
LinkedList
在列表中间添加/删除时仍然获胜。
答案 2 :(得分:0)
我认为你可以在LInkedLists上使用迭代器,在数组中使用索引:
Iterator<Integer> i1 = listOne.iterator();
Iterator<Integer> i2 = listTwo.iterator();
for (int i = 0; i < combined.length; i++) {
combined[i] = i1.next() + i2.next();
}