将LinkedList转换为ArrayList以加快并发迭代

时间:2017-02-02 05:38:55

标签: java arraylist collections linked-list

我很清楚使用外部索引(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方法,这是一个可行的解决方案吗?转换的开销是否会抵消效率提升?这种方法还有其他缺点吗?

3 个答案:

答案 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(或ListIteratorSpliterator)进行迭代。

另请注意,并行迭代有时是唯一的选择,例如在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();
    }