【问题标题】:What's the quickest way to remove an element from a Map by value in Java?在 Java 中按值从 Map 中删除元素的最快方法是什么?
【发布时间】:2010-11-23 02:01:16
【问题描述】:

在 Java 中按值从 Map 中删除元素的最快方法是什么?

目前我正在使用:

    DomainObj valueToRemove = new DomainObj();
    String removalKey = null;

    for (Map.Entry<String, DomainObj> entry : map.entrySet()) {
        if (valueToRemove.equals(entry.getValue())) {
            removalKey = entry.getKey();
            break;
        }
    }

    if (removalKey != null) {
        map.remove(removalKey);
    }

【问题讨论】:

  • 我正在使用 Java 的 HashMap
  • 为什么要删除 if 语句中的元素?不安全吗?

标签: java performance collections


【解决方案1】:
map.values().removeAll(Collections.singleton(null));

参考How to filter "Null" values from HashMap<String, String>?,我们可以为java 8做以下:

map.values().removeIf(valueToRemove::equals);

【讨论】:

  • 但是问题不是问空值吗?
  • 只是一个例子,注意Collections.singleton(null)
【解决方案2】:

正确且快速的单线实际上是:

while (map.values().remove(valueObject));

有点奇怪,上面的大多数示例都假设 valueObject 是唯一的。

【讨论】:

  • 适用于基本用例,不适用于提供的谓词。
【解决方案3】:

这是单行解决方案:

map.values().remove(valueToRemove);

这可能比定义自己的迭代器要快,因为 JDK 集合代码已经过显着优化。

正如其他人所提到的,bimap 将具有更快的值删除,尽管它需要更多的内存并且需要更长的时间来填充。此外,bimap 仅在值唯一时才有效,这在您的代码中可能是也可能不是。

【讨论】:

  • 但是如果 valueToRemove 在地图中出现两次,这段代码不是只删除一个值而不是两个吗?
【解决方案4】:

迭代器的更短用法是使用 values() 迭代器。

DomainObj valueToRemove = new DomainObj();
for (Iterator<DomainObj> it = map.values().iterator(); it.hasNext();)) {
        if (valueToRemove.equals(it.next())) {
                it.remove();
                break;
        }
}

