如果我的很多对象的键更改缓慢,应该使用哪个Java Collection?
基本上,我有一个ArrayList,其中包含所有需要排序的对象。该ArrayList有时被另一个线程更改。为了对其进行迭代,直到现在,我已经先使用clear然后将addAll写入ArrayList到另一个ArrayList中,但是我现在意识到这也可能导致ConcurrentModificationException,所以我想更改它。
我用于迭代的新Collection(当前为ArrayList)需要使用自定义Comparator进行排序。问题在于,在我制作下一个原始副本之前,密钥将更改,并且我必须对其进行多次排序。通常,这些排序的顺序几乎是正确的。插入排序可能在这里很好。
现在最大的问题是:
我认为只有在考虑必须先复制另一个未排序的Collection(但不一定是)并在另一个线程更改Collection中的数据时才这样做时,才能给出此问题的正确答案。
我对解决方案的想法如下:
您还需要记住一点,我只需要遍历原始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效率更高,但我不知道。有人可以解释一下。
我还使用了一个自定义比较器,其中甚至包含一点数学运算,因此优化此处的排序过程确实是有益的
答案 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