【问题标题】:TreeMap vs HashMap in java8java8中的TreeMap与HashMap
【发布时间】:2017-08-28 15:22:45
【问题描述】:

根据 Java 8 中的this 链接,为了避免地图中的冲突 (HashMap, LinkedHashMap, and ConcurrentHashMap) 使用 平衡树 而不是 LinkedList 来实现。

那么有什么区别:

  1. TreeMap 和其他地图 (HashMap, LinkedHashMap, and ConcurrentHashMap) 都是使用 self-balancing-trees 实现的,因为最坏情况下的可访问性是相同的。
  2. entry的排序我可以实现如下:

    public <K extends Comparable,V extends Comparable> LinkedHashMap<K,V> sortByKeys(LinkedHashMap<K,V> map){
        List<K> keys = new LinkedList<K>(map.keySet());
        Collections.sort(keys, (Comparator<? super K>) new Comparator<String>() {
            @Override
            public int compare(String first, String second) {                
                return first.compareTo(second);
            }
        });
    }
    

除了排序和可访问性之外,TreeMap 的其他属性是什么?

【问题讨论】:

  • 你的观点是什么?在 #1 中,您暗示没有区别,因为 worst case 是相同的。假设你总是有最坏的情况,那你就太悲观了。在 #2 中,您说您总是可以在需要时对键进行排序,但是排序很昂贵。 TreeMap 总是被排序的,所以如果Map 不断地被修改,并且你不断地需要按顺序排列的结果,就会有巨大的 性能差异。 HashMap 性能更好(假设有少量哈希函数)并且不需要键是可比较的TreeMap 如果要求键是有序的更好。
  • ... 此外,TreeMap 可以为您提供给定键值的相邻键,无论键值是否在映射中。 HashMap 不能这样做。排序列表无法做到这一点,除非您在浪费时间首先对列表进行排序之后执行二进制搜索。
  • 问题是什么?
  • Java 8 将 HashMaps 更改为使用平衡树进行冲突这一事实纯粹是 性能提升,将性能从 O(n) 更改为O( log n) collissions。它没有任何功能意义/影响。您的整个问题似乎是基于对具有功能意义的更改的假设,并且被误导以至于问题毫无意义,因此所有的反对票。如果您对 HashMapTreeMap 有疑问,请提出问题,但这仍然与 Java 8 中实现的性能改进无关。
  • @RameshPapaganti 为什么你如此关注最坏情况?如果您确实有一个包含 100 多个键的映射(请记住,键是唯一的,不能相同)并且映射中的 每个 键具有完全相同的哈希码,那么您的哈希算法有缺陷,你应该修复它,而不是考虑HashMap vs TreeMap

标签: java java-8 hashmap collision treemap


【解决方案1】:
  1. (TreeMap 和其他地图(HashMap、LinkedHashMap 和 ConcurrentHashMap)都是使用自平衡树实现的,因为最坏情况下的可访问性是相同的。

    在 jdk7 和旧 jdk recalculate bucket index size >= threshold 时,每个桶作为 linked list,但在 jdk8 中调整 balancing-trees,每个桶作为 Red-Black Tree

    在 jdk7 中将这样的东西放入 HasMap 时只有一个桶。那么查找节点是O(N)

    class Foo{
       int hashCode(){
           return 0;
       }
    }
    Foo first=new Foo();
    Foo last=new Foo();
    map.put(first,"anything");
    map.put(last,"anything");
    map.get(last | first);// O(N)
    

    在 jdk8 中将这样的东西放入 HasMap 时只有一个桶,但密钥实现了Comparable。那么查找节点是O(log(N)),但是如果i 相同,查找节点将回退到O(N)。

    class Foo implements Comparable<Foo> {
        private int i;
    
        public Foo(int i) {
    
            this.i = i;
        }
    
        public int hashCode() {
            return 0;
        }
    
        @Override
        public int compareTo(Foo that) {
            return i - that.i;
        }
    }
    
    Foo first=new Foo(1);
    Foo last=new Foo(2);
    map.put(first,"anything");
    for(int i=2;i<threshold;i++)
      map.put(new Foo(i + 1), "anything");
    map.put(last,"anything");
    map.get(last | first);// O(log(N))
    
  2. 对 LinkedHashMap 进行排序,您可以创建一个 TreeMap 实例,因为 Map.keySet() 返回无序集而不是列表,因此您无法通过对其键进行排序来对地图进行排序。

    public <K extends Comparable<? super K>, V> Map<K, V> sortByKeys(Map<K, V> map) {
     return new TreeMap<K, V>(map);
    }
    

【讨论】:

  • @Holger 请帮我看看这个答案是否正确?先谢谢了。
  • 新的HashMap 实现仍会在大小超过阈值 (loadfactor*size) 时调整表的大小/重新散列。当每个桶的元素数量低于阈值时,它仍然使用链表(不要像类型名 LinkedList 那样写它)。当超过阈值时,将创建一个首先使用哈希码的平衡树,因此对不同哈希码的桶冲突进行排序,无论键是否可比。只有对于真正相同的哈希码,它才会尝试使用自然顺序,如果有的话。
  • 仍有可能 a) compareTo 返回零但 equals 返回 false(将 last=new Foo(2) 更改为 last=new Foo(1))或 b) 密钥不是 Comparable,在这种情况下,地图基本上会退回到带有O(n) 查找的链表。但是,必须强调的是,对于树查找的O(log(n))n 是桶的数量,对于回退的O(n)n 是具有相同哈希码的对象的数量,没有&lt;&gt; 关系,所以n 不等于HashMap 的大小。
  • 在您的示例中只有两个键,无论如何都不会有树节点......
  • 第二部分,方法应该是public &lt;K extends Comparable&lt;? super K&gt;, V&gt; Map&lt;K, V&gt; sortByKeys(Map&lt;K, V&gt; map) { return new TreeMap&lt;&gt;(map); }。 1) K extends Comparable 引用原始类型 Comparable。 2)将参数类型缩小为LinkedHashMap没有意义;任何Map 都可以,结果顺序由TreeMap 决定,输入映射是否有顺序。
猜你喜欢
  • 1970-01-01
  • 2021-04-29
  • 2018-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-22
  • 2013-12-11
相关资源
最近更新 更多