吊打面试官系列:HashMap详细介绍(源码解析)和使用示例

本文深入探讨HashMap,从基本概念到源码解析,覆盖HashMap介绍、数据结构、源码分析(JDK1.6.0_45)、遍历方式和实例应用。内容包括HashMap的构造函数、主要接口、拉链法解决哈希冲突、构造函数、遍历方法及其实现。同时,文章提供了HashMap的使用示例,帮助读者掌握HashMap的实战技巧。
摘要由CSDN通过智能技术生成

概要

这一章,我们对HashMap进行学习。
我们先对HashMap有个整体认识,然后再学习它的源码,最后再通过实例来学会使用HashMap。内容包括:

第1部分 HashMap介绍
第2部分 HashMap数据结构
第3部分 HashMap源码解析(基于JDK1.6.0_45)
第3.1部分 HashMap的“拉链法”相关内容
第3.2部分 HashMap的构造函数
第3.3部分 HashMap的主要对外接口
第3.4部分 HashMap实现的Cloneable接口
第3.5部分 HashMap实现的Serializable接口
第4部分 HashMap遍历方式
第5部分 HashMap示例

第1部分 HashMap介绍

HashMap简介

HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。
HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。

HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。容量 是哈希表中桶的数量,初始容量 只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。
通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。

HashMap的构造函数

HashMap共有4个构造函数,如下:

// 默认构造函数。
HashMap()

// 指定“容量大小”的构造函数
HashMap(int capacity)

// 指定“容量大小”和“加载因子”的构造函数
HashMap(int capacity, float loadFactor)

// 包含“子Map”的构造函数
HashMap(Map<? extends K, ? extends V> map)

HashMap的API

void                 clear()
Object               clone()
boolean              containsKey(Object key)
boolean              containsValue(Object value)
Set<Entry<K, V>>     entrySet()
V                    get(Object key)
boolean              isEmpty()
Set<K>               keySet()
V                    put(K key, V value)
void                 putAll(Map<? extends K, ? extends V> map)
V                    remove(Object key)
int                  size()
Collection<V>        values()

第2部分 HashMap数据结构

HashMap的继承关系

java.lang.Object
   ↳     java.util.AbstractMap<K, V>
         ↳     java.util.HashMap<K, V>

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable { }

HashMap与Map关系如下图

从图中可以看出:
(01) HashMap继承于AbstractMap类,实现了Map接口。Map是"key-value键值对"接口,AbstractMap实现了"键值对"的通用函数接口。
(02) HashMap是通过"拉链法"实现的哈希表。它包括几个重要的成员变量:table, size, threshold, loadFactor, modCount。
  table是一个Entry[]数组类型,而Entry实际上就是一个单向链表。哈希表的"key-value键值对"都是存储在Entry数组中的。
  size是HashMap的大小,它是HashMap保存的键值对的数量。
  threshold是HashMap的阈值,用于判断是否需要调整HashMap的容量。threshold的值="容量*加载因子",当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。
  loadFactor就是加载因子。
  modCount是用来实现fail-fast机制的。

第3部分 HashMap源码解析(基于JDK1.6.0_45)

