【问题标题】:How to efficiently (performance) remove many items from List in Java?如何有效地(性能)从 Java 中的 List 中删除许多项目?
【发布时间】:2011-01-03 20:48:21
【问题描述】:

我有相当大的 List 命名项目(>= 1,000,000 个项目)和一些由 表示的条件,它选择要删除的项目,并且 对于我列表中的许多(可能是一半)项目都是 true。

我的目标是有效地删除 选择的项目并保留所有其他项目,可能会修改源列表,可能会创建新列表 - 应该考虑性能来选择最佳方式。

这是我的测试代码:

    System.out.println("preparing items");
    List<Integer> items = new ArrayList<Integer>(); // Integer is for demo
    for (int i = 0; i < 1000000; i++) {
        items.add(i * 3); // just for demo
    }

    System.out.println("deleting items");
    long startMillis = System.currentTimeMillis();
    items = removeMany(items);
    long endMillis = System.currentTimeMillis();

    System.out.println("after remove: items.size=" + items.size() + 
            " and it took " + (endMillis - startMillis) + " milli(s)");

和幼稚的实现:

public static <T> List<T> removeMany(List<T> items) {
    int i = 0;
    Iterator<T> iter = items.iterator();
    while (iter.hasNext()) {
        T item = iter.next();
        // <cond> goes here
        if (/*<cond>: */i % 2 == 0) {
            iter.remove();
        }
        i++;
    }
    return items;
}

如您所见,我使用项目索引模 2 == 0 作为删除条件 () - 仅用于演示目的。

可以提供什么更好的removeMany 版本,为什么这个更好的版本实际上更好?

【问题讨论】:

  • 哪些性能指标很重要 - 只是速度,还是内存使用很重要?这份名单是短暂的吗?是否保证可以访问较短(删除后)列表的每个条目?我想知道创建一个将删除条件存储为返回条件的新列表迭代器是否可能是解决某一类问题的有效解决方案。您可以让迭代器的 next() 方法跳过与条件不匹配的项目,而不是从列表中删除。这样做的好处是只测试您操作的条目,但会浪费大量内存。
  • 就像上面的例子一样:输入是一个列表,输出是一个列表(删除选定的项目或保留项目的新项目相同),速度是我最重要的指标。
  • 感谢您的回答!我刚刚给出了我的答案,它编译了不同的建议方法并在实践中对其进行了测试。我希望我的代码没有错误,我的最终结论是有帮助的。

标签: java performance list collections


【解决方案1】:

我想构建一个新列表,而不是修改现有列表,会更高效 - 特别是当项目数量与您指示的一样大时。这假设您的列表是ArrayList,而不是LinkedList。对于非循环LinkedList,插入是 O(n),但在现有迭代器位置移除是 O(1);在这种情况下,您的幼稚算法应该足够高效。

除非列表是LinkedList,否则每次调用remove() 时移动列表的成本可能是实现中最昂贵的部分之一。对于数组列表,我会考虑使用:

public static <T> List<T> removeMany(List<T> items) {
    List<T> newList = new ArrayList<T>(items.size());
    Iterator<T> iter = items.iterator();
    while (iter.hasNext()) {
        T item = iter.next();
        // <cond> goes here
        if (/*<cond>: */i++ % 2 != 0) {
            newList.add(item);
        }
    }
    return newList;
}

【讨论】:

  • 你应该否定条件并且你不需要i++
  • 我认为您忘记否定删除条件以获得保留条件 ;-) 但我理解您的意思,这是(恕我直言)我目前所知的最佳解决方案...
  • @notnoop 我只是好奇:为什么你建议将 i++ 作为单独的语句删除?有什么帮助吗?我故意不理会它,因为它不是移除条件 (cond) 的一部分,而且代码似乎更长但更清晰......关注点的微分离 ;-)。
  • 说某事不是一个词,即使它在书面和口语中都很常用,对我来说似乎很愚蠢。语言是有生命的实体,随着新概念和想法的出现而发展,需要有效地交流。让字典识别它,并不能让它成为一个词——让人们使用和理解它,确实如此。
【解决方案2】:

您可以尝试的一件事是使用LinkedList 而不是ArrayList,与ArrayList 一样,如果从列表中删除元素,则需要复制所有其他元素。

【讨论】:

  • 为什么在我的情况下使用 LinkedList 比 LBushkin 给出的解决方案更好(创建新列表)?还是和新列表一样好(考虑性能)?
  • 我认为对于您的情况,添加到新的LinkedList 或从现有的LinkedList 中删除应该是相同的,因为两者都是 O(1),并且您删除了一半的元素。如果删除的元素多于留下的元素,则添加到新列表应该更快,反之亦然。
【解决方案3】:

