【问题标题】:Iterator removal/edit is safe, however I also need to edit all following Iterations迭代器删除/编辑是安全的,但是我还需要编辑所有后续迭代
【发布时间】:2013-10-17 13:54:26
【问题描述】:

我在 stackflow 中阅读了一些关于 ConcurrentModificationException 的信息,我的实际更新似乎不是问题,这可能是我的设计中的问题,或者我需要一种我还没有学过的技术。

示例情况: 我的迭代器沿着位置标记运行。 然后可以执行一个动作来移动标记(例如插入字符串)。 所有大于当前位置的标记也必须移动以保持正确性。

任务: 如何在迭代器不爆炸的情况下更新剩余的标记? 我可以刷新迭代器,还是中断并重新开始循环?

以下代码是从我的工作中抽象出来的。

 public void innerLoop(Boolean b) {
    //An Example of what I'm working with
    HashMap<String, HashSet<Integer>> map = new HashMap<String, HashSet<Integer>>() {
        {
            put("Nonce",
                new HashSet<Integer>() {

                {
                    add(1);
                    add(2);
                    add(3);
                    add(4);
                    add(5);
                }
            });
        }
    };

    //for each key
    for (String key: map.keySet()) {
        HashSet<Integer> positions = map.get(key);

        //for each integer
        for (Iterator<Integer> it = positions.iterator(); it.hasNext();) {
            Integer position = it.next();

            System.out.println("position =" + position);
            //(out of scope) decision requiring elements from the outter loops
            if (new Random().nextBoolean()&&b) {
                //shift position by +4 (or whatever)
                //and every other (int >= position)
                System.out.println("Shift " + position + " by 4");
                Integer shift = 4;
                update(position,
                       shift,
                       positions);
                it.remove();
            }
        }
    }
}

public void update(Integer current,
                   Integer diff,
                   Set<Integer> set) {

    if (set != null) {
        HashSet<Integer> temp = new HashSet<Integer>();
        for (Integer old: set) {
            if (old >= current) {
                temp.add(old);
                System.out.println(old + "Added to temp");
            }
        }

        for (Integer old: temp) {
            set.remove(old);
            System.out.println(old + "removed");
            set.add(old + diff);
            System.out.println((old + diff) + "Added");
        }
    }
}

使用 Garrett Hall 解决方案编辑

 public void nestedloops() {

    HashMap<String, HashSet<Integer>> map = new HashMap<String, HashSet<Integer>>() {
        {
            put("Hello",
                new HashSet<Integer>() {

                {
                    add(5);
                    add(2);
                    add(3);
                    add(4);
                    add(1);
                    add(6);
                }
            });
        }
    };

    //for each key
    for (String key: map.keySet()) {
        ArrayList<Integer> positions = new ArrayList<Integer>(map.get(key));
        //for each integer
        for (int i = 0; i < positions.size(); i++) {
            Integer position = positions.get(i);
            System.out.println("[" + i + "] =" + position);
            //out of scope decision
            if (new Random().nextBoolean()) {
                //shift position by +4
                //and every other (int >= position)
                System.out.println("Shift after " + position + " by 4");
                Integer shift = 4;
                //Update the array
                for (int j = 0; j < positions.size(); j++) {
                    Integer checkPosition = positions.get(j);
                    if (checkPosition > position) {
                        System.out.println(checkPosition + "increased by 4");
                        positions.set(j,
                                      checkPosition + shift);
                    }
                }
            }
        }
        //Add updated Array
        map.put(key,
                new HashSet<Integer>(positions));
    }
}

【问题讨论】:

    标签: java optimization iterator concurrentmodification


    【解决方案1】:

    最好的办法是通过将HashSet 放入一个列表来对其进行索引。然后你可以使用索引来引用元素而不是Iterator。只要您不删除或添加(仅更新)元素,那么您的索引就是正确的。否则你将不得不考虑这一点。示例:

    ArrayList<Integer> positions = new ArrayList<Integer>(map.get(key));
    for (int i = 0; i < positions.size(); i ++) {
      // updating list
      for (int j = i; i < positions.size(); j ++) {
        positions.set(j, positions.get(i) + diff);
      }
    }
    

    【讨论】:

    • map.keyset() 返回 Set 我需要for(String key: map.keyset() ){ ArrayList&lt;Integer&gt; positions = new ArrayList&lt;Integer&gt;(map.get(key)); 所以我沿着整数数组移动,但只要不改变大批?最初看起来像我想要的,让我检查一下
    • ArrayList 的实例化方式应与您当前代码中的positions 相同。 (更新map.get(key) 以反映这一点)。
    • @GarrettHall 我已经为我的问题添加了您的解决方案。
    • @GarrettHall 是我放置在末尾以保留所做更改所需的吗?
    • 正确,看来最终还是需要修改map
    【解决方案2】:

    我会将原始集合复制到一个列表中,这样您就不必担心当前的迭代代码。然后更新一个二级列表(未迭代)。

    原因:

    1. 你不能一次迭代和修改你的原始集合(没有办法绕过ConcurrentModificationExceptions
    2. 很高兴one liner 移动列表中的项目。

      Collections.rotate(list.subList(j, k+1), -1);
      
    3. Guava 将能够处理“找到满足谓词并转换列表的第一个索引”这一系列实用方法。

    【讨论】:

    • 问:如果我有“editPositions”和“loopingPositions”。在 loopingPositions 中,我发现我想转移 position = 3。我更新了editPositions = {1, 2, 3+4, 4+4, 5+4} = {1,2,7,8,9},循环位置的下一次迭代将是position = 4,当我真正需要position = 8 时,它已经过时了。会这样吗?
    • 好吧,没有什么能阻止你在每次迭代时将原始列表交换为它的副本,它们会生成一个新副本,旋转它,与前一个副本交换等等(它很昂贵但会起作用) .视图还可以缓解性能问题。
    • 一个更好的策略将涉及“轮换位置”的累积计数器(以0 开头),然后您始终将loopingPosition + cumulativeCounter 视为要移动editPositions 的位置,并且,在在每次迭代结束时,使cumulativeCounter += diff 这样您就可以迭代原始值,并且仍然只使用原始集合的单个副本获得所需的结果。
    • 我已尽我所能将您的编辑添加到问题中。它似乎工作正常,但我如何在退出时保留每个位置的适当移位量。 {1+(4*0), 2+(4*1), 3+(4*2), 4+(4*2), 5(4*3)} = {1,6,11,12,17}
    • 只需创建一个临时 List 并在每次迭代时添加 positionValue + counterShift(在 counterShift 已更新之后)...或使用 Guava transform 函数一步完成;)
    猜你喜欢
    • 2016-11-15
    • 2012-09-05
    • 1970-01-01
    • 1970-01-01
    • 2021-09-25
    • 2011-09-03
    • 2016-11-29
    • 2015-11-29
    • 1970-01-01
    相关资源
    最近更新 更多