HashMap简单分析
基于哈希表的Map接口实现。此实现提供所有可选的映射操作,并允许使用null值和null键。(除了非同步和允许使用null之外,HashMap类和Hashtable大致相同。)此类不保证映射顺序,特别是不保证该顺序永恒不变。 ——该描述来自JDK_API_1.6
HashMap概述
在了解HashMap之前必须先了解什么是Hash表;简单介绍几种常见的数据结构
- 数组:采用一段连续的单元来存储数据;常见的特点是查找快,插入慢
- 链表:采用单个节点连接来存储数据,一般单向链表:上一个节点存储的下一个节点索引;常见的特点是查找慢,插入快
- 哈希表:根据关键码值进行直接访问的数据结构,也就是说,它通过把关键码值映射到表中的某一个位置,再次访问的时候通过关键值进行查询,插入和删除;但是当我们存储相同Hash值的元素时,我们这个时候就要面临解决Hash冲突,这里就不详细叙述Hash冲突的解决方式,可以自行百度或Google
HashMap在Java中是使用哈希表来存储键值对(Entry),然而在是通过Key来决定Entry在Hash表中的位置,当Key出现了Hash冲突时,Java中采用的是“链表法”来解决冲突,如下图所示
HashMap源码分析
HashMap.put()方法源码分析
public V put(K key, V value) { // 判断是key是否为null,如果是null值的话,那么就使用null键值插入方式 if (key == null) return putForNullKey(value); // 代码执行到此处,key不为null,获取key对象的hash值,然后再次hash int hash = hash(key.hashCode()); // 获取key的hash值在Hash表中的位置 int i = indexFor(hash, table.length); // 这个循环的作用是将Entry根据Hash值放入指定的Hash表中的并解决Hash冲突 for (Entry<K,V> e = table[i]; e != null; e = e.next) { // 获取内部类Entry中具体的一个Entry的当前的Key Object k; // 比较这个具体Entry中的key的Hash值和当前添加key的Hash值,以及两个key值是否相等 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { // 如果相等,那么将当前value赋值给oldValue,将添加进来的value赋值给当前key对应的value,最后将被覆盖的oldValue返回出去 V oldValue = e.value; e.value = value; // 这个方法是一个空实现,我也不知道它有啥作用,JDK源码中方法注释大致翻译一下:这个方法这样情况下被执行,这个情况是当调用put()方法时,且key在当前Entry中是存在的,然后覆盖当前Entry中的value值 e.recordAccess(this); return oldValue; } } // modCount作用是计算HashMap当前多少元素,之所以每次在添加元素都计数, // 是因为HashMap在元素超过当前容量时,需要对当前HashMap进行reHash,对 // 当前HashMap进行扩容,然后对每一个元素进行重新填充,就是因为这个原因 //,API中提示不保证该顺序是永久不变的 modCount++; // 执行到这里的时候,说明该HashMap没有相同key值的Entry,那么就直接添加一个Entry addEntry(hash, key, value, i); // 最后因为没有替换oldValue,然后返回的是一个null的value return null; } void addEntry(int hash, K key, V value, int bucketIndex) { // 首先拿出当前hash链表上的Entry,然后将原本的Entry挂在新创建的Entry下 Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<K,V>(hash, key, value, e); // 当size大于HashMap的限制的时候,那么就开始扩容 if (size++ >= threshold) resize(2 * table.length); }
HashMap.get()方法分析
public V get(Object key) { // 判断当前key值是否为null,假如为null的时候那么就使用特殊获取value的方法 if (key == null) // 特殊或缺value的方法 return getForNullKey(); // 假如key不为null,那么根据传递key值,算出相应的hash值,找到指定的Entry位置 int hash = hash(key.hashCode()); // 获取Hash值对应的Entry,取到Entry链表下的所有Entry,遍历,直到找到响应的key值,找到后获取相应Entry的value for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } // 最后没有直到相应的Entry,那么就返回null return null; }
HashMap构造方法简单分析
- HashMap():创建一个初始容量为16,负载因子为0.75的HashMap
- HashMap(int initialCapacity):创建一个指定初始化容量initialCapacity大小,负载因子为0.75的HashMap
- HashMap(int initialCapacity, float loadFactor):创建一个指定初始化容量initialCapacity大小,负载因子为loadFactor的HashMap
这里我们简单聊一下初始化容量和负载因子的作用,初始化容量其实可以从字面理解就是HashMap的最大长度,负载因子指的是HashMap的最大容量的实际使用率;作为负载因子来说,足够大的话,空间利用虽然充分,有可能出现查找效率低,之所以查找效率低,是因为一旦利用率大的话,哈希冲突可能越多,链表越长;负载因子越小,那么导致Hash列表过去稀疏,那么导致空间浪费
上述博客参考:HashMap实现原理及源码分析,深入Java集合学习系列:HashMap的实现原理,java_api_1.6