【问题标题】:advantage of creating a temporary buffer when iterating over and modifying a collection in java在 java 中迭代和修改集合时创建临时缓冲区的优势
【发布时间】:2009-11-18 17:08:06
【问题描述】:

我正在迭代并修改一个映射(由现有的一组枚举对象创建),如下所示:

public class Dispenser {
   private Map<Ingredient, Integer> availableIngredients = 
         new EnumMap<Ingredient, Integer>(Ingredient.class);
   public void orderSandwich(SandwichType sandwichType) {
      Map<Ingredient, Integer> buffer = 
            new EnumMap<Ingredient, Integer>(availableIngredients);
      for (Map.Entry<Ingredient, Integer> entry : 
            sandwichType.getIngredients().entrySet()) {
         Integer currentUnits = buffer.get(entry.getKey());
         buffer.put(entry.getKey(), currentUnits - entry.getValue());
      }     
      availableIngredients.clear();
      availableIngredients.putAll(buffer);
   }
}

我想问一下在这种情况下是否需要临时的、方法本地的、buffer 集合。我的意思是,它可以正常工作,但不确定它的好处。我必须清除我的原始集合并将其替换为缓冲区集合的内容,这基本上是在循环中修改的实际映射。

由于没有缓冲区集合(仅使用我的原始集合),它可以正常工作,我想知道是否推荐一种方法而不是其他方法以及为什么。

非常感谢您就这方面的最佳做法提供任何建议。

【问题讨论】:

  • 您的问题基本上是,“我正在像这样添加 2 和 2:2 + 3 + 2 - 3。它有效,但我不清楚添加和减去 3 的好处。”你真的没有解释为什么这看起来像是一开始就需要的。为什么不直接修改 availableIngredients 呢?我假设,从“玩具程序”的外观来看,您不关心多线程?顺便说一句,使用 google-collections.googlecode.com 中的 EnumMultiset 而不是 Map 似乎对您有所帮助,但我猜如果这是一个学校项目,这无关紧要。

标签: java collections enums map


【解决方案1】:

您不需要buffer 地图。您可以改为在availableIngredients 上操作。一切都会好起来的。没有无用的开销。

【讨论】:

  • 那么上一个答案中提到的 ConcurrentModificationException 风险呢?
  • 我没有发生,我只是想了解使用复制/缓冲区集合的优势。我想在这种情况下没有优势,因为没有并发功能
  • 但话又说回来,我想象 dtsazza 指的是,不是两个不同的线程同时对集合进行操作,而是只有一个线程会同时执行两个操作,这意味着同时迭代和修改
  • 你不会迭代 availableIngredients 所以你可以修改它。
  • joel - 这是我的误读 - Mykola 是对的。模式看起来是为了避免 CME,但事实证明它不是必需的。
【解决方案2】:

这就像是避开ConcurrentModificationExceptions。

你不能在迭代集合时修改它,否则会抛出这样的异常。您发布的内容是处理此问题的常见习语 - 以一种或另一种方式获取集合的副本,然后您可以在修改另一个时迭代其中一个。

即使在单线程代码中也可能发生这种情况 - 例如,这样的事情会在包含至少两个元素的集合上引发异常:

for (Object o : myCollection)
{
   myCollection.remove(o);
}

解决此问题的另一种可能更高效的方法是显式声明迭代器(而不是使用 foreach 循环),然后在适当的情况下使用迭代器的 remove 方法. (但这不适用于您的情况,因为您是在重新映射而不是删除元素)。

编辑:不过,经过反思,availableIngredients 地图没有被循环,因此可以直接修改。事实证明,你感到困惑是对的。 :-) 这可能是以前重构的痕迹,但可以替换为

public void orderSandwich(SandwichType sandwichType) {
  for (Map.Entry<Ingredient, Integer> entry : 
        sandwichType.getIngredients().entrySet()) {
     Integer currentUnits = availableIngredients.get(entry.getKey());
     availableIngredients.put(entry.getKey(), currentUnits - entry.getValue());
  }         
}

如你所料。

刚刚想到的一个想法是,这也可能是一种误导性的尝试,通过减少冲突更新的窗口来降低并发问题的“可能性”。但是,被误导了,因为线程安全是绝对的;让某样东西出现数据竞争的可能性降低十倍并不是对时间的良好投资。它仍然会“随机”失败,因此是不正确的。

【讨论】:

  • 我没有得到。他在迭代时在哪里修改了集合?
  • 谢谢,即使根本没有解决并发问题,集合是否会抛出 ConcurrentModificationException ?我的意思是,我的应用程序不是多线程的,所以不能有多个线程同时在同一个集合上运行
  • 我认为这里有些混乱。该方法正在迭代 SandwichType.getIngredients() 并修改 availableIngredients。也许早期版本正在迭代和修改同一个集合,但我认为这里没有必要。
  • -1 他正在迭代三明治配料并修改可用配料。
  • @Don Kirkby:是的,你是对的。我正在迭代一个集合并实际修改另一个集合 - 感谢您的澄清
【解决方案3】:

在这种情况下您不必使用缓冲区,因为您在修改 sandwichType.getIngredients() 时迭代集合 availableIngredients 这些是单独的集合。

对于使用缓冲区是个好主意的情况,可以像这样更轻松地替换原始文件:

  availableIngredients = buffer;

原来的合集不用更新了,以后用新版本就够了。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-05-13
    • 2018-05-09
    • 1970-01-01
    • 1970-01-01
    • 2015-09-07
    • 1970-01-01
    • 1970-01-01
    • 2020-12-20
    相关资源
    最近更新 更多