HashMap
和TreeMap
都是 Java 中实现键值对映射的类,两者在数据结构、顺序性、时间复杂度和线程安全方面存在差异。
二者区别:
数据结构:
HashMap
:底层实现为哈希表(Hash Table),使用数组和链表/红黑树实现。使用键的hashCode()
进行计算桶下标,将值存储在对应的桶中。当发生哈希冲突时,会以链表或红黑树的形式将相同 hash 值的元素连接在一起。TreeMap
:底层实现为红黑树(Red-Black Tree),是一种自平衡二叉查找树,根据 Key 值进行排序。查找 insert/delete 的时间复杂度为 O(logN)。
排序能力:
HashMap
:不保证 Key-Value 映射的插入顺序与遍历顺序相同。具有随机存取特性,访问元素速度快,适合大规模数据存储,但其存储顺序不能保证恒定。TreeMap
:每次插入元素都会使树重新调整,将所有元素排序后以缩进列表方式展示。具有重载优化特性,每一次插入只需要更改相关节点指向,速度稳定。
性能:
-
HashMap
:- insert:平均为 O(1),最坏情况可能退化成 O(n);
- delete:平均为 O(1),最坏情况可能退化成 O(n);
- search/get:平均为 O(1),最坏情况可能退化成 O(n)。
-
TreeMap
:- insert:每次将节点插入到红黑树中,时间复杂度平均为 O(logN);
- delete:删除节点后调整节点替代者连接以保持树的性质,时间复杂度平均为 O(logN);
- search/get:二分查找,时间复杂度平均为 O(logN)。
从时间复杂度角度看,
HashMap
插入、删除和随机访问元素都具有常数复杂度 O(1),但并不保证最坏情况下的稳定性;而TreeMap
的插入、查询和删除操作的时间复杂度都为 O(logN),稳定性更好,但在数据量较大时表现可能会相对较差。此外,HashMap
在考虑键值映射唯一性时需要考虑键值的HashCode
计算和哈希函数冲突问题,而TreeMap
没有这个问题。
等价比较:
HashMap
判断两个键是否“相等”的标准是 hashCode()
和 equals()
方法,而 TreeMap
判断两个键是否“相等”的标准是 comparator()
或 compareTo()
方法。所以TreeMap的等价比较会更加的灵活点
线程安全:
HashMap
和 TreeMap
都是非线程安全的集合,即它们不支持多线程并发访问。如果在无并发保护的情况下同时进行多个写操作,则可能导致数据丢失、数据结构损坏等问题。
使用适当的同步工具
HashMap
可以使用Collections.synchronizedMap()
方法或者ConcurrentHashMap
类来提供线程安全的访问方式。其中synchronizedMap()
方法返回一个同步的Map
包装器,在需要同步时将方法调用委托给原始Map
,从而确保线程安全。TreeMap
不提供直接的实现来获得线程安全,因此可以考虑通过读写锁ReentrantReadWriteLock
或其他同步工具来确保并发访问的安全。
避免在多线程环境下直接修改集合
另外一种方法是避免在多线程环境下修改导致并发问题的集合(例如添加/删除元素)。在多线程访问时,可以使用 CopyOnWriteArrayList
等类,即对原有数据进行快照形成新的数组,由于通常在快照 (也就是对象内部存储的数组) 上执行写操作,所以所有副本可以自由地读取。
在多线程读取或遍历时避免遍历未同步的集合。对于高度竞争的数据访问,可以考虑其它采用单例、缓存或异步等方式来保证安全性。
选型建议:
对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。
- 对于少量数据元素的存储,使用
TreeMap
合适(O(NlogN) 的常数较小)。 - 对于元素数量相对更多的场景,可以使用 hash 实现的
HashMap
(O(1) 的时间复杂度)