首页 > 代码库 > 深入理解 hash 函数、HashMap、LinkedHashMap、TreeMap 【中】
深入理解 hash 函数、HashMap、LinkedHashMap、TreeMap 【中】
LinkedHashMap - 有序的 HashMap
我们之前讲过的 HashMap 的性能表现非常不错,因此使用的非常广泛。但是它有一个非常大的缺点,就是它内部的元素都是无序的。如果在遍历 map 的时候, 我们希望元素能够保持它被put进去时候的顺序,或者是元素被访问的先后顺序,就不得不使用 LinkedHashMap。
LinkdHashMap 继承了 HashMap,因此,它具备了 HashMap 的优良特性-高性能。在HashMap 的基础上, LinkedHashMap 又在内部维护了一个链表,用来存放元素的顺序。因此,我们可以将 LinkedHashMap 理解为在内部增加了一个链表的 HashMap。
LinkedHashMap 提供了两种类型的顺序:元素插入的顺序 和 元素最近访问的顺序。可以通过下面构造函数指定排序方式。
/** * Constructs an empty <tt>LinkedHashMap</tt> instance with the * specified initial capacity, load factor and ordering mode. * * @param initialCapacity the initial capacity * @param loadFactor the load factor * @param accessOrder the ordering mode - <tt>true</tt> for * access-order, <tt>false</tt> for insertion-order * @throws IllegalArgumentException if the initial capacity is negative * or the load factor is nonpositive */ public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }
accessOrder 为 true 时为访问顺序排列,false 为插入顺序排列。
通过上节我们知道,HashMap 中的 Entry 包括 key,value,next 指针以及 hash 值。而 LinkedHashMap 通过继承它,实现了 LinkedHashMap.Entry , 为 HashMap.Entry 增加了 before 和 after 属性用来记录某一表项的前驱和后继,并构成循环链表。因此,它的 Entry table 结构如下图:
以下代码实例,展示了 LinkedHashMap 的有序性:
public static void main(String[] args){ Map<String , String > map = new LinkedHashMap(); map.put("1", "aa"); map.put("2", "bb"); map.put("3", "cc"); map.put("4", "dd"); map.get("3"); for(Iterator<String> it = map.keySet().iterator();it.hasNext();){ String name = it.next(); System.out.println(name+"-->"+map.get(name)); } }
输出结果为:
1-->aa
2-->bb
3-->cc
4-->dd
如果将实现改为 HashMap,则输出结果为:
3-->cc
2-->bb
1-->aa
4-->dd
可以看到,LinkedHashMap 迭代中,很好的保持了元素输入时候的顺序。我们还可以将其顺序改为按照访问时间排序:
Map<String , String > map = new LinkedHashMap(16,0.75f,true);
结果出乎意料,抛出了异常:java.util.ConcurrentModificationException 。
我们知道,实现了 Iterable 接口都会在集合内部维护一个 modcount 的变量,当集合元素被改动的时候,便会自动递增。这是防止使用迭代器的时候,代码修改原集合状态。这种情况一般是由于用户迭代过程中调用了 remove add 等方法。但这里为什么会报这种异常呢?问题就在 LinkedHashMap 的 get 方法上。一般认为 get 方式是只读的,但是当前的 LinkedHashMap 工作在元素访问顺序排序的模式中,get 方法会修改 LinkedHashMap 中的链表结构,以便将最近访问的元素放置到链表的末尾,因此,LinkedHashMap 工作在这种模式的时候,不能使用 get 操作。
TreeMap - 另一种 Map 实现
HashMap 通过 hash 算法可以最快速的进行 put() 和 get() 操作。TreeMap 则提供了一种完全不同的 Map 实现。从功能上讲,TreeMap 有着比 HashMap 更为强大的功能,它实现了 SortedMap 接口,这意味着它可以对元素进行排序。而且与 LinkedHashMap 基于元素进入顺序和元素访问顺序排列相比,TreeMap 是基于元素的固有顺序排列的(由 Comparator 或者 Comparable 确定)。然而,TreeMap 的性能略低于 HashMap 。
虽然性能略有逊色,但是,如果在开发中需要对元素进行排序,TreeMap 就成为了不二选择。TreeMap 提供的有关排序的接口如下:
public SortedMap<K,V> subMap(K fromKey, K toKey) public SortedMap<K,V> headMap(K toKey) public SortedMap<K,V> tailMap(K fromKey) public K firstKey() public K lastKey()由于 TreeMap 根据 key 进行排序,为了确定 key 的顺序,有两种方式:
1) 在 TreeMap 的构造函数中注入一个 Comparator。
public TreeMap(Comparator<? super K> comparator)
2) 使用实现了 Comparable 接口的 key。
对 TreeMap 而言,排序是一个必须的过程,因此,要正常使用 TreeMap ,必须指定排序规则,否则会抛出 java.lang.ClassCastException 异常。
TreeMap 的内部实现是基于红黑树的。红黑树是一种平衡查找树,它的统计性能要由于平衡二叉树。有良好的最坏运行时间,可以再 O(log n) 时间内做查找、插入和删除,n 表示树中元素的个数。
下面以一个实例展示 TreeMap 对排序的支持。假设现在需要对学生的成绩进行统计,以学生的成绩排序,需要通过统计的范围筛选出符合条件的学生,并查找这些学生的详细信息。这样的功能仅使用 HashMap 来时间是相当费力的,排序算法也需要在应用程序中自己实现,其效率也取决于程序员对算法的理解,较难得到保证。而是用 TreeMap 则可以高效且方便的实现以上功能。
public class Student implements Comparable<Student> { public Student(String name, int score) { this.name = name; this.score = score; } String name; int score; @Override public int compareTo(Student o) { return score == o.score ? 0 : score > o.score ? 1 : -1; } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append("name:"); sb.append(name); sb.append(" "); sb.append("score:"); sb.append(score); return sb.toString(); } public static void main(String[] args) { Map<Student, StudentDetailInfo> map = new TreeMap(); Student s1 = new Student("Billy", 70); Student s2 = new Student("David", 85); Student s3 = new Student("Kite", 92); Student s4 = new Student("Cissy", 68); map.put(s1, new StudentDetailInfo(s1)); map.put(s2, new StudentDetailInfo(s2)); map.put(s3, new StudentDetailInfo(s3)); map.put(s4, new StudentDetailInfo(s4)); // 筛选出成绩介于 Cissy 和 David 之间的所有学生 Map map1 = ((TreeMap) map).subMap(s4, s2); for (Iterator<Student> it = map1.keySet().iterator(); it.hasNext();) { Student key = it.next(); System.out.println(key + "->" + map1.get(key)); } System.out.println("subMap end"); // 筛选出成绩低于 Billy的所有学生 Map map2 = ((TreeMap) map).headMap(s1); for (Iterator<Student> it = map2.keySet().iterator(); it.hasNext();) { Student key = it.next(); System.out.println(key + "->" + map2.get(key)); } System.out.println("headMap end"); // 筛选出成绩大于等于 Billy的所有学生 Map map3 = ((TreeMap) map).tailMap(s1); for (Iterator<Student> it = map3.keySet().iterator(); it.hasNext();) { Student key = it.next(); System.out.println(key + "->" + map3.get(key)); } System.out.println("tailMap end"); } } class StudentDetailInfo { Student s; public StudentDetailInfo(Student s) { this.s = s; } @Override public String toString() { return s.name + "'s detail infmation"; } }
运行结果如下:
name:Cissy score:68->Cissy‘s detail infmation
name:Billy score:70->Billy‘s detail infmation
subMap end
name:Cissy score:68->Cissy‘s detail infmation
headMap end
name:Billy score:70->Billy‘s detail infmation
name:David score:85->David‘s detail infmation
name:Kite score:92->Kite‘s detail infmation
tailMap end
可以看到,TreeMap 提供了简明的接口对有序的 key 集合进行筛选,其结果集也是一个有序的 Map。同时,TreeMap 在最坏的情况下也可以在 O(log n) 时间内做查找、插入和删除。因此,就一个实现了排序功能的 Map 而言,TreeMap 是十分高效的。
深入理解 hash 函数、HashMap、LinkedHashMap、TreeMap 【中】