为了更了解HashMap的原理,下面对HashMap源码代码作出分析。
在阅读源码时,建议参考后面的说明来建立对HashMap的整体认识,这样更容易理解HashMap。

  1 package java.util;
  2 import java.io.*;
  3 
  4 public class HashMap<K,V>
  5     extends AbstractMap<K,V>
  6     implements Map<K,V>, Cloneable, Serializable
  7 {
  8 
  9     // 默认的初始容量是16,必须是2的幂。
 10     static final int DEFAULT_INITIAL_CAPACITY = 16;
 11 
 12     // 最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换)
 13     static final int MAXIMUM_CAPACITY = 1 << 30;
 14 
 15     // 默认加载因子
 16     static final float DEFAULT_LOAD_FACTOR = 0.75f;
 17 
 18     // 存储数据的Entry数组,长度是2的幂。
 19     // HashMap是采用拉链法实现的,每一个Entry本质上是一个单向链表
 20     transient Entry[] table;
 21 
 22     // HashMap的大小,它是HashMap保存的键值对的数量
 23     transient int size;
 24 
 25     // HashMap的阈值,用于判断是否需要调整HashMap的容量(threshold = 容量*加载因子)
 26     int threshold;
 27 
 28     // 加载因子实际大小
 29     final float loadFactor;
 30 
 31     // HashMap被改变的次数
 32     transient volatile int modCount;
 33 
 34     // 指定“容量大小”和“加载因子”的构造函数
 35     public HashMap(int initialCapacity, float loadFactor) {
 36         if (initialCapacity < 0)
 37             throw new IllegalArgumentException("Illegal initial capacity: " +
 38                                                initialCapacity);
 39         // HashMap的最大容量只能是MAXIMUM_CAPACITY
 40         if (initialCapacity > MAXIMUM_CAPACITY)
 41             initialCapacity = MAXIMUM_CAPACITY;
 42         if (loadFactor <= 0 || Float.isNaN(loadFactor))
 43             throw new IllegalArgumentException("Illegal load factor: " +
 44                                                loadFactor);
 45 
 46         // 找出“大于initialCapacity”的最小的2的幂
 47         int capacity = 1;
 48         while (capacity < initialCapacity)
 49             capacity <<= 1;
 50 
 51         // 设置“加载因子”
 52         this.loadFactor = loadFactor;
 53         // 设置“HashMap阈值”,当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。
 54         threshold = (int)(capacity * loadFactor);
 55         // 创建Entry数组,用来保存数据
 56         table = new Entry[capacity];
 57         init();
 58     }
 59 
 60 
 61     // 指定“容量大小”的构造函数
 62     public HashMap(int initialCapacity) {
 63         this(initialCapacity, DEFAULT_LOAD_FACTOR);
 64     }
 65 
 66     // 默认构造函数。
 67     public HashMap() {
 68         // 设置“加载因子”
 69         this.loadFactor = DEFAULT_LOAD_FACTOR;
 70         // 设置“HashMap阈值”,当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。
 71         threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
 72         // 创建Entry数组,用来保存数据
 73         table = new Entry[DEFAULT_INITIAL_CAPACITY];
 74         init();
 75     }
 76 
 77     // 包含“子Map”的构造函数
 78     public HashMap(Map<? extends K, ? extends V> m) {
 79         this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
 80                       DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
 81         // 将m中的全部元素逐个添加到HashMap中
 82         putAllForCreate(m);
 83     }
 84 
 85     static int hash(int h) {
 86         h ^= (h >>> 20) ^ (h >>> 12);
 87         return h ^ (h >>> 7) ^ (h >>> 4);
 88     }
 89 
 90     // 返回索引值
 91     // h & (length-1)保证返回值的小于length
 92     static int indexFor(int h, int length) {
 93         return h & (length-1);
 94     }
 95 
 96     public int size() {
 97         return size;
 98     }
 99 
100     public boolean isEmpty() {
101         return size == 0;
102     }
103 
104     // 获取key对应的value
105     public V get(Object key) {
106         if (key == null)
107             return getForNullKey();
108         // 获取key的hash值
109         int hash = hash(key.hashCode());
110         // 在“该hash值对应的链表”上查找“键值等于key”的元素
111         for (Entry<K,V> e = table[indexFor(hash, table.length)];
112              e != null;
113              e = e.next) {
114             Object k;
115             if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
116                 return e.value;
117         }
118         return null;
119     }
120 
121     // 获取“key为null”的元素的值
122     // HashMap将“key为null”的元素存储在table[0]位置!
123     private V getForNullKey() {
124         for (Entry<K,V> e = table[0]; e != null; e = e.next) {
125             if (e.key == null)
126                 return e.value;
127         }
128         return null;
129     }
130 
131     // HashMap是否包含key
132     public boolean containsKey(Object key) {
133         return getEntry(key) != null;
134     }
135 
136     // 返回“键为key”的键值对
137     final Entry<K,V> getEntry(Object key) {
138         // 获取哈希值
139         // HashMap将“key为null”的元素存储在table[0]位置,“key不为null”的则调用hash()计算哈希值
140         int hash = (key == null) ? 0 : hash(key.hashCode());
141         // 在“该hash值对应的链表”上查找“键值等于key”的元素
142         for (Entry<K,V> e = table[indexFor(hash, table.length)];
143              e != null;
144              e = e.next) {
145             Object k;
146             if (e.hash == hash &&
147                 ((k = e.key) == key || (key != null && key.equals(k))))
148                 return e;
149         }
150         return null;
151     }
152 
153     // 将“key-value”添加到HashMap中
154     public V put(K key, V value) {
155         // 若“key为null”,则将该键值对添加到table[0]中。
156         if (key == null)
157             return putForNullKey(value);
158         // 若“key不为null”,则计算该key的哈希值,然后将其添加到该哈希值对应的链表中。
159         int hash = hash(key.hashCode());
160         int i = indexFor(hash, table.length);
161         for (Entry<K,V> e = table[i]; e != null; e = e.next) {
162             Object k;
163             // 若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出!
164             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
165                 V oldValue = e.value;
166                 e.value = value;
167                 e.recordAccess(this);
168                 return oldValue;
169             }
170         }
171 
172         // 若“该key”对应的键值对不存在,则将“key-value”添加到table中
173         modCount++;
174         addEntry(hash, key, value, i);
175         return null;
176     }
177 
178     // putForNullKey()的作用是将“key为null”键值对添加到table[0]位置
179     private V putForNullKey(V value) {
180         for (Entry<K,V> e = table[0]; e != null; e = e.next) {
181             if (e.key == null) {
182                 V oldValue = e.value;
183                 e.value = value;
184                 e.recordAccess(this);
185                 return oldValue;
186             }
187         }
188         // 这里的完全不会被执行到!
189         modCount++;
190         addEntry(0, null, value, 0);
191         return null;
192     }
193 
194     // 创建HashMap对应的“添加方法”,
195     // 它和put()不同。putForCreate()是内部方法,它被构造函数等调用,用来创建HashMap
196     // 而put()是对外提供的往HashMap中添加元素的方法。
197     private void putForCreate(K key, V value) {
198         int hash = (key == null) ? 0 : hash(key.hashCode());
199         int i = indexFor(hash, table.length);
200 
201         // 若该HashMap表中存在“键值等于key”的元素,则替换该元素的value值
202         for (Entry<K,V> e = table[i]; e != null; e = e.next) {
203             Object k;
204             if (e.hash == hash &&
205                 ((k = e.key) == key || (key != null && key.equals(k)))) {
206                 e.value = value;
207                 return;
208             }
209         }
210 
211         // 若该HashMap表中不存在“键值等于key”的元素,则将该key-value添加到HashMap中
212         createEntry(hash, key, value, i);
213     }
214 
215     // 将“m”中的全部元素都添加到HashMap中。
216     // 该方法被内部的构造HashMap的方法所调用。
217     private void putAllForCreate(Map<? extends K, ? extends V> m) {
218         // 利用迭代器将元素逐个添加到HashMap中
219         for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {
220             Map.Entry<? extends K, ? extends V> e = i.next();
221             putForCreate(e.getKey(), e.getValue());
222         }
223     }
224 
225     // 重新调整HashMap的大小,newCapacity是调整后的单位
226     void resize(int newCapacity) {
227         Entry[] oldTable = table;
228         int oldCapacity = oldTable.length;
229         if (oldCapacity == MAXIMUM_CAPACITY) {
230             threshold = Integer.MAX_VALUE;
231             return;
232         }
233 
234      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值