如何在HashMap上使用双重迭代器修复'java.util.ConcurrentModificationException'

时间:2019-05-16 22:32:38

标签: java

如果它们验证彼此之间的条件,我想“合并”同一哈希图的元素。

“合并”的意思是:总结自己的属性。

如果合并了2个项目,则应将第二个项目从哈希图中删除,因为它已集成到第一个项目中。

此操作必须高效,因为地图可能包含大量元素。

public static void main(String[] args) {
    Map<Long, Item> itemMap = new HashMap(){
        {
            put(0L, new Item(2558584));
            put(1L, new Item(254243232));
            put(2L, new Item(986786));
            put(3L, new Item(672542));
            put(4L, new Item(48486));
            put(5L, new Item(76867467));
            put(6L, new Item(986786));
            put(7L, new Item(7969768));
        }
    };

    Iterator it_I = itemMap.entrySet().iterator();
    while (it_I.hasNext()) {
        Map.Entry pair_I = (Map.Entry)it_I.next();
        Item tempItem_I = (Item)pair_I.getValue();

        System.out.println("I:" + tempItem_I);

        Iterator it_J = itemMap.entrySet().iterator();
        while (it_J.hasNext()) {
            Map.Entry pair_J = (Map.Entry)it_J.next();
            Item tempItem_J = (Item)pair_J.getValue();

            if (!pair_J.getKey().equals(pair_I.getKey())) {
                boolean isSame = tempItem_I.isSame(tempItem_J);
                if(isSame){
                    tempItem_I.merge(tempItem_J);
                    it_J.remove();
                }

            }
        }
    }
}




public class Item {
    long id;

    public Item(long id) {
        this.id = id;
    }

    public boolean isSame(Item item) {
        if(this.id == item.id) return true;
        else return false;
    }

    public void merge(Item item) {
        this.id += item.id;
    }

    public String toString() {
        return String.valueOf(this.id);
    }
}

只有两个包含ID:986786的项目可以合并在一起,因为它们会验证要合并的条件(相同ID)。

但是无法将其删除:

Exception in thread "main" java.util.ConcurrentModificationException

   I:2558584
   I:254243232
        at java.util.HashMap$HashIterator.nextNode(HashMap.java:1445)
    I:986786
        at java.util.HashMap$EntryIterator.next(HashMap.java:1479)
        at java.util.HashMap$EntryIterator.next(HashMap.java:1477)
        at Main.main(Main.java:23)

3 个答案:

答案 0 :(得分:1)

这是一种更线性的解决方案,使用额外的Map来跟踪商品标识符是否存在

Map<Long, Long> reversed = new HashMap<>();
Iterator<Map.Entry<Long, Item>> iterator = itemMap.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<Long, Item> pair = iterator.next();
    Long itemKey = reversed.get(pair.getValue().id);
    if (itemKey != null) {
        Item item = itemMap.get(itemKey);
        item.merge(pair.getValue());
        iterator.remove();
    } else {
        reversed.put(pair.getValue().id, pair.getKey());
    }
}

答案 1 :(得分:0)

您有两个单独的迭代器同时运行,处理相同的地图项。如果一个迭代器删除了一个项目,则该项目不会反映在另一个迭代器中,因此它们将不同步。因此,您将获得并发修改异常。

在不知道键的重要性的情况下,我只是将Item用作键。在浏览项目列表时,请检查密钥是否存在。如果是这样,请将其删除,将其添加到当前项中,然后将其读取到地图中。

如果类确实代表您的Item,则应消除isSame方法,并同时覆盖equalshashCode

答案 2 :(得分:0)

基本问题是您同时运行两个迭代器,这意味着它们同时处理mapItem

但是为什么会出现此错误?

因为,当一个iteratormapItem中删除一个项目时,它不能反映在另一个iterator中,它们不同步,这就是为什么您会获得并发修改异常的原因

@Joakim Danielson提出的解决方案不是线程安全的。这意味着,如果有多个线程使用该方法,则无法从竞争条件中保护对共享数据的访问。

可能的替代方法是将itemMap转换为ConcurrentHashMap实例。这将使我们能够使用for-each循环并保持代码线程的安全。代替使用Iterator

private static void solutionFor(Map<Long, Item> itemMap)
{
    Map<Long, Item> fooMap = itemMap
            .entrySet()
            .stream()
            .collect(Collectors.toConcurrentMap(Map.Entry::getKey, e -> new Item(e.getValue().getId())));
    Map<Long, Long> valueMapper = new HashMap<>();

    System.out.println("Before deletion: ");
    System.out.println(fooMap);

    Set<Map.Entry<Long, Item>> entries = fooMap.entrySet();
    for (Map.Entry<Long, Item> entry : entries) {
        Long index = valueMapper.get(entry.getValue().getId());
        if (index != null) {
            Item accumulator = fooMap.get(index);
            accumulator.merge(entry.getValue());
            fooMap.remove(entry.getKey());
        } else {
            valueMapper.put(entry.getValue().getId(), entry.getKey());
        }
    }

    System.out.println("After deletion: ");
    System.out.println(fooMap);
}

替代

我在这里尝试实施优化的解决方案。使用Stream.collect()方法创建具有过滤值的新地图。

public static void main(String[] args)
{
    Map<Long, Item> itemMap = Stream.of(
            entry(0L, new Item(2558584)),
            entry(1L, new Item(254243232)),
            entry(2L, new Item(986786)),
            entry(3L, new Item(672542)),
            entry(4L, new Item(4846)),
            entry(5L, new Item(76867467)),
            entry(6L, new Item(986786)),
            entry(7L, new Item(7969768)),
            entry(8L, new Item(68868486)),
            entry(9L, new Item(923)),
            entry(10L, new Item(986786)),
            entry(11L, new Item(549768)),
            entry(12L, new Item(796168)),
            entry(13L, new Item(868421)),
            entry(14L, new Item(923)),
            entry(15L, new Item(986786)),
            entry(16L, new Item(549768)),
            entry(17L, new Item(4846)),
            entry(18L, new Item(4846)),
            entry(19L, new Item(76867467)),
            entry(20L, new Item(986786)),
            entry(21L, new Item(7969768)),
            entry(22L, new Item(923)),
            entry(23L, new Item(4846)),
            entry(24L, new Item(986786)),
            entry(25L, new Item(549768))
    ).collect(entriesToMap());

    Map<Long, Item> fooMap = itemMap
            .entrySet()
            .stream()
            .collect(Collectors.toMap(Map.Entry::getKey, e -> new Item(e.getValue().getId())));
    Map<Long, Long> valueMapper = new HashMap<>();

    System.out.println("Before deletion: ");
    System.out.println(fooMap);

    Map<Long, Item> result = fooMap.entrySet()
            .stream()
            .filter(entry -> {
                Long index = valueMapper.get(entry.getValue().getId());
                if (index != null) {
                    Item item = fooMap.get(index);
                    item.merge(entry.getValue());
                    return false;
                } else {
                    valueMapper.put(entry.getValue().getId(), entry.getKey());
                }
                return true;
            })
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

    System.out.println("After deletion: ");
    System.out.println(result);
}

public static <K, V> Map.Entry<K, V> entry(K key, V value)
{
    return new AbstractMap.SimpleEntry<>(key, value);
}

public static <K, U> Collector<Map.Entry<K, U>, ?, Map<K, U>> entriesToMap()
{
    return Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue);
}