前面写了list下arrlist和linkedlist的区别也就没有下文了,抽空总结一下map下的一些类。
一、概述
首先说一下三个map的介绍(treemap比较特殊,暂时忽略)
1、hashtable:数组+单链表结构、线程安全(操作加锁)、无序、
2、hashmap:数组+单链表结构、线程不安全、无序、
3、linkedhashmap:继承了hashmap、数组+单链表结构、线程不安全、有序(1、插入顺序 2、lru:最少最近访问顺序 [采用双向链表存储顺序])
上面就是三个map的大体特性,那么这些特性是怎么实现的呢,我们可以从几个方面切入源码分析三个map不同之处:构造函数、put方法、get方法、
二、底层结构
1、HashTable:存储一个值时,先根据key算出所存入table数组的下角标i,如果table[i]为空,直接存入生成的entry,如果不为空则从该位置的entry头遍历,如果key相同,则覆盖value,如果没有相同的key的entry,则将新生成的entry放入该数组下角标的头部;
/** * hashtable用来存储数据的数组,数组中的元素是Entry,下面说明. */ private transient Entry<K,V>[] table; /** * hashtable数组中的元素为Entry,是一个key和value的结构(类中方法省略) */ private static class Entry<K,V> implements Map.Entry<K,V> { //当前entry的hash值 int hash; //当前entry的key值 final K key; //当前enry的value值 V value; //当前entry的下一个entry(这里可以看出是一个单链表) Entry<K,V> next; }
2、hashmap:跟hashtable完全相同的底层结构
/** * 一个空entry数组 */ static final Entry<?,?>[] EMPTY_TABLE = {}; /** * 跟hashtable一样,table数组,存放entry对象,唯一区别就是直接先初始化为空数组 */ transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; //entry对象跟entry完全相同 static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; int hash; }
3、LinkedHashMap:下面的代码可以看到,linkedhashmap继承了hashmap,在hashmap的基础上加了hear双向链表和entry中的前后标记
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> { /** * 类中看出继承了hashmap,所以底层的数据格直接使用了hashmap的table. 这个hear是一个链表,head为链表的头,后面涉及到 */ private transient Entry<K,V> header; /** * accessorder属性:用来标记hear双向链表是根绝lru排序,还是插入顺序排序 */ private final boolean accessOrder; }
/** * linkedhashmap中的entry继承了hashmap中的enry */ private static class Entry<K,V> extends HashMap.Entry<K,V> { // 多了两个属性:entry前 和 entry后的记录,这是给head用的 Entry<K,V> before, after; }
三、构造函数(只说明大流程)
1、hashtable
* 最常用的构造函数,这里调用了this构造函数,传入两个参数11和0.75,下面说明参数意义 */ public Hashtable() { this(11, 0.75f); } /** * 可以看到,第一个参数为初始化的容量小,这里默认设置为11,第二个参数是扩容因子,默认当容器中有百分之75使用的时候进行扩容*/ public Hashtable(int initialCapacity, float loadFactor) { //check容量设置大小 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); //check加载因子大小 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal Load: "+loadFactor); if (initialCapacity==0) initialCapacity = 1; this.loadFactor = loadFactor; //根据初始容量大小,初始化entry数组(还记得上面hashtable是定义属性的时候就初始化了么) table = new Entry[initialCapacity]; //计算扩容的临界值:总容量*加载因子 threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); //计算hashseed参数,用来计算元素key的hash值 initHashSeedAsNeeded(initialCapacity); }
可以看到:
1、hashtable默认初始化大小为11,默认扩容加载因子为0.75
2、hashtable的数组容器在初始化时完成
2、hashmap
/** * 这里进行了代码优化,使用常量,传入两个参数为:16和0.75 */ public HashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); } /** * 初始容量跟hashtable不一样,这里是16,加载因子都为0.75 * capacity and load factor.*/ public HashMap(int initialCapacity, float loadFactor) { //check初始容量 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); //设置加载因子 和 初始容量值 this.loadFactor = loadFactor; threshold = initialCapacity; //这个方法为空,可以看到hashmap只是将初始值设置,并没有跟hashtable一样进行计算,到目前位置table数组还是一个空数组 //(其实hashmap是在第一次put进元素的时候,才对hashmap的容量等信息进行初始化的,下面会看到) init(); }
这里可以看到hashmap构造函数做的内容:
1、hashmap容器,默认容量16,扩容加载因子0.75
2、构造函数只进行了参数check和参数设置,并没有将table设置为真正的大小,也没有计算扩容的容量临界值
3、linkedhashmap
/** * 完全复用的hashmap的构造函数。accessorder属性默认设置为false:就是hear链表按照插入顺序排序 */ public LinkedHashMap() { super(); accessOrder = false; }
linkedhashmap没什么好说的,完全跟hashmap一样,多了一个head链表的排序设置。
四、put方法
1、hashtable