【问题标题】:The best way to iterate SortedSet / SortedMap in Java backwards在 Java 中向后迭代 SortedSet / SortedMap 的最佳方法
【发布时间】:2009-03-16 22:05:22
【问题描述】:

我需要向后遍历 SortedMap 的条目集(即 SortedSet)。我正在编写的代码对性能非常敏感,因为每秒会从许多地方调用它数千次,甚至更多。对最快的方式有什么建议吗?

【问题讨论】:

  • 你的意思是,你需要向前迭代它和向后?否则,只需反转您的比较器。

标签: java collections


【解决方案1】:

在 Java 1.6 中,您可以使用 NavigableSet

【讨论】:

  • NavigableSet descendingIterator()
  • 是的。具体来说,使用 NavigableSet.descendingIterator()。或者您可以使用 NavigableSet.descendingSet() 以相反的顺序获取元素的视图,该视图由现有集合支持。
  • According to Josh Bloch,这两个接口的作者,能够轻松地以相反的顺序迭代是 Java 6 中引入 NavigableSet 的主要原因之一。
  • 你能给出一个代码示例如何在 NavigableSet 的帮助下迭代一个 SortedSet 吗?
  • 您将接口 SortedMap 和 SortedSet 分别替换为 NavigableMap 和 NavigableSet。严格来说,仅使用 SortedSet 是不可能的。 (您可能知道底层实现支持 NavigableMap 并使用强制转换,但这将是一个相当糟糕的 hack,并且可能会在实现更改时中断。)
【解决方案2】:

在填写地图之前使用它:

SortedMap sortedMap = new TreeMap(java.util.Collections.reverseOrder());

【讨论】:

  • 谢谢!添加它比将实现更改为 NavigableSet 要容易得多。
【解决方案3】:

迭代集合的更快方法是获取它的数组副本。这可以在不创建对象甚至方法调用的情况下向前和向后迭代。缺点是你需要在它发生变化时更新它以及你的集合。

在以下示例中,向前或向后迭代超过 1000 个元素平均需要 1,208 ns。

import java.util.Comparator;
import java.util.Random;
import java.util.NavigableSet;
import java.util.TreeSet;

/*
Average time for iteration of 1000 elements was 1,208 ns
*/
public class Main {
    public static void main(String... args) {
        doPerfTest(true);
        doPerfTest(false);
    }

    private static void doPerfTest(boolean warmup) {
        NavigableSet<MyData> set = new TreeSet<MyData>(new MyCompataror());
        Random random = new Random();
        for (int i = 0; i < 1000; i++) {
            set.add(new MyData("text-" + random.nextLong(), random.nextInt()));
        }
        MyData[] myDatas = set.toArray(new MyData[set.size()]);
        long start = System.nanoTime();
        final int runs = 500 * 1000;
        for (int i = 0; i < runs; i+=2) {
            // forward iteration
            for (MyData md : myDatas) {

            }
            // reverse iteration
            for (int j = myDatas.length - 1; j >= 0; j--) {
                MyData md = myDatas[j];
            }
        }
        long time = System.nanoTime() - start;
        if (!warmup)
            System.out.printf("Average time for iteration of 1000 elements was %,d ns", time / runs);
    }

    static class MyCompataror implements Comparator<MyData> {
        public int compare(MyData o1, MyData o2) {
            int cmp = o1.text.compareTo(o2.text);
            if (cmp != 0)
                return cmp;
            return o1.value > o2.value ? +1 :
                    o1.value < o2.value ? -1 : 0;
        }
    }

    static class MyData {
        String text;
        int value;

        MyData(String text, int value) {
            this.text = text;
            this.value = value;
        }
    }
}

现在将主循环替换为,平均时间变为 20,493。

// forward iteration
for(Iterator<MyData> it = set.iterator(); it.hasNext();) {
    MyData md = it.next();
}
// reverse iteration
for(Iterator<MyData> it = set.descendingIterator(); it.hasNext();) {
    MyData md = it.next();
}

现在让我们将其与每次复制(我已经说过不如仅在更改时复制最佳)进行比较,时间下降到 15,134 ns!

因此,使用 NavigableSet 可能是所讨论的三个选项中最慢​​的

【讨论】:

  • 数组复制——每个方法调用,每秒超过 1000 次?! :)
  • 当然。复制数组是 Java 中最快的事情。简短的基准测试:复制一个包含 1000 个对象的 SortedSet 和一个反转排序顺序的自定义 Comparator 大约需要 0.8 毫秒。这甚至不仅仅是一个副本,而且还颠倒了排序顺序。所以,是的,它很快。
  • 我说“你需要更新它以及你的收藏,只要它发生变化。”不是每个方法调用。如果数组已经排序,则不需要对其进行反向排序。
  • 对一个包含 1000 个元素的有序集合执行一个 toArray() 只需要不到 14 微秒(不是你应该经常这样做)我不知道你是怎么得到 0.8 毫秒的。 ;)
【解决方案4】:

您可以使用自定义 Comparator 来定义一个 SortedMap(例如 TreeMap),以反转排序顺序。如果您需要双向迭代,那么在内存中保留 两个 数据结构副本是否可行?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-25
    • 2012-08-09
    • 2019-12-07
    • 1970-01-01
    • 2021-08-31
    相关资源
    最近更新 更多