ArrayList 中删除大量元素是O(n^2) 操作。我会建议简单地使用LinkedList,它更适合插入和删除(但不适用于随机访问)。 LinkedList 有一点内存开销。

如果您确实需要保留ArrayList,那么您最好创建一个新列表。

更新:与创建新列表的比较:

重用同一个列表,主要成本来自于删除节点和更新 LinkedList 中的适当指针。这是任何节点的恒定操作。

在构建新列表时,主要成本来自创建列表和初始化数组条目。两者都是廉价的操作。您也可能会产生调整新列表后端数组大小的成本;假设最终数组大于传入数组的一半。

因此,如果您只删除一个元素,那么LinkedList 方法可能更快。如果您要删除除一个以外的所有节点,那么新的列表方法可能会更快。

当你带来内存管理和 GC 时,会有更多的复杂性。我想省略这些。

最好的选择是自己实施替代方案,并在运行典型负载时对结果进行基准测试。

【讨论】:

  • 为什么在我的情况下使用 LinkedList 比 LBushkin 给出的解决方案更好(创建新列表)?还是和新列表一样好(考虑性能)?
  • 这取决于您的使用情况,如果您需要经常从列表中删除大量项目,或者您的功能只是“给定这个列表,返回一个删除了 N 个项目的列表”。
  • 可能还取决于您要删除的项目数量与列表中有多少项目,即如果您有一个包含 10,000 个项目的列表,并且您只需要删除 2 个而不是一个包含 10,000 个项目的列表需要删除其中的 9,999 个。
  • @matt b:如果列表中元素的顺序不重要,实际上可以使 LinkedList 和 ArrayList 的删除性能相同。例如,不是在 ArrayList 的元素上调用 remove(),而是将当前索引处的值替换为列表尾部的项目。然后在最后一项上调用 remove() 。这会导致不移动任何项目,但会更改列表中项目的顺序。
  • @notnoop:好的解决方案一般有两种方法:1)创建新列表,2)使用LinkedList作为输入列表。我一定会测试这两种并在这里发布我的结果。但在我这样做之前......我认为从 LinkedList 中删除一项不会带来明显更好的性能,因为必须在两种方法中遍历整个列表。这种遍历给出了 O(n) 的总成本,不管要删除多少元素。这只是我在进行测试之前的想法......
【解决方案4】:

我会创建一个新的List 来添加项目,因为从列表中间删除一个项目非常昂贵。

public static List<T> removeMany(List<T> items) {
    List<T> tempList = new ArrayList<T>(items.size()/2); //if about half the elements are going to be removed
    Iterator<T> iter = items.iterator();
    while (item : items) {
        // <cond> goes here
        if (/*<cond>: */i % 2 != 0) {
            tempList.add(item);
        }
    }
    return tempList;
}

编辑:我还没有测试过这个,所以很可能有小的语法错误。

第二次编辑:当您不需要随机访问但快速添加时间时,使用 LinkedList 会更好。

但是...

ArrayList 的常数因子小于 LinkedList (Ref) 的常数因子。由于您可以合理猜测将删除多少元素(您在问题中说“大约一半”),因此只要您没有,在 ArrayList 的末尾添加一个元素就是 O(1)重新分配它。因此,如果您可以做出合理的猜测,我预计ArrayList 在大多数情况下会比LinkedList 稍微快一点。 (这适用于我发布的代码。在您的幼稚实现中,我认为LinkedList 会更快)。

