在本教程中,我们将重点讨论 TreeMap和HashMap的核心区别。
TreeMap 和HashMap 非常相似,都是实现了Map接口的集合。但它们也有一些区别,在某些情况下,一个比另一个更好。让我们来看看这些区别。
1.HashMap 和TreeMap的区别
让我们讨论一下这两种地图之间的一些主要区别。
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.性能比较
- HashMap 比TreeMap快,在没有哈希碰撞的最佳情况下,对于最基本的操作如
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.总结
在这篇文章中,我们看到了HashMap 和TreeMap 之间的一些关键区别,以及在我们的代码中使用它们时,我们可以根据哪些因素来决定这两者的区别。