有效地在困难的情况下对Java集合进行排序

时间:2019-06-05 09:54:41

标签: java performance sorting collections

如果我的很多对象的键更改缓慢,应该使用哪个Java Collection?
基本上,我有一个ArrayList,其中包含所有需要排序的对象。该ArrayList有时被另一个线程更改。为了对其进行迭代,直到现在,我已经先使用clear然后将addAll写入ArrayList到另一个ArrayList中,但是我现在意识到这也可能导致ConcurrentModificationException,所以我想更改它。

我用于迭代的新Collection(当前为ArrayList)需要使用自定义Comparator进行排序。问题在于,在我制作下一个原始副本之前,密钥将更改,并且我必须对其进行多次排序。通常,这些排序的顺序几乎是正确的。插入排序可能在这里很好。
现在最大的问题是:

  1. 我是否使用TreeSet并在顺序更改时调用Collections.sort
  2. 我是否使用某种列表(哪种类型?ArrayList?LinkedList?)并在更改顺序时调用Collections.sort

我认为只有在考虑必须先复制另一个未排序的Collection(但不一定是)并在另一个线程更改Collection中的数据时才这样做时,才能给出此问题的正确答案。

我对解决方案的想法如下:

  1. 让第二个Collection为ArrayList并始终使用Collections.copy对其进行更新(首先使其具有正确的大小,但在我的场景中通常应已具有正确的大小(我意识到这不是原子的,并且可能导致问题) ),然后根据需要多次调用Collections.sort。也许可以对初始排序后的每个排序实施插入排序算法,因为这样可以更快。
  2. 以某种方式将TreeSet用于第二个集合。问题是我不知道如何遍历原始线程安全来从那里添加所有元素。另外,我不知道如何在初始化之后有效地对Set进行排序。我看到有人使用Collections.sort,但我不知道那里的效率。还请记住,它几乎是排序的。

您还需要记住一点,我只需要遍历原始Collection和工作副本Collection,就无需索引。

我已经编写了一些Java代码来显示问题:

public static List<FloatWrapper> pending_additions = Collections.synchronizedList(new ArrayList<>());
public static List<FloatWrapper> pending_removals = Collections.synchronizedList(new ArrayList<>());

public static List<FloatWrapper> list = new ArrayList<>();

public static Random rnd = new Random();

public static void main(String[] args) {
    // initialize array
    for (int i = 0; i < 1000; i++) {
        list.add(new FloatWrapper(i));
    }
    // the main loop (runs basically infinitly)
    for (int runs_infinitly = 0; runs_infinitly < 100; runs_infinitly++) {
        // apply pending changes
        synchronized (pending_additions) {
            list.addAll(pending_additions);
            pending_additions.clear();
        }
        synchronized (pending_removals) {
            list.removeAll(pending_removals);
            pending_removals.clear();
        }

        // doing my stuff
        for (int i = 0; i < 5; i++) {
            // sort array with quicksort I believe, which is not the fastest
            // for this scenario usually, except for the start when its completly unsorted
            System.out.println("sorting");
            Collections.sort(list);
            // iterate very often doing different things
            for (int j = 0; j < 1; j++) {
                for (FloatWrapper num : list) {
                    // do something with num
                    System.out.print(num.number + " ");
                }
                System.out.println();
            }
            System.out.println("changing keys");
            // change the values that are used for sorting
            for (FloatWrapper num : list) {
                num.number += (rnd.nextFloat() - 0.5f) * 0.2f;
            }
        }
    }
}

public static class FloatWrapper implements Comparable<FloatWrapper> {
    public float number;

    public FloatWrapper(float number) {
        this.number = number;
    }

    public int compareTo(FloatWrapper arg0) {
        return Float.compare(number, arg0.number);
    }
}

仅从另一个线程写入的数组中,pending_additions和pending_removals是数组。自从我写这篇文章以来,这是我的进步,因此不需要复制和使用整个列表。
我的问题仍然存在,我应该使用TreeSet来提高性能,还是应该做其他不同的事情?基本上我不知道如何有效地对TreeSet进行排序。我什至可以想象带有Collection.sort()的ArrayList效率更高,但我不知道。有人可以解释一下。

我还使用了一个自定义比较器,其中甚至包含一点数学运算,因此优化此处的排序过程确实是有益的

2 个答案:

答案 0 :(得分:2)

您当前的实现已经利用了列表的部分排序:

Collections.sort的Javadoc编写

  

此实现遵循List.sort(Comparator)方法

以及该方法says的Javadoc

  

此实现是一种稳定的,自适应的迭代合并排序,在对输入数组进行部分排序时,所需的比较少于n lg(n),而在对输入数组进行随机排序时,它提供了传统合并排序的性能。如果输入数组几乎已排序,则该实现需要大约n个比较。临时存储要求从几乎排序的输入数组的一个小常数到随机排序的输入数组的n / 2个对象引用都有。

     

该实现在其输入数组中充分利用了升序和降序,并且可以在同一输入数组的不同部分中利用了升序和降序。它非常适合合并两个或多个排序后的数组:只需将这些数组连接起来,然后对结果数组进行排序。

     

该实现改编自Tim Peters针对Python的列表排序(TimSort)。它使用了Peter McIlroy的“乐观排序和信息理论复杂性”技术,该技术在1993年1月举行的第四届ACM-SIAM离散算法年会上发表,第467-474页。

由于遍历集合的次数比对集合进行排序的次数要多,因此迭代(及其中的操作)的成本可能远高于排序。也就是说,通过进一步调整排序,您不太可能获得重大改进。

答案 1 :(得分:1)

您真正需要的还不是很清楚,但是我认为您可以保持list.stream().sorted(yourComparator).forEach(yourAction); ,然后再进行迭代

CopyOnWriteArrayList

ConcurrentModificationException是线程安全的,即,如果在迭代进行时其他某个线程对其进行了更改,则您将不会获得List<FloatWrapper> sorted = list.stream().sorted().collect(toList()); for (int i = 0; i < 5; i++) { sorted.forEach(i -> doYourStuff(i)); } 并继续迭代列表,就像开始时一样。

编辑:或者,由于您要迭代几次:

PivotItem