1、概述:HashMap 基于哈希表<key,value>,实现Map接口,接受null的键和值,不允许重复的key,但允许重复的value,即不同的key可以对应相同的value值,非线程安全,Hashtable和HashMap非常类似,同样实现了Map接口,但是Hashtable不接受null的键和值,Hashtable是线程安全的,Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
2、数据结构:数组和链表的结合。本身是一个数组,数组中的元素是链表。如图所示:
数组中存储的是链表的头结点,元素存储到数组中的规则是:一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。
HashMap和Hashtable里面有一个静态内部类Entry,其重要属性包括key,value,next,从这些属性就可以看出这是对键值对实现的一个基础bean,所以HashMap和Hashtable是一个线性数组Entry[]
3、存数据
键值对A,通过计算key的hash值得到index,则该元素存储在Entry[index],当有键值对B获得的index相同时,上面我们提到过Entry类里面有一个next属性,作用是指向下一个Entry,所以HashMap会这样做:B.next = A,Entry[index] = B,如果又进来C,index也相同,那么C.next = B,Entry[index] = C,数组中存储的是最后插入的元素(注:HashMap和Hashtable里面设置一个加载因子,随着map的size越来越大,Entry[]会根据加载因子以一定的规则加长长度。)
4、取元素,先根据key值获取到键值对在数组中的下标,然后再遍历该下标处的链表。
5、null key总是存放在Entry[]数组的第一个元素。
6、HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。
注:“快速失败”也就是fail-fast,它是Java集合的一种错误检测机制。当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。记住是有可能,而不是一定。例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。