讨论 TreeMap和HashMap的核心区别

158 阅读4分钟

在本教程中,我们将重点讨论 TreeMapHashMap的核心区别。

TreeMapHashMap 非常相似,都是实现了Map接口的集合。但它们也有一些区别,在某些情况下,一个比另一个更好。让我们来看看这些区别。

1.HashMapTreeMap的区别

让我们讨论一下这两种地图之间的一些主要区别。

1.1.类的层次结构

HashMap 类扩展了AbstractMap 类并实现了Map 接口,而*TreeMap* 类扩展了AbstractMap 类并实现了 可导航地图 接口

// HashMap class declaration
public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable

// TreeMap class declaration
public class TreeMap<K, V> extends AbstractMap<K, V> implements NavigableMap<K, V>, Cloneable, Serializable

1.2.内部实现

  • HashMap 内部使用*HashTable* ,并按照Hashing的原理工作。它以*LinkedList的形式包含桶当桶中有超过8个条目时,LinkedList 就会转变为平衡树* (TreeNodes)。
  • TreeMap内部使用 红黑树,一种自平衡的 二进制搜索树。

1.3.空键和空值

TreeMap 不允许空键 ,但可以包含任意数量的空值.

HashMapHashMap类允许一个空键(对于其他空键,现有的值将简单地用一个新值覆盖)和任意数量的空值

// Putting null key in TreeMap
TreeMap<String, String> map = new TreeMap<>();

map.put(null, "value");    //Exception in thread "main" java.lang.NullPointerException

TreeMap 内部使用compareTo()或compare()方法,这些方法分别来自于Comparable和 Comparator接口,以保持地图中元素的顺序,如果出现空键,这些方法会抛出"NullPointerException"。

1.4.功能

HashMap相比,TreeMap 在功能上更加丰富。除了Map 接口的正常方法(get(), put(), remove()) ,它还包含了NavigableMap接口的方法,如pollFirstEntry(), pollLastEntry(), tailMap(), firstKey(), lastKey() ,等等,这是HashMap 类所没有的。

// Creating TreeMap
TreeMap<String, String> map = new TreeMap<>();

// Putting values in TreeMap
map.put("key1", "value1");
map.put("key2", "value2");
map.put("key3", "value3");

// Printing map
System.out.println(map);                       // Prints {key1=value1, key2=value2, key3=value3}

// Getting first key from map
System.out.println(map.firstKey());              // Prints key1
// Getting last key from map
System.out.println(map.lastKey());             // Prints key3

// Getting first entry from map
System.out.println(map.firstEntry());         // Prints key1=value1
// Polling last entry from map
System.out.println(map.pollLastEntry());       // Prints key3=value3

// Printing map again
System.out.println(map);                           // Prints {key1=value1, key2=value2}

1.5.元素排序

HashMap 不为其元素保持任何顺序,也就是说,它不会提供任何保证,即在地图的迭代过程中,首先插入地图的元素将首先被打印出来。

TreeMap 按照其键的排序顺序存储元素。排序可以是默认的自然排序(数字的升序和字符串的字母顺序)或基于创建地图时指定的 比较器在地图创建过程中指定的对象。

// Creating HashMap
HashMap<Integer, String> map = new HashMap<>();

// Putting values in the map
map.put(10, "value1");
map.put(2, "value2");
map.put(13, "value3");
map.put(5, "value4");
map.put(25, "value5");

// Printing map
System.out.println(map);   //{2=value2, 5=value4, 25=value5, 10=value1, 13=value3}  - No ordering
// Creating TreeMap using normal TreeMap() constructor which sorts the elements 
// based on natural sorting order of keys
TreeMap<Integer, String> map = new TreeMap<>();

// Putting values in map
map.put(10, "value1");
map.put(2, "value2");
map.put(13, "value3");
map.put(5, "value4");
map.put(25, "value5");

// Printing map
System.out.println(map);  //{2=value2, 5=value4, 10=value1, 13=value3, 25=value5}


// Creating TreeMap using TreeMap(Comparator) constructor by specifying Comparator object 
// as Lambda expression which sorts the elements according to customized sorting of keys
map = new TreeMap<Integer,String>((I1,I2) -> (I1<I2) ? 1 : (I1>I2) ? -1 : 0); 

// Putting values in map
map.put(10, "value1");
map.put(2, "value2");
map.put(13, "value3");
map.put(5, "value4");
map.put(25, "value5");

// Printing map
System.out.println(map);  //{25=value5, 13=value3, 10=value1, 5=value4, 2=value2}

1.6.性能比较

  • HashMapTreeMap快,在没有哈希碰撞的最佳情况下,对于最基本的操作如get(), put(), contains() & remove() ,提供了恒定时间性能 O(1)
  • 在发生哈希碰撞的情况下(两个键具有相同的哈希码),HashMap 通过使用LinkedList来存储碰撞的元素,因此在这种情况下,性能降低到 O(n)
  • 为了提高HashMap 在碰撞时的性能,当一个桶中的条目数超过8个时,LinkedList 会转变为 平衡树 ,从而将最坏情况下的 性能从O(n)提高到O(log(n))。

另一方面,TreeMap 为大多数基本操作如,提供了一个性能 O(log(n)) 对于大多数基本操作如get(), put(), contains() & remove()

1.7.内存使用

TreeMap 在内存管理方面有更好的性能,因为它不在内部维护一个数组来存储键值对。

HashMap中,数组的大小是在初始化或调整大小时确定的,这往往超过了当时的需要。这就浪费了内存。而TreeMap则不存在这样的问题。

1.8.关键搜索

HashMap 在比较地图的键时使用hashCode()equals() 方法,而TreeMap在比较键时使用compareTo()compare() 方法。

// Creating HashMap
HashMap<Integer, String> hashMap = new HashMap<>();

// Putting values in map
hashMap.put(10, "value1");
hashMap.put(10, "value2");
System.out.println("HashMap: " + hashMap);

// Creating TreeMap
TreeMap<Integer, String> treeMap = new TreeMap<>();

// Putting values in map
treeMap.put(10, "value1");
treeMap.put(10, "value2");
System.out.println("TreeMap: " + treeMap);

请注意程序的输出。尽管两种情况下的输出是一样的,但在内部,HashMap 在比较键的时候使用了equals(),并拒绝第二个键,因为它是一个重复的。而TreeMap 在比较键的时候使用compareTo(),因此拒绝了第二个键。

另外,两个地图都更新了前一个条目,而地图上只有一个条目。

HashMap: {10=value2}
TreeMap: {10=value2}

2.何时使用HashMap和TreeMap

如果我们需要以排序的方式添加元素(键值对),我们应该使用TreeMap 。让我们举个例子,创建一个字典 ,其中的单词按字母顺序排序。所以我们可以用TreeMap轻松地实现这一点。

TreeMap内存效率更高,所以在我们不确定要存储在内存中的元素数量的情况下,它是一个很好的地图实现。

HashMap 更像是一种通用的地图实现,可以用在我们不需要对数据进行任何排序的地方,而且条目可以按照任何顺序或次序来维护。在高性能应用中,我们可以选择使用HashMap 而不是TreeMap ,因为它与TreeMap相比性能更好。

3.总结

在这篇文章中,我们看到了HashMapTreeMap 之间的一些关键区别,以及在我们的代码中使用它们时,我们可以根据哪些因素来决定这两者的区别。