【讨论】:

    【解决方案5】:

    我们知道这种情况很少发生,但非常有帮助。我会更喜欢 BidiMap 而不是 org.apache.commons.collections

    【讨论】:

      【解决方案6】:

      就像大多数其他发帖人所说的那样,这通常是一个 O(N) 操作,因为无论如何您都必须查看整个哈希表值列表。 @tackline 有正确的解决方案将内存使用量保持在 O(1) (我给了他一个赞成票)。

      您的另一个选择是为了速度而牺牲内存空间。如果您的地图大小合理,您可以并行存储两张地图。

      如果你有一个 Map,那么维护一个与之并行的 Map。当您在一张地图上插入/删除时,也在另一张地图上进行。当然这更难看,因为您在浪费空间,并且您必须确保正确编写 DomainObj 的“hashCode”方法,但是您的删除时间从 O(N) 下降到 O(1),因为您可以查找密钥/object 以恒定时间向任一方向映射。

      通常不是最好的解决方案,但如果您最关心的是速度,我认为这可能与您将获得的一样快。

      ===================== 附录:这基本上是@msaeed 建议的,只是没有第三方库。

      【讨论】:

        【解决方案7】:

        我认为这在您的应用程序的生命周期中不会只发生一次。

        所以我要做的是将维护对添加到该地图的对象的引用的责任委托给另一个对象。

        所以下次你需要删除它时,你使用那个“反向映射”......

         class MapHolder { 
        
             private Map<String, DomainObj> originalMap;
             private Map<DomainObj,String> reverseMap;
        
             public void remove( DomainObj value ) {
                   if ( reverseMap.contains( value ) ) { 
                         originalMap.remove( reverseMap.get( value ) );
                         reverseMap.remove( value );
                   }
             }
          }
        

        这比迭代要快得多。

        显然,您需要使它们保持同步。但是,如果您重新编写代码以让一个对象负责地图的状态,那应该不会那么难。

        请记住,在 OOP 中,我们有具有状态和行为的对象。如果您的数据在各处传递变量,则您在对象之间创建了不必要的依赖关系

        是的,改正代码需要一些时间,但是改正代码所花费的时间,会为你以后省去很多麻烦。想想看。

        【讨论】:

        • 这是一个“重新发明轮子”的案例,或者在这种情况下是双向 Map。 Google Collections 提供了全面的实施。
        • 要求值是唯一的,但不一定如此。
        • @Steve:当然,应该添加更多代码(例如实现 Map 接口以能够同步 put 和 get)这更像是建议的“草图”和不是真正的“准备复制/粘贴”解决方案。是的,作为一种替代方案,可能有谷歌集合,但同样的原则适用,应该有一个对象负责该地图和双向,而不是在代码周围传递引用。感谢您的评论。
        • @Thorbj... .. o rn Ravn Andersen。 (对不起,我没有找到那个o)......你是对的。该值应具有关联的键列表,第一个将是要删除的键....当列表中没有更多键时,将删除整个值。我想过,但没有发布,因为它会创建一个丑陋的代码。这就是为什么我说:“显然你需要保持......”但我同意你的看法。
        【解决方案8】:

        如果你没有反向映射,我会选择迭代器。

        DomainObj valueToRemove = new DomainObj();
        
        for (
            Iterator<Map.Entry<String, DomainObj>> iter = map.entrySet().iterator();
            iter.hasNext();
        ) {
            Map.Entry<String, DomainObj> entry = iter.next();
            if (valueToRemove.equals(entry.getValue())) {
                iter.remove();
                break; // if only want to remove first match.
            }
        }
        

        【讨论】:

        • 同意。我要提到的唯一另一件事是,如果您需要经常按值删除,请确保 Map 是您应该用于您的场景的实际数据结构。
        【解决方案9】:

        我会用这个

         Map x = new HashMap();
        x.put(1, "value1");
        x.put(2, "value2");
        x.put(3, "value3");
        x.put(4, "value4");
        x.put(5, "value5");
        x.put(6, "value6");
        
        x.values().remove("value4");
        

        编辑: 因为对象是由“指针”而不是值来引用的。

        没有

        【讨论】:

          【解决方案10】:

          您始终可以使用 values 集合,因为对该集合所做的任何更改都会导致更改反映在地图中。因此,如果您要调用 Map.values().remove(valueToRemove) 应该可以工作-尽管我不确定您是否会看到比该循环更好的性能。一种想法是扩展或覆盖 map 类,这样后备集合总是按值排序 - 这将使您能够对可能更快的值进行二进制搜索。

          编辑:这与 Alcon 的答案基本相同,除了我认为他不会起作用,因为 entrySet 仍将按键排序 - 在这种情况下,您不能使用该值调用 .remove() 。

          这还假设该值应该是唯一的,或者您也希望从地图中删除任何重复项。

          【讨论】:

            【解决方案11】:

            如果您无法从 DomainObj 中找出密钥,那么我看不出您如何改进它。没有内置方法可以从值中获取键,因此您必须遍历地图。

            如果这是您一直在做的事情,您可能会维护两个映射(string->DomainObj 和 DomainObj->Key)。

            【讨论】:

            • +1。如果您想优化,您应该找到一种方法将您的值存储为某种键。如果你不能,你就别无选择:你必须迭代。
            【解决方案12】:

            如果不使用双向地图(commons-collectionsgoogle collections 有它们),您将无法迭代地图

            【讨论】:

            • +1 推荐谷歌收藏 BiMap。它更快,但会带来内存开销。
            • 或者你可以维护两张地图,这就是这些地图的作用。
            猜你喜欢
            • 2011-08-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-08-12
            • 2012-02-01
            • 1970-01-01
            • 2015-07-03
            • 1970-01-01
            相关资源
            最近更新 更多