深入理解 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 是十分高效的。

时间: 2024-12-25 01:27:13

深入理解 hash 函数、HashMap、LinkedHashMap、TreeMap 【中】的相关文章

深入理解 hash 函数、HashMap、LinkedHashMap、TreeMap 【上】

前言 Map 是非常常用的一种数据接口.在 Java 中,提供了成熟的 Map 实现. 图 1 最主要的实现类有 Hashtable.HashMap.LinkedHashMap和 TreeMap.在 HashTable 的子类中,还有 Properties的实现.Properties 是专门读取配置文件的类,我们会在稍后介绍.这里首先值得关注的是 HashMap 和 HashTable 两套不同的实现,两者都实现了 Map 接口.从表面上看,并没有多大差别,但是在内部实现上却有些微小的细节. 首

HashMap,LinkedHashMap,TreeMap的区别(转)

Map主要用于存储健值对,根据键得到值,因此不允许键重复(重复了覆盖了),但允许值重复.Hashmap 是一个最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的.HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null;HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致.如果需要同步,可以用 Collections的synchronizedM

HashMap,LinkedHashMap,TreeMap的区别

Map主要用于存储健值对,根据键得到值,因此不允许键重复(重复了覆盖了),但允许值重复.Hashmap 是一个最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序是完全随机的.HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null;HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致.如果需要同步,可以用 Collections的synchronizedM

HashMap,LinkedHashMap,TreeMap之间的区别

Map 主要用于存储键(key)值(value)对,根据键得到值,因此键不允许键重复,但允许值重复. HashMap 是一个最常用的Map,它根据键的HashCode 值存储数据,根据键可以直接获取它的值,具有很快的访问速度.HashMap最多只允许一条记录的键为Null;允许多条记录的值为Null;HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap;可能会导致数据的不一致.如果需要同步,可以用Collections的synchronizedMap方法使HashMap具

Bitset&lt;&gt;用于unordered container时的默认hash函数

自从c++11起,bitset用于unordered container,将会提供默认的hash函数. 在gcc中,相关代码如下: 01495 // DR 1182. 01496 /// std::hash specialization for bitset. 01497 template<size_t _Nb> 01498 struct hash<_GLIBCXX_STD_D::bitset<_Nb>> 01499 : public std::unary_functi

深入理解HashMap(及hash函数的真正巧妙之处)

原文地址:http://www.iteye.com/topic/539465 Hashmap是一种非常常用的.应用广泛的数据类型,最近研究到相关的内容,就正好复习一下.网上关于hashmap的文章很多,但到底是自己学习的总结,就发出来跟大家一起分享,一起讨论. 1.hashmap的数据结构 要知道hashmap是什么,首先要搞清楚它的数据结构,在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,hashmap也不例外.

java学习笔记——Java中HashMap和TreeMap的区别深入理解

本文转载自Java中HashMap和TreeMap的区别深入理解 首先介绍一下什么是Map.在数组中我们是通过数组下标来对其内容索引的,而在Map中我们通过对象来对对象进行索引,用来索引的对象叫做key,其对应的对象叫做value.这就是我们平时说的键值对. HashMap通过hashcode对其内容进行快速查找,而 TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的). HashMap 非线程安全 

Java中HashMap和TreeMap的区别深入理解(转载)

首先介绍一下什么是Map.在数组中我们是通过数组下标来对其内容索引的,而在Map中我们通过对象来对对象进行索引,用来索引的对象叫 做key,其对应的对象叫做value.这就是我们平时说的键值对. HashMap通过hashcode对其内容进行快速查找,而 TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应 该使用TreeMap(HashMap中元素的排列顺序是不固定的). HashMap 非线程安全 TreeMap 非线程安全 线程安全 在Java里,线程安全一

Java中HashMap和TreeMap的区别深入理解

首先介绍一下什么是Map.在数组中我们是通过数组下标来对其内容索引的,而在Map中我们通过对象来对对象进行索引,用来索引的对象叫做key,其对应的对象叫做value.这就是我们平时说的键值对. HashMap通过hashcode对其内容进行快速查找,而 TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的). HashMap 非线程安全 TreeMap 非线程安全 线程安全 在Java里,线程安全一般体