HashMap
HashMap要聊的东西太多了,而且由于HashSet接口中底层实现就是用的HashMap,所以建议先看HashMap的源码。这里就直接转载别人的文章中的总结;毕竟别人总结 的非常到位。先说下结构,对HashMap的结构有个大概的了解后,再说些其工作原理以及其中涉及到的哈希算法。
参考:【http://blog.csdn.net/qq_27093465/article/details/52207152】
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
数组的特点是:寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易。那么我们能不能
综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表。
HashMap实现了Map接口,继承AbstractMap。其中Map接口定义了键映射到值的规则,而AbstractMap类提供
Map 接口的骨干实现,以最大限度地减少实现此接口所需的工作。
一 HashMap结构
上图 可能很直观清晰的介绍了HashMap的结构:从上图我们可以发现哈希表是由数组+链表组成的,HashMap其实也是一个线性的数组实现的,所以可以理解为其存储数据的容器就是一个线性数组。这可能让我们很不解,一个线性的数组怎么实现按键值对来存取数据呢?
1 .首先HashMap里面实现一个静态内部类Entry,其重要的属性有 key , value, next,从属性key,value。
我们就能很明显的看出来Entry就是HashMap键值对实现的一个基础bean,我们上面说到HashMap的基础就是一个线性数组,
这个数组就是Entry[],Map里面的内容都保存在Entry[]里面。
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;//键
V value;//值
Entry<K,V> next;//下个元素指针
int hash;//key的hash值
}
2 两个概念:
1).这里面有个Hash冲突的概念:就像上面的一个数组的位置上出现了一条链,即一个链表的出现,这就是所谓的
hash冲突,解决hash冲突,就是让链表的长度变短,或者干脆就是不产生链表,一个好的hash算法应该是让数据很好的
散列到数组的各个位置,即一个位置存一个数据就是最好的散列,下面说的链地址法,说的就是在hashmap里面冲突的时候,
一个节点可以存多个数据。
2).还有个桶:bucket,就是上面的数组的每一个成员,数组的每个位置就叫一个桶。对应前面的单词。
二 HashMap基本定义
HashMap也是我们使用非常多的Collection,它是基于哈希表的 Map 接口的实现,以key-value的形式存在。
HashMap可以接受null键值和值,而HashTable则不能;HashMap是非synchronized;HashMap很快;HashMap储存的是键值对。
三 构造函数
HashMap提供了三个构造函数:
HashMap():构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity):构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity, float loadFactor):构造一个带指定初始容量和加载因子的空 HashMap。
在这里提到了两个参数:初始容量,加载因子。这两个参数是影响HashMap性能的重要参数,其中容量表示哈希表中桶的数量,初始容量是创建哈希表时的容量,加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度,它衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。
对于使用链表法的散列表来说,查找一个元素的平均时间是O(1+a),因此如果负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。系统默认负载因子为0.75,一般情况下我们是无需修改的。
四、工作原理
1 存储的实现put(K key,V value)
核心就是:根据key的hashcode得到桶的位置,往里面添加值;若发现有对应的键存在就覆盖。
当我们想一个HashMap中添加一对key-value时,系统首先会计算key的hash值,然后根据hash值确认在table中存储的位置。若该位置没有元素,则直接插入。否则迭代该处元素链表并依此比较其key的hash值。如果两个hash值相等且key值相等(e.hash == hash && ((k = e.key) == key || key.equals(k))),则用新的Entry的value覆盖原来节点的value。如果两个hash值相等但key值不等 ,则将该节点插入该链表的链头。
①首先判断key==null?,若为null,就调用putForNullKey方法。
②若 key != null ,对key调用hashCode()方法,根据hash值确认在table中存储的位置,即桶的位置,确定数据要插入到那个桶中。(bucket位置来储存Entry对象)。
若桶中有元素,