Map类族的类图关系:
跟List 和Set 不同的是,Map接口并不继承于Collection接口,它有一套自己的实现。
抽象类AbstractMap实现了Map接口,同时有EnumMap、HashMap、WeakHashMap三个实现类,而LinkedHashMap又继承于HashMap.
首先关注一下hashTble和hashMap的区别。两者同样实现了Map接口,但HashTable的大部分方法都做了线程同步控制,而HashMap则没有;
public synchronized int size() { return count; } public synchronized boolean isEmpty() { return count == 0; } public synchronized Enumeration<K> keys() { return this.<K>getEnumeration(KEYS); }另外,HashTable不允许key、value任意一值为null;从内部算法上而言,HashTable和HashMap对key-value的映射算法不同。以HashMap为例,总结一下它的实现机理。
一、HashMap
1、HashMap的实现原理
将key做hashCode(hash算法),将得到的hash值映射到内存地址中,直接取得key所对应的value。在HashMap中底层数据结构基于数组,内存地址也就是数组的下标索引。JDK1.8对HashMap相关的hash算法如下:
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }取得key的hash值后,根据hash值找到对应的数组下标,获取对应的value。
2、Hash冲突
HashMap性能影响最值得关注的就是Hash冲突问题。简单来说就是两个对象通过hash计算后,指向同一个内存地址。针对hash冲突,就得说到HashMap的数组内元素-Entry类对象。Entry类有key、value、next、hash四个属性,HashMap中的put方法里的Node类就继承于Entry,当发生hash冲突时,产生的新的Entry对象会被安放到对应的内存地址中,替换原有值,为保证旧数据不丢失,将新entry对象的next指向旧值。所以总的来说,HashMap还是一个基于数组-数组中又基于链表的数据结果。JDK1.8中主要是对Node对象的操作,但是大同小异,不管怎么写,HashMap的源码还是让人很吐血的。
所以,一般只要对象的hashCode方法写的足够好,减少hash冲突的发生,HashMap的性能还是可以保证。但如果有大量的hash冲突,使得不得不遍历多次链表,这个对于链表的遍历在上一篇博客中已经提到了-使用随机遍历的时间是打杯水上个厕所也等不到结果的。呵呵哒。
3、容量参数
对于ArrayList和Vector内部使用数组结构实现,当内存空间不足需要扩容时,开销是比较大的。在HashMap上做了一定的优化。主要体现在HashMap的多个构造参数
public HashMap(int initialCapacity) {//初始容量==数组大小 this(initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } public HashMap(int initialCapacity, float loadFactor) {负载因子=元素个数/内部数组总大小 。。。。。 } public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); }当负载因子>1时,也就意味着,需要放入的元素个数>容量大小,僧多粥少,产生hash冲突的可能性就会提高。所以通常建议负载因子在0~1之间,默认HashMap的容量是16,负载因子为0.75。合理的设置容量和负载因子大小,调用构造函数初始化HashMap,可以减少冲突,提高访问速度。
二 、LinkedHashMap
HashMap性能已经不错,不过它的缺点之一在于无序,存入HashMap中的元素在遍历时都是无序的。LinkedHashMap就是在顺序行这方面对HashMap做了优化。
LinkedHashMap继承于HashMap, 增加了首尾次序的参数:也提供了相应的在node前、后\插入移除等方法实现(after、before)。
transient LinkedHashMap.Entry<K,V> head; transient LinkedHashMap.Entry<K,V> tail; void afterNodeInsertion(boolean evict) { // possibly remove eldest …… } void afterNodeAccess(Node<K,V> e) { // move node to last …… }
三、TreeMap
TreeMap继承于AbstractMap抽象类,主要也是应用于对元素进行排序,但排序方式有别于LinkedHashMap,TreeMap基于一种平衡查找树结构,可根据一定的条件对元素进行排序。具体应用笔者并没有实践研究,就不在此献丑了。
四、总结
对于Map类族的主要实现类和应用特点本文都做了简单的总结,这些源码跟原理上的知识可能稍微有些晦涩,但源码慢慢看,先看能看懂的,看多了就会发现对于java核心特性例如可变参数、反射、泛型应用是非常广泛的。(特性内容在上个月的博文中有系列介绍)知识都是相通的,知道的多了看的也就流畅了。