【讨论】:

    【解决方案5】:

    尝试在您的算法中实现递归。

    【讨论】:

    • 你能举例说明你的意思吗?
    • -1 递归几乎总是比迭代慢。此外,这是一个非常简单的算法,丝毫没有从递归中受益。在 C/C++ 中,递归可能是一个不错的选择如果列表的长度未知,但这里不是这种情况。
    • @chinmay,递归并不总是比迭代慢,尤其是尾部优化等。
    • 是的,但通常是这样。如果不是,性能大致相当。尾调用优化本质上将递归变成了迭代。递归对于这么简单的事情没有意义,尤其是当性能是最重要的事情时。
    • 此外,Sun JVM/JDK 目前不支持尾调用优化(我必须查一下)。
    【解决方案6】:

    也许列表不是最适合您的数据结构?你能改变它吗?也许您可以使用一棵树来对项目进行排序,从而删除一个节点会删除所有满足条件的项目?或者至少可以加快您的运营速度?

    在您的简单示例中,使用两个列表(一个包含 i % 2 != 0 为真的项目,另一个包含 i % 2 != 0 为假的项目)可以很好地发挥作用。但这当然是非常依赖于领域的。

    【讨论】:

    • 移除条件可能会有所不同,并且可以预先确定,因此无法按特殊顺序/结构等准备项目,因为不知道源列表(命名项目)何时填充项目.
    【解决方案7】:

    正如其他人所说,您的第一个倾向应该是建立第二个列表。

    但是,如果您还想尝试就地编辑列表,那么有效的方法是使用来自 Guava 的 Iterables.removeIf()。如果它的参数是一个列表,它会将保留的元素合并到前面,然后简单地切掉末尾——比一个一个地删除()内部元素要快得多。

    【讨论】:

    • 该死的,完全是来这里拉皮条番石榴(或谷歌收藏,如果你需要一些已经以二进制形式可用并且可以在公共 maven 存储库中找到的东西......温柔的刺激,凯文) 但你已经打败了我。
    • 是的,很遗憾 Iterables.removeIf() 在 google-collections 中不存在;从那时起,它就在 Guava 中出现了!
    【解决方案8】:

    使用Apache Commons Collections。特别是this function。这与人们建议您实现它的方式基本相同(即创建一个新列表,然后添加到它)。

    【讨论】:

      【解决方案9】:

      由于速度是最重要的指标,因此有可能使用更多内存并减少对列表的重新创建(如我的评论中所述)。不过,实际的性能影响将完全取决于功能的使用方式。

      该算法假定以下至少一项为真:

      • 原始列表的所有元素都不需要测试。如果我们真的在寻找符合我们条件的前 N ​​个元素,而不是所有符合我们条件的元素,就会发生这种情况。
      • 将列表复制到新内存中的成本更高。如果原始列表使用超过 50% 的已分配内存,则可能会发生这种情况,因此就地工作可能会更好,或者如果内存操作变得更慢(这将是一个意外的结果)。
      • 从列表中删除元素的速度损失太大而无法一次全部接受,但将这种损失分散到多个操作中是可以接受的,即使整体损失大于一次全部处理。这就像申请 20 万美元的抵押贷款:每月支付 1000 美元,为期 30 年,每个月都可以负担得起,并且拥有拥有房屋和股权的好处,即使在整个贷款期限内总还款额为 36 万美元。李>

      免责声明:存在大量语法错误 - 我没有尝试编译任何东西。

      首先,继承 ArrayList

      公共类 ConditionalArrayList 扩展 ArrayList { public Iterator iterator(条件条件) { 返回列表迭代器(条件); } public ListIterator listIterator(条件条件) { return new ConditionalArrayListIterator(this.iterator(),condition); } public ListIterator listIterator(){ return iterator(); } 公共迭代器(){ throw new InvalidArgumentException("您必须为迭代器指定条件"); } }

      那么我们需要帮助类:

      公共类 ConditionalArrayListIterator 实现 ListIterator { 私有 ListIterator 列表迭代器; 条件条件; // 以下两个标志用作快速优化,以便 // 我们不会不必要地重复对已知良好元素的测试。 布尔 nextKnownGood = false; boolean prevKnownGood = false; public ConditionalArrayListIterator(ListIterator listIterator, Condition 条件) { this.listIterator = listIterator; this.condition = 条件; } public void add(Object o){ listIterator.add(o); } /** * 请注意,这样做效率极低 * 交替调用 hasNext() 和 hasPrev() * 之间有一堆不匹配的元素 * 两个匹配元素。 */ 公共布尔 hasNext() { if(nextKnownGood) 返回真; /* 查找列表中的下一个对象 * 符合我们的条件,如果有的话。 */ 而(!listIterator.hasNext()) { 对象下一个 = listIterator.next(); 如果(条件匹配(下一个)){ listIterator.set(next); nextKnownGood = true; 返回真; } } nextKnownGood = 假; // 没有找到匹配的元素。 返回假; } /** * 有关效率说明,请参阅 hasPrevious。 * 复制并粘贴 hasNext()。 */ 公共布尔 hasPrevious() { if(prevKnownGood) 返回真; /* 查找列表中的下一个对象 * 符合我们的条件,如果有的话。 */ 而(!listIterator.hasPrevious()) { 对象 prev = listIterator.next(); 如果(条件匹配(上一个)){ prevKnownGood = true; listIterator.set(prev); 返回真; } } // 没有找到匹配的元素。 prevKnwonGood = false; 返回假; } /** 见 hasNext() 效率说明 **/ 公共对象下一个() { if( nextKnownGood || hasNext() ) { prevKnownGood = nextKnownGood; nextKnownGood = 假; 返回 listIterator.next(); } throw NoSuchElementException("没有更多匹配的元素"); } /** 见 hasNext() 效率说明;复制和粘贴 next() **/ 公共对象先前() { if( prevKnownGood || hasPrevious() ) { nextKnownGood = prevKnownGood; prevKnownGood = false; 返回 listIterator.previous(); } throw NoSuchElementException("没有更多匹配的元素"); } /** * 注意 nextIndex() 和 previousIndex() 返回数组索引 * 的值,而不是该类返回的结果数。 * 如果这对您不利,只需维护您自己的当前索引和 * 在 next() 和 previous() 中递增或递减 */ public int nextIndex(){ return listIterator.previousIndex(); } public int previousIndex(){ return listIterator.previousIndex(); } public remove(){ listIterator.remove(); } 公共集合(对象 o){ listIterator.set(o); } }

      当然,我们还需要条件接口:

      /** 很像比较器... **/ 公共接口条件 { 公共布尔匹配(对象 obj); }

      以及测试的条件

      公共类 IsEvenCondition { { 公共布尔匹配(对象 obj){ 返回(数字(obj)).intValue() % 2 == 0; }

      我们终于准备好了一些测试代码

      条件条件 = new IsEvenCondition(); System.out.println("准备物品"); startMillis = System.currentTimeMillis(); List items = new ArrayList(); // 整数用于演示 for (int i = 0; i 注意:实际上没有删除任何内容。此算法使用额外的" + "内存以避免修改或复制原始列表。"); System.out.println("即将遍历列表"); startMillis = System.currentTimeMillis(); 整数计数=迭代(项目,条件); endMillis = System.currentTimeMillis(); System.out.println("迭代后:items.size=" + items.size() + " count=" + count + " 花费了 " + (endMillis - startMillis) + " milli(s)"); System.out.println("--> 注意:这应该有点低效。" + " 主要是由于多个类的开销。" +“这个算法被设计(希望)比”更快 + " 使用列表中所有元素的算法。"); System.out.println("即将遍历列表"); startMillis = System.currentTimeMillis(); int total = addFirst(30, items, condition); endMillis = System.currentTimeMillis(); System.out.println("合计前 30 个元素后:total=" + total + " 并且花费了 " + (endMillis - startMillis) + " milli(s)"); ... private int iterate(List items, Condition 条件) { // i++和返回值真的是为了防止JVM优化 // - 为了安全起见。 迭代器 iter = items.listIterator(condition); for(int i=0; iter.hasNext()); i++){ iter.next(); } 返回我; } private int addFirst(int n, List items, Condition 条件) { 整数 = 0; 迭代器 iter = items.listIterator(condition); for(int i=0; i

      【讨论】:

      • 这似乎过于复杂了。我不知道你最初的假设来自哪里。我已经清楚地说明了我的问题(恕我直言),甚至给出了测试代码。重要的是我的测试代码的运行时间与不同的 removeMany 实现。越快越好——就是这么简单。
      • @WildWezyr:这很可能是您的问题的一个过于复杂的解决方案。这就是为什么我在对这个问题的评论中提出了额外的问题(除了“速度是唯一的因素”)。
      • 另外,可能只是实现过于复杂。如果你不需要一个通用的解决方案,并且如果你只是在删除项目一次后对数据执行操作,你仍然可以使用底层算法。
      【解决方案10】:

      很抱歉,但我认为所有这些答案都没有抓住重点:您可能不必也可能不应该使用列表。

      如果这种“查询”很常见,为什么不构建一个有序的数据结构,无需遍历所有数据节点呢?您没有告诉我们足够多的问题,但是鉴于您提供的示例,一个简单的树可以解决问题。每个项目都有插入开销,但是您可以非常快速地找到包含匹配节点的子树,因此您可以避免现在进行的大多数比较。

      此外:

        1234563 >drop 该子树,而不是更新整个列表节点。
      • 每次删除列表项时,都会更新指针 - 例如 lastNode.nextnextNode.prev 或其他东西 - 但如果它变成如果你还想删除nextNode,那么你刚刚引起的指针更新会被新的更新丢弃。)

      【讨论】:

      • 你可能是对的:更好的源结构在某些情况下应该表现更好。但如果您不知道移除条件是什么,则很难选择专用结构。如果移除条件是固定的 - 那么可以在一开始就选择更好的数据结构。但是在我的问题中并非如此,因此解决方案必须在 List 上运行,这就是我所要求的 - 从 List 中有效删除怎么样。
      【解决方案11】:

      好的,现在是对所提议方法的测试结果的时候了。这是我测试过的方法(每种方法的名称也是我的来源中的类名):

      1. NaiveRemoveManyPerformer - ArrayList 带有迭代器和删除 - 我的问题中给出的第一个和幼稚的实现。
      2. BetterNaiveRemoveManyPerformer - ArrayList 向后迭代并从头到尾移除。
      3. LinkedRemoveManyPerformer - 幼稚的迭代器和删除,但在 LinkedList 上工作。缺点:仅适用于LinkedList
      4. CreateNewRemoveManyPerformer - ArrayList 作为副本制作(仅添加保留元素),使用迭代器遍历输入 ArrayList
      5. SmartCreateNewRemoveManyPerformer - 更好 CreateNewRemoveManyPerformer - 结果 ArrayList 的初始大小(容量)设置为最终列表大小。缺点:启动时必须知道列表的最终大小。
      6. FasterSmartCreateNewRemoveManyPerformer - 更好 (?) SmartCreateNewRemoveManyPerformer - 使用项目索引 (items.get(idx)) 而不是迭代器。
      7. MagicRemoveManyPerformer - 适用于ArrayList 的原地工作(无列表副本),并从列表末尾的项目开始压缩洞(删除的项目)。缺点:这种方法会改变列表中项目的顺序。
      8. ForwardInPlaceRemoveManyPerformer - 适用于 ArrayList - 移动保留项目以填充孔,最后返回 subList(没有最终删除或清除)。
      9. GuavaArrayListRemoveManyPerformer - Google Guava Iterables.removeIf 用于 ArrayList - 与 ForwardInPlaceRemoveManyPerformer 几乎相同,但最终删除列表末尾的项目。

      本答案末尾给出了完整的源代码。

      使用不同的列表大小(从 10,000 项到 10,000,000 项)和不同的删除因素(指定必须从列表中删除多少项)执行的测试。

      正如我在 cmets 中发布的其他答案一样 - 我认为将项目从 ArrayList 复制到第二个 ArrayList 将比迭代 LinkedList 并仅删除项目要快。 Sun 的 Java 文档说,与 LinkedList 实现相比,ArrayList 的常数因子较低,但令人惊讶的是,我的问题并非如此。

      在实践中LinkedList 简单的迭代和删除在大多数情况下具有最佳性能(这种方法在LinkedRemoveManyPerformer 中实现)。通常只有MagicRemoveManyPerformer 的性能与LinkedRemoveManyPerformer 相当,其他方法明显慢得多。 Google Guava GuavaArrayListRemoveManyPerformer 比手工制作的类似代码慢(因为我的代码不会删除列表末尾不必要的项目)。

      从 1,000,000 个源项目中删除 500,000 个项目的示例结果:

      1. NaiveRemoveManyPerformer:未进行测试 - 我不是那么有耐心,但它的表现比 BetterNaiveRemoveManyPerformer 差。
      2. BetterNaiveRemoveManyPerformer: 226080 毫秒
      3. LinkedRemoveManyPerformer:69 毫微秒
      4. CreateNewRemoveManyPerformer: 246 毫微秒
      5. SmartCreateNewRemoveManyPerformer: 112 毫微秒
      6. FasterSmartCreateNewRemoveManyPerformer: 202 毫微秒
      7. MagicRemoveManyPerformer: 74 毫微秒
      8. ForwardInPlaceRemoveManyPerformer: 69 毫微秒
      9. GuavaArrayListRemoveManyPerformer: 118 毫微秒

      从 1,000,000 个源项目中删除 1 个项目的示例结果(第一个项目被删除):

      1. BetterNaiveRemoveManyPerformer:34 毫秒
      2. LinkedRemoveManyPerformer:41 毫秒
      3. CreateNewRemoveManyPerformer:253 毫(秒)
      4. SmartCreateNewRemoveManyPerformer:108 毫微秒
      5. FasterSmartCreateNewRemoveManyPerformer:71 毫秒
      6. MagicRemoveManyPerformer:43 毫秒
      7. ForwardInPlaceRemoveManyPerformer:73 毫秒
      8. GuavaArrayListRemoveManyPerformer:78 毫(秒)

      从 1,000,000 个源项目中删除 333,334 个项目的示例结果:

      1. BetterNaiveRemoveManyPerformer:253206 毫秒
      2. LinkedRemoveManyPerformer:69 毫微秒
      3. CreateNewRemoveManyPerformer:245 毫秒
      4. SmartCreateNewRemoveManyPerformer:111 毫(秒)
      5. FasterSmartCreateNewRemoveManyPerformer:203 毫秒
      6. MagicRemoveManyPerformer:69 毫秒
      7. ForwardInPlaceRemoveManyPerformer:72 毫秒
      8. GuavaArrayListRemoveManyPerformer:102 毫(秒)

      从 1,000,000 个源项中删除 1,000,000 个(全部)项的示例结果(所有项均已删除,但通过逐一处理,如果您先验地知道要删除所有项,则应简单地清除列表):

      1. BetterNaiveRemoveManyPerformer:58 毫微秒
      2. LinkedRemoveManyPerformer:88 毫秒
      3. CreateNewRemoveManyPerformer:95 毫微秒
      4. SmartCreateNewRemoveManyPerformer:91 毫(秒)
      5. FasterSmartCreateNewRemoveManyPerformer:48 毫微秒
      6. MagicRemoveManyPerformer:61 毫秒
      7. ForwardInPlaceRemoveManyPerformer:49 毫秒
      8. GuavaArrayListRemoveManyPerformer:133 毫(秒)

      我的最终结论:使用混合方法 - 如果处理 LinkedList - 简单的迭代和删除是最好的,如果处理 ArrayList - 这取决于项目顺序是否重要 - 然后使用 ForwardInPlaceRemoveManyPerformer,如果项目顺序可能会更改 - 最佳选择是MagicRemoveManyPerformer。如果删除因素是先验已知的(您知道将删除多少项与保留多少项),则可以使用更多条件来选择在特定情况下表现更好的方法。但是已知的删除因素并不常见...... Google Guava Iterables.removeIf 就是这样一种混合解决方案,但假设略有不同(必须更改原始列表,无法创建新列表并且项目顺序始终很重要) - 这些是最常见的假设所以removeIf 是大多数现实生活中的最佳选择。

      还要注意,所有好的方法(天真不好!)都足够好 - 它们中的任何一种在实际应用中都应该做得很好,但必须避免天真的方法。

      最后 - 我的测试源代码。

      package WildWezyrListRemovalTesting;
      
      import com.google.common.base.Predicate;
      import com.google.common.collect.Iterables;
      import java.util.ArrayList;
      import java.util.Iterator;
      import java.util.LinkedList;
      import java.util.List;
      
      public class RemoveManyFromList {
      
          public static abstract class BaseRemoveManyPerformer {
      
              protected String performerName() {
                  return getClass().getSimpleName();
              }
      
              protected void info(String msg) {
                  System.out.println(performerName() + ": " + msg);
              }
      
              protected void populateList(List<Integer> items, int itemCnt) {
                  for (int i = 0; i < itemCnt; i++) {
                      items.add(i);
                  }
              }
      
              protected boolean mustRemoveItem(Integer itemVal, int itemIdx, int removeFactor) {
                  if (removeFactor == 0) {
                      return false;
                  }
                  return itemIdx % removeFactor == 0;
              }
      
              protected abstract List<Integer> removeItems(List<Integer> items, int removeFactor);
      
              protected abstract List<Integer> createInitialList();
      
              public void testMe(int itemCnt, int removeFactor) {
                  List<Integer> items = createInitialList();
                  populateList(items, itemCnt);
                  long startMillis = System.currentTimeMillis();
                  items = removeItems(items, removeFactor);
                  long endMillis = System.currentTimeMillis();
                  int chksum = 0;
                  for (Integer item : items) {
                      chksum += item;
                  }
                  info("removing took " + (endMillis - startMillis)
                          + " milli(s), itemCnt=" + itemCnt
                          + ", removed items: " + (itemCnt - items.size())
                          + ", remaining items: " + items.size()
                          + ", checksum: " + chksum);
              }
          }
          private List<BaseRemoveManyPerformer> rmps =
                  new ArrayList<BaseRemoveManyPerformer>();
      
          public void addPerformer(BaseRemoveManyPerformer rmp) {
              rmps.add(rmp);
          }
          private Runtime runtime = Runtime.getRuntime();
      
          private void runGc() {
              for (int i = 0; i < 5; i++) {
                  runtime.gc();
              }
          }
      
          public void testAll(int itemCnt, int removeFactor) {
              runGc();
              for (BaseRemoveManyPerformer rmp : rmps) {
                  rmp.testMe(itemCnt, removeFactor);
              }
              runGc();
              System.out.println("\n--------------------------\n");
          }
      
          public static class NaiveRemoveManyPerformer
                  extends BaseRemoveManyPerformer {
      
              @Override
              public List<Integer> removeItems(List<Integer> items, int removeFactor) {
                  if (items.size() > 300000 && items instanceof ArrayList) {
                      info("this removeItems is too slow, returning without processing");
                      return items;
                  }
                  int i = 0;
                  Iterator<Integer> iter = items.iterator();
                  while (iter.hasNext()) {
                      Integer item = iter.next();
                      if (mustRemoveItem(item, i, removeFactor)) {
                          iter.remove();
                      }
                      i++;
                  }
                  return items;
              }
      
              @Override
              public List<Integer> createInitialList() {
                  return new ArrayList<Integer>();
              }
          }
      
          public static class BetterNaiveRemoveManyPerformer
                  extends NaiveRemoveManyPerformer {
      
              @Override
              public List<Integer> removeItems(List<Integer> items, int removeFactor) {
      //            if (items.size() > 300000 && items instanceof ArrayList) {
      //                info("this removeItems is too slow, returning without processing");
      //                return items;
      //            }
      
                  for (int i = items.size(); --i >= 0;) {
                      Integer item = items.get(i);
                      if (mustRemoveItem(item, i, removeFactor)) {
                          items.remove(i);
                      }
                  }
                  return items;
              }
          }
      
          public static class LinkedRemoveManyPerformer
                  extends NaiveRemoveManyPerformer {
      
              @Override
              public List<Integer> createInitialList() {
                  return new LinkedList<Integer>();
              }
          }
      
          public static class CreateNewRemoveManyPerformer
                  extends NaiveRemoveManyPerformer {
      
              @Override
              public List<Integer> removeItems(List<Integer> items, int removeFactor) {
                  List<Integer> res = createResultList(items, removeFactor);
                  int i = 0;
      
                  for (Integer item : items) {
                      if (mustRemoveItem(item, i, removeFactor)) {
                          // no-op
                      } else {
                          res.add(item);
                      }
                      i++;
                  }
      
                  return res;
              }
      
              protected List<Integer> createResultList(List<Integer> items, int removeFactor) {
                  return new ArrayList<Integer>();
              }
          }
      
          public static class SmartCreateNewRemoveManyPerformer
                  extends CreateNewRemoveManyPerformer {
      
              @Override
              protected List<Integer> createResultList(List<Integer> items, int removeFactor) {
                  int newCapacity = removeFactor == 0 ? items.size()
                          : (int) (items.size() * (removeFactor - 1L) / removeFactor + 1);
                  //System.out.println("newCapacity=" + newCapacity);
                  return new ArrayList<Integer>(newCapacity);
              }
          }
      
          public static class FasterSmartCreateNewRemoveManyPerformer
                  extends SmartCreateNewRemoveManyPerformer {
      
              @Override
              public List<Integer> removeItems(List<Integer> items, int removeFactor) {
                  List<Integer> res = createResultList(items, removeFactor);
      
                  for (int i = 0; i < items.size(); i++) {
                      Integer item = items.get(i);
                      if (mustRemoveItem(item, i, removeFactor)) {
                          // no-op
                      } else {
                          res.add(item);
                      }
                  }
      
                  return res;
              }
          }
      
          public static class ForwardInPlaceRemoveManyPerformer
                  extends NaiveRemoveManyPerformer {
      
              @Override
              public List<Integer> removeItems(List<Integer> items, int removeFactor) {
                  int j = 0; // destination idx
                  for (int i = 0; i < items.size(); i++) {
                      Integer item = items.get(i);
                      if (mustRemoveItem(item, i, removeFactor)) {
                          // no-op
                      } else {
                          if (j < i) {
                              items.set(j, item);
                          }
                          j++;
                      }
                  }
      
                  return items.subList(0, j);
              }
          }
      
          public static class MagicRemoveManyPerformer
                  extends NaiveRemoveManyPerformer {
      
              @Override
              public List<Integer> removeItems(List<Integer> items, int removeFactor) {
                  for (int i = 0; i < items.size(); i++) {
                      if (mustRemoveItem(items.get(i), i, removeFactor)) {
                          Integer retainedItem = removeSomeFromEnd(items, removeFactor, i);
                          if (retainedItem == null) {
                              items.remove(i);
                              break;
                          }
                          items.set(i, retainedItem);
                      }
                  }
      
                  return items;
              }
      
              private Integer removeSomeFromEnd(List<Integer> items, int removeFactor, int lowerBound) {
                  for (int i = items.size(); --i > lowerBound;) {
                      Integer item = items.get(i);
                      items.remove(i);
                      if (!mustRemoveItem(item, i, removeFactor)) {
                          return item;
                      }
                  }
                  return null;
              }
          }
      
          public static class GuavaArrayListRemoveManyPerformer
                  extends BaseRemoveManyPerformer {
      
              @Override
              protected List<Integer> removeItems(List<Integer> items, final int removeFactor) {
                  Iterables.removeIf(items, new Predicate<Integer>() {
      
                      public boolean apply(Integer input) {
                          return mustRemoveItem(input, input, removeFactor);
                      }
                  });
      
                  return items;
              }
      
              @Override
              protected List<Integer> createInitialList() {
                  return new ArrayList<Integer>();
              }
          }
      
          public void testForOneItemCnt(int itemCnt) {
              testAll(itemCnt, 0);
              testAll(itemCnt, itemCnt);
              testAll(itemCnt, itemCnt - 1);
              testAll(itemCnt, 3);
              testAll(itemCnt, 2);
              testAll(itemCnt, 1);
          }
      
          public static void main(String[] args) {
              RemoveManyFromList t = new RemoveManyFromList();
              t.addPerformer(new NaiveRemoveManyPerformer());
              t.addPerformer(new BetterNaiveRemoveManyPerformer());
              t.addPerformer(new LinkedRemoveManyPerformer());
              t.addPerformer(new CreateNewRemoveManyPerformer());
              t.addPerformer(new SmartCreateNewRemoveManyPerformer());
              t.addPerformer(new FasterSmartCreateNewRemoveManyPerformer());
              t.addPerformer(new MagicRemoveManyPerformer());
              t.addPerformer(new ForwardInPlaceRemoveManyPerformer());
              t.addPerformer(new GuavaArrayListRemoveManyPerformer());
      
              t.testForOneItemCnt(1000);
              t.testForOneItemCnt(10000);
              t.testForOneItemCnt(100000);
              t.testForOneItemCnt(200000);
              t.testForOneItemCnt(300000);
              t.testForOneItemCnt(500000);
              t.testForOneItemCnt(1000000);
              t.testForOneItemCnt(10000000);
          }
      }
      

      【讨论】:

      • 很高兴您费了这么多麻烦来对这些方法进行经验测试,因此不得不通知您这些测量结果尚无定论,这很痛苦。获得有意义的 Java 代码微基准测试非常非常困难。每一件你可以做对的事情,都有一百件你可以做错的事情,这将极大地扭曲结果。作为一个示例,您需要在开始测量之前重复运行测试大约 10 秒左右。每次测量都应测量“多次”重复。继续测量直到结果稳定......
      • ... 每个 VM 调用仅测量 一个 事物(这很重要)。多次运行每次测量——您会惊讶于结果的不一致。这只是表面问题。如果您做所有这些事情并遵循数十条建议,那么您的基准测试结果很可能像我的一样,仍然具有可疑的意义。这就是 Java 中微基准的现状。
      • @Kevin:太好了,您也了解 Java 中微基准测试困难的理论考虑;-)。但是在我的特殊情况下你的观点是什么?我的代码或我的结论有什么问题?请给出更好的代码或更好的结论。你已经知道我的结论是错误的了?你有没有在我的代码中看到热身阶段(它很隐蔽,但它就在那里)。请为我的测试提供更多详细信息、代码修复或只是您更好的结论...
      • @Kevin:不知道你为什么认为我的测量值不稳定(是不是因为我没有提到 Java 中的 microbencharks 的一些明智之处?)。它们非常稳定,并且几乎没有相对偏差,因为我在每次测试中使用不同的主动方法集多次执行我的测试。然后,当我非常确定我的结果已经稳定时,我收集了它们并将它们作为我的结论发布在我的答案中。
      • @Kevin:前段时间我已经“很高兴”从您的链接中阅读文章。乐趣更大,因为我已经知道那里所说的内容。我做了大约 10 年的代码优化(不同的语言:Java、SQL 语句等),所以我必须熟悉如何发现瓶颈、基准优化等。再次感谢您提供的一堆理论信息......但是请 - 要有建设性 -提供见解我上面的代码有什么问题,为什么我的结论是错误的,如何解决它们。
      【解决方案12】:

      与其混淆我的第一个答案,这已经相当长,这里有第二个相关选项:您可以创建自己的 ArrayList,并将内容标记为“已删除”。该算法做出以下假设:

      • 在施工过程中浪费时间(降低速度)比在拆除操作过程中浪费时间要好。换句话说,它将速度惩罚从一个位置移动到另一个位置。
      • 现在最好浪费内存,在计算结果之后再进行垃圾收集,而不是花时间在前面(您总是被时间垃圾收集卡住......)。
      • 一旦开始删除,元素将永远不会添加到列表中(否则重新分配标志对象时会出现问题)

      此外,这又是未经测试的,因此存在语法错误。

      公共类 FlaggedList 扩展 ArrayList { 私有 Vector flags = new ArrayList(); 私有静态最终字符串 IN = Boolean.TRUE; // 未删除 私有静态最终字符串 OUT = Boolean.FALSE; // 移除 私有int删除= 0; 公共 MyArrayList(){ 这个(1000000); } 公共 MyArrayList(int 估计){ 超级(估计); flags = new ArrayList(estimate); } 公共无效删除(int idx){ flags.set(idx, OUT); 移除++; } public boolean isRemoved(int idx){ return flags.get(idx); } }

      和迭代器 - 可能需要做更多的工作来保持同步,这一次省略了许多方法:

      公共类 FlaggedListIterator 实现 ListIterator { int idx = 0; 公共 FlaggedList 列表; public FlaggedListIterator(FlaggedList 列表) { this.list = 列表; } 公共布尔 hasNext() { while(idx

      【讨论】:

        猜你喜欢
        • 2014-05-20
        • 1970-01-01
        • 2016-07-25
        • 2023-03-08
        • 1970-01-01
        • 1970-01-01
        • 2014-07-20
        • 2015-01-06
        • 1970-01-01
        相关资源
        最近更新 更多