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



我很清楚使用外部索引(for循环)迭代LinkedList的成本。查看LinkedList#listIterator返回的ListIterator的源代码,我注意到它通过跟踪当前使用的节点显着加快了该过程。

但是,我最近遇到了这个问题,它基本上是关于同时迭代两个或多个列表,但需要在跟踪索引以将值传输到数组的同时这样做。在我看来,这使得迭代器的使用有点多余,更容易出现人为错误,因为除了循环和调用每个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方法,这是一个可行的解决方案吗?转换产生的开销是否会抵消效率增益?这种方法还有其他缺点吗?

[...

]同时迭代两个或多个列表,但需要在跟踪索引以将值传输到数组的同时执行此操作,从而防止使用迭代器。

仅仅因为你还需要一个索引,并不意味着你不能使用Iterator,所以"阻止使用迭代器">是一个完全不正确的断言。

你只是在做一个简单的 3 向并行迭代(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();
}
>UPDATE(回答特定问题)

由于这只会调用每个LinkedList的迭代器一次,然后使用更有效的 ArrayList#get 方法,这是一个可行的解决方案吗?

是的,这绝对是一个更可行的解决方案。随着列表越来越大,LinkedListget(index)的指数响应时间使得使用get()成为一个非常糟糕的解决方案。

转换产生的开销是否会抵消效率增益?

不。即使在较小的列表大小下,get(index)LinkedList上的顺序搜索性能也将远远超过复制列表造成的任何性能损失。

这种方法还有其他缺点吗?

首先复制列表会增加内存要求,并且需要额外(不必要的)数据迭代。


UPDATE(用于响应问题中的更改)

[...]在我看来,这使得迭代器的使用有点多余,更容易出现人为错误。

并行使用多个迭代器不是多余的。

此外,所有编程都容易出现人为错误。您通常应该使用最合适/正确的算法,而不是考虑(非常轻微)由于复杂性增加而导致的潜在编程误差的增加。当然,如果一种算法非常复杂,而另一种算法很容易,你可能想使用简单的算法,如果复杂算法的改进不值得的话。但是没有人使用气泡排序是有原因的,即使它非常简单:性能真的很差。在您的情况下,并行迭代的复杂性微乎其微。

比较使用多个并行迭代器与复制到ArrayList,哪个更冗余?复制到ArrayList是因为您最终会迭代数据两次,并且需要更多的内存来执行此操作。

并行迭代是解决问题的最佳方法。它使用所提供List的预期迭代机制,而不知道列表的特征。按索引迭代List本质上是错误的。列表(和其他集合)应始终由提供的Iterator(或ListIteratorSpliterator)迭代。

另请注意,并行迭代有时是唯一的选择,例如在合并排序中,您不会以相同的速度迭代两个输入。

我知道这不是对您的问题的特别回答,但我觉得您可以从这条信息中受益。

从 Java 1.6 开始,有一种称为ArrayDeque的新型集合,它具有像数组一样的快速随机访问,但在末尾也有快速添加/删除。

如果列表,LinkedList仍然在中间添加/删除中获胜。

我认为你可以在 LInkedList 上使用迭代器,并为数组使用索引:

Iterator<Integer> i1 = listOne.iterator();
Iterator<Integer> i2 = listTwo.iterator();
for (int i = 0; i < combined.length; i++) {
combined[i] = i1.next() + i2.next();
}

最新更新