我有一个对象的ArrayList,该对象的版本号为字段。我想对该ArrayList做一些工作,但是我只想要对象的最新版本。我当时正在考虑这样编码:
company/
这有效吗?我不知道我们最初处于for循环中的事实是否会在运行时破坏ArrayList的可变性。
答案 0 :(得分:0)
不,这不起作用。 iter.remove()
将导致out for循环失败,并显示ConcurrentModificationException
。
您可以使用索引为for的循环以及BitSet
来跟踪要删除的内容,而不是这样做:
BitSet toRemove = new BitSet();
for (int m = 0; m < ObjectList.size(); ++m) {
if (toRemove.get(m)) continue;
ObjectVO myVO = ObjectList.get(m);
for (int c = 0; c < ObjectList.size(); ++c) {
if (toRemove.get(c)) continue;
ObjectVO checkVO = ObjectList.get(c);
if(myVO.getID().equals(checkVO.getID()) {
//they are the same object ID. Check the version number, remove it lower
if(myVO.getVersion() > checkVO.getVersion()) {
toRemove.set(c);
}
}
}
}
这基本上是您的代码,但尚未删除。然后,您可以在之后浏览列表并将其删除:
int dst = 0;
for (int src = 0; src < ObjectList.size(); ++src) {
if (!toRemove.get(src)) {
ObjectList.set(dst++, ObjectList.get(src));
}
}
ObjectList.subList(dst, ObjectList.size()).clear();
像这样使用BitSet
的要点是,如果要从末尾以外的任何地方删除ArrayList
,效率很低,因为它需要所有元素在“右边”移除的元素的位置会随机移动一个位置。设置/获取和清除的循环仅允许您将每个保留的元素移动一次。
但是,如果将列表元素按具有相同ID的事物进行分组,则可以比二次循环做得更好:那么您就不必继续检查整个列表:
BitSet toKeep = new BitSet();
IntStream.range(0, ObjectList.size())
.mapToObj(a -> a)
.collect(
groupingBy(a -> ObjectList.get(a).getID(),
maxBy(comparingInt(a -> ObjectList.get(a).getVersion()))))
.values()
.forEach(a -> toKeep.set(a));
int dst = 0;
for (int src = 0; src < ObjectList.size(); ++src) {
if (toKeep.get(src)) {
ObjectList.set(dst++, ObjectList.get(src));
}
}
ObjectList.subList(dst, ObjectList.size()).clear();
答案 1 :(得分:0)
假设您有存储空间,而不是执行O(N ^ 2)操作,则可以通过使用Map跟踪每个ID的最新版本来更有效地执行操作(O(N))。一遍跟踪每个ID的最新版本,第二遍删除不是最新的元素。
Map<Integer, Thing> newestById = new HashMap<>();
for (Thing thing : list) {
newestById.merge(thing.id, thing, (a,b) -> a.version > b.version ? a : b);
}
list.removeIf(thing -> thing != newestById.get(thing.id)); }
根据您的用例,您甚至可以将数据存储在Map中而不是List中,并在将其添加到Map中之前检查版本是否为最新版本。
答案 2 :(得分:0)
其他答案已经讨论过,这是行不通的。正如我所看到的,您有三个选择,可以为CPU周期/灵活性交换内存。在我的示例中,我使用了Integer而不是ObjectVO,但是交换它们很简单。
选项1 -中等内存,阵列的单次通过
跟踪您看到的最高ID,并在符合条件的新项目中填充ArrayList。当遇到新的更高ID时,丢弃ArrayList并创建一个新ID:
ArrayList<Integer> objectList = getObjectList();
Integer bestId = -1;
ArrayList<Integer> allObjectsMatchingId = new ArrayList<>();
for(Integer currentObject : objectList) {
if(currentObject > bestId) {
bestId = currentObject;
allObjectsMatchingId = new ArrayList<>();
} else if(currentObject == bestId) {
allObjectsMatchingId.add(currentObject);
}
}
return allObjectsMatchingId;
选项2 -内存更昂贵,阵列的单次通过,最灵活。
对于您看到的每个ID,创建一个ArrayList并将其存储在地图上。这样一来,您就可以轻松更改有关要保留的ID的标准。
ArrayList<Integer> objectList = getObjectList();
Map<Integer, ArrayList<Integer>> objectsById = new HashMap<>();
for(Integer currentObject : objectList) {
ArrayList<Integer> listForId = objectsById.get(currentObject);
if(listForId == null) {
listForId = new ArrayList<Integer>();
}
listForId.add(currentObject);
objectsById.put(currentObject, listForId);
}
Integer bestId = -1;
for(Integer i : objectsById.keySet()) {
if(i > bestId) {
bestId = i;
}
}
return objectsById.get(bestId);
选项3 -除id,数组的两次遍历外,没有其他内存。
在ArrayList中搜索最高ID,然后将数组过滤为仅通过该过滤器的元素。
这是最接近当前实现的方法,不同之处在于您需要在单独的步骤中进行操作。这样可以将复杂度从O(N ^ 2)降低到O(N),这是有效的,因为您在迭代时不修改ArrayList。如果您与Java 8兼容,则可以在此处使用Stream而不是迭代器进行过滤。参见Java: Efficient ArrayList filtering?
ArrayList<Integer> objectList = getObjectList();
Integer bestId = -1;
for(Integer currentObject : objectList) {
if(currentObject > bestId) {
bestId = currentObject;
}
}
Iterator<Integer> iter = objectList.iterator();
while(iter.hasNext()) {
if(iter.next() != bestId) {
iter.remove();
}
}
答案 3 :(得分:0)
为什么不使用Java Streams解决此问题:
Collection<ObjectVO> result = objectList.stream()
.collect(Collectors.toMap(ObjectVO::getID, Function.identity(),
BinaryOperator.maxBy(Comparator.comparing(ObjectVO::getVersion))))
.values();
这将创建一个包含每个ID的最高版本的地图。然后,您可以只使用Map.values()
来获取对象列表。
如果您需要List
或ArrayList
,则可以只使用new ArrayList<>(result)
。