Java.util.Map详解

Java为数据结构中的映射提供了一个接口Java.util.Map,此接口主要有四个常用的实现类:HashMap、Hashtable、LinkedHashMap和TreeMap。 继承关系图为:

Java.util.Map详解_第1张图片

下面针对各个实现类的特点做一些说明:

(1)HashMap: 它是根据键的hashcode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度,但遍历顺序却是不确定的。首先了解一下它的存储结构:

Java.util.Map详解_第2张图片

Hashmap实际上是一个数组和链表的结合体(在数据结构中,一般称之为“链表散列“)。当我们往hashmap中put元素的时候,先根据key的hash值得到这个元素在数组中的位置(即下标),然后就可以把这个元素放到对应的位置中了。如果这个元素所在的位子上已经存放有其他元素了,那么在同一个位子上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。从hashmap中get元素时,首先计算key的hashcode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。从这里我们可以想象得到,如果每个位置上的链表只有一个元素,那么hashmap的get效率将是最高的。

那么如何根据key的hashcode来确定元素存放的位置?我们首先想到的就是把hashcode对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是,“模”运算的消耗还是比较大的,能不能找一种更快速,消耗更小的方式那?java中时这样做的:

Java.util.Map详解_第3张图片

首先算得key得hashcode值,然后跟数组的长度-1做一次“与”运算(&)。看上去很简单,其实比较有玄机。比如数组的长度是2的4次方,那么hashcode就会和2的4次方-1做“与”运算。很多人都有这个疑问,为什么hashmap的数组初始化大小都是2的次方大小时,hashmap的效率最高,我以2的4次方举例,来解释一下为什么数组大小为2的幂时hashmap访问的性能最高。

例如:假设数组长度为2^4=16,则length-1表示成二进制为1111。两组的hashcode均为8和9,(1111&1000与1111&1001结果都为1000和1001,不会发生碰撞,但是如果数组长度为15,length-1=1110,则1110&1000与1110&1001结果为1000,发生碰撞)但是很明显,当它们和1110“与”的时候(),产生了相同的结果,也就是说它们会定位到数组中的同一个位置上去,这就产生了碰撞,8和9会被放到同一个链表上,那么查询的时候就需要遍历这个链表,得到8或者9,这样就降低了查询的效率。

此外,HashMap继承自AbstractMap类。HashMap最多只允许一条记录的键为null,允许多条记录的值为null。HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap(这里的synchronizedMap和concurrentHashMap下节再行介绍)。

(2)HashTable

Hashtable继承自Dictionary类 ,它也是无序的,但是Hashtable是线程安全的,同步的,即任一时刻只有一个线程能写Hashtable.HashTable中不允许有null的key和value.

(3)LinkedHashMap

LinkedHashMap是Map中常用的有序的两种实现之一, 它保存了记录的插入顺序,先进先出。
对于LinkedHashMap而言,它继承与HashMap,底层使用哈希表与双向链表来保存所有元素。其基本操作与父类HashMap相似,它通过重写父类相关的方法,来实现自己的链接列表特性。LinkedHashMap采用的hash算法和HashMap相同,但是它重新定义了数组中保存的元素Entry,该Entry除了保存当前对象的引用外,还保存了其上一个元素before和下一个元素after的引用,从而在哈希表的基础上又构成了双向链接列表,效果图如下:

Java.util.Map详解_第4张图片

(4)TreeMap

TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。

你可能感兴趣的:(数据结构,数据结构)