String key = null;
while (iter2.hasNext()) {
key = (String)iter2.next();
System.out.print(key);
System.out.println(map.get(key));
}
// 方法3:获得value的Set集合 再遍历
System.out.println(“方法3”);
// 1. 获得value的Set集合
Collection valueSet = map.values();
// 2. 遍历Set集合,从而获取value
// 2.1 获得values 的Iterator
Iterator iter3 = valueSet.iterator();
// 2.2 通过遍历,直接获取value
while (iter3.hasNext()) {
System.out.println(iter3.next());
}
}
}
// 注:对于遍历方式,推荐使用针对 key-value对(Entry)的方式:效率高
// 原因:
// 1. 对于 遍历keySet 、valueSet,实质上 = 遍历了2次:1 = 转为 iterator 迭代器遍历、2 = 从 HashMap 中取出 key 的 value 操作(通过 key 值 hashCode 和 equals 索引)
// 2. 对于 遍历 entrySet ,实质 = 遍历了1次 = 获取存储实体Entry(存储了key 和 value )
- 运行结果
方法1
Java2
iOS3
数据挖掘4
Android1
产品经理5
Java2
iOS3
数据挖掘4
Android1
产品经理5
方法2
Java2
iOS3
数据挖掘4
Android1
产品经理5
Java2
iOS3
数据挖掘4
Android1
产品经理5
方法3
2
3
4
1
5
下面,我们按照上述的使用过程,对一个个步骤进行源码解析
4. 基础知识:HashMap中的重要参数(变量)
- 在进行真正的源码分析前,先讲解
HashMap
中的重要参数(变量) HashMap
中的主要参数 = 容量、加载因子、扩容阈值- 具体介绍如下
// 1. 容量(capacity): HashMap中数组的长度
// a. 容量范围:必须是2的幂 & <最大容量(2的30次方)
// b. 初始容量 = 哈希表创建时的容量
// 默认容量 = 16 = 1<<4 = 00001中的1向左移4位 = 10000 = 十进制的2^4=16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量 = 2的30次方(若传入的容量过大,将被最大值替换)
static final int MAXIMUM_CAPACITY = 1 << 30;
// 2. 加载因子(Load factor):HashMap在其容量自动增加前可达到多满的一种尺度
// a. 加载因子越大、填满的元素越多 = 空间利用率高、但冲突的机会加大、查找效率变低(因为链表变长了)
// b. 加载因子越小、填满的元素越少 = 空间利用率小、冲突的机会减小、查找效率高(链表不长)
// 实际加载因子
final float loadFactor;
// 默认加载因子 = 0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 3. 扩容阈值(threshold):当哈希表的大小 ≥ 扩容阈值时,就会扩容哈希表(即扩充HashMap的容量)
// a. 扩容 = 对哈希表进行resize操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数
// b. 扩容阈值 = 容量 x 加载因子
int threshold;
// 4. 其他
// 存储数据的Entry类型 数组,长度 = 2的幂
// HashMap的实现方式 = 拉链法,Entry数组上的每个元素本质上是一个单向链表
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
// HashMap的大小,即 HashMap中存储的键值对的数量
transient int size;
- 参数示意图
- 此处 详细说明 加载因子
5. 源码分析
- 本次的源码分析主要是根据 使用步骤 进行相关函数的详细分析
- 主要分析内容如下:
- 下面,我将对每个步骤内容的主要方法进行详细分析
步骤1:声明1个 HashMap的对象
/**
- 函数使用原型
*/
Map<String,Integer> map = new HashMap<String,Integer>();
/**
- 源码分析:主要是HashMap的构造函数 = 4个
- 仅贴出关于HashMap构造函数的源码
*/
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable{
// 省略上节阐述的参数
/**
- 构造函数1:默认构造函数(无参)
- 加载因子 & 容量 = 默认 = 0.75、16
*/
public HashMap() {
// 实际上是调用构造函数3:指定“容量大小”和“加载因子”的构造函数
// 传入的指定容量 & 加载因子 = 默认
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
/**
- 构造函数2:指定“容量大小”的构造函数
- 加载因子 = 默认 = 0.75 、容量 = 指定大小
*/
public HashMap(int initialCapacity) {
// 实际上是调用指定“容量大小”和“加载因子”的构造函数
// 只是在传入的加载因子参数 = 默认加载因子
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
- 构造函数3:指定“容量大小”和“加载因子”的构造函数
- 加载因子 & 容量 = 自己指定
*/
public HashMap(int initialCapacity, float loadFactor) {
// HashMap的最大容量只能是MAXIMUM_CAPACITY,哪怕传入的 > 最大容量
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
// 设置 加载因子
this.loadFactor = loadFactor;
// 设置 扩容阈值 = 初始容量
// 注:此处不是真正的阈值,是为了扩展table,该阈值后面会重新计算,下面会详细讲解
threshold = initialCapacity;
init(); // 一个空方法用于未来的子对象扩展
}
/**
- 构造函数4:包含“子Map”的构造函数
- 即 构造出来的HashMap包含传入Map的映射关系
- 加载因子 & 容量 = 默认
*/
public HashMap(Map<? extends K, ? extends V> m) {
// 设置容量大小 & 加载因子 = 默认
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
// 该方法用于初始化 数组 & 阈值,下面会详细说明
inflateTable(threshold);
// 将传入的子Map中的全部元素逐个添加到HashMap中
putAllForCreate(m);
}
}
- 注:
- 此处仅用于接收初始容量大小(
capacity
)、加载因子(Load factor
),但仍无真正初始化哈希表,即初始化存储数组table
- 此处先给出结论:真正初始化哈希表(初始化存储数组
table
)是在第1次添加键值对时,即第1次调用put()
时。下面会详细说明
至此,关于HashMap
的构造函数讲解完毕。
步骤2:向HashMap添加数据(成对 放入 键 - 值对)
- 添加数据的流程如下
注:为了让大家有个感性的认识,只是简单的画出存储流程,更加详细 & 具体的存储流程会在下面源码分析中给出
- 源码分析
/**
- 函数使用原型
*/
map.put(“Android”, 1);
map.put(“Java”, 2);
map.put(“iOS”, 3);
map.put(“数据挖掘”, 4);
map.put(“产品经理”, 5);
/**
- 源码分析:主要分析: HashMap的put函数
*/
public V put(K key, V value)
(分析1)// 1. 若 哈希表未初始化(即 table为空)
// 则使用 构造函数时设置的阈值(即初始