【问题标题】:Convert a LinkedList to ArrayList for faster concurrent iterating将 LinkedList 转换为 ArrayList 以实现更快的并发迭代
【发布时间】:2017-02-02 05:38:55
【问题描述】:

我很清楚使用外部索引 (forloop) 迭代 LinkedList 的成本。查看LinkedList#listIterator 返回的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);
}

既然这样只会调用每个LinkedListonce的迭代器,然后使用效率更高的ArrayList#get方法,这是一个可行的方案吗?转换的开销是否会抵消效率增益?这种方法还有其他缺点吗?

【问题讨论】:

  • @MouseEvent 因此对combined 进行大小检查;)

标签: java arraylist collections linked-list


【解决方案1】:

[...] 同时迭代两个或多个列表,但需要这样做同时跟踪索引以将值传输到数组,因此阻止使用迭代器。

仅仅因为你需要一个索引,并不意味着你不能使用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();
}

更新 (回答具体问题)

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

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

转换的开销是否会抵消效率提升?

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

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

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


更新 (回应问题的变化)

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

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

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

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

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

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

【讨论】:

  • 谢谢。现在我想起来了,我曾想过,但忽略了它,因为在我看来,在循环和调用每个 next 之上单独声明迭代器是多余的。我将编辑我的问题以使其更清楚。
  • 感谢您非常有帮助的回答。这肯定会改变我看待迭代器和集合的方式。
【解决方案2】:

我知道这不是您问题的具体答案,但我觉得您可以从这条信息中受益。

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

LinkedList 仍然在列表中的添加/删除中获胜。

【讨论】:

  • 感谢您的信息。我刚刚浏览了Collection interface tutorial,但我现在意识到它并没有提到每个集合实现,我不记得有ArrayDequeue这样的东西。
  • 很遗憾ArrayDeque 不支持随机访问。 (还)
  • @Stuart Right,不知道。但是looks like it's on the way.
【解决方案3】:

我认为您可以在 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();
    }

【讨论】:

  • 这是我试图避免的 (In my mind, this made the use of iterators slightly redundant and more prone to human error)。但 Andreas 指出,我不一定需要避免它。
猜你喜欢
  • 2015-10-07
  • 2015-01-22
  • 1970-01-01
  • 2013-08-01
  • 2021-11-16
  • 2021-05-09
  • 1970-01-01
  • 1970-01-01
  • 2012-01-16
相关资源
最近更新 更多