在Java集合类中最常用的除了ArrayList外,就是HashMap了。本文尽自己所能,尽量详细的解释HashMap的源码。一山还有一山高,有不足之处请之处,定感谢指定并及时修正。
在看HashMap源码之前先复习一下数据结构。
Java最基本的数据结构有数组和链表。数组的特点是空间连续(大小固定)、寻址迅速,但是插入和删除时需要移动元素,所以查询快,增加删除慢。链表恰好相反,可动态增加或减少空间以适应新增和删除元素,但查找时只能顺着一个个节点查找,所以增加删除快,查找慢。有没有一种结构综合了数组和链表的优点呢?当然有,那就是哈希表(虽说是综合优点,但实际上查找肯定没有数组快,插入删除没有链表快,一种折中的方式吧)。一般采用拉链法实现哈希表。哈希表?拉链法?可能一下想不起来,不过放张图就了然了。
(图片google来的,好多都用了文章用了这张图了,不知道出处了就没申明作者了)
学计算机的肯定学过这玩意儿,也不需要解释,都懂的。
铺垫了这么多,又是数组又是链表,还有哈希表,拉链法,该入正题了,我们什么时候用到了这些内容,具体它是怎么实现的?
其实我们一直在用(别告诉我你没用过HashMap什么的),可能你一直没去深究,没看到它如何实现的,所以一直没感受到。这里主要分析HashMap的源码,就不再多扯其他的了。
HashMap继承自AbstractMap,实现了Map接口(这些内容可以参考《Java集合类》)。来看类的定义。
1 public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
Map接口定义了所有Map子类必须实现的方法。Map接口中还定义了一个内部接口Entry(为什么要弄成内部接口?改天还要学习学习)。Entry将在后面有详细的介绍。
AbstractMap也实现了Map接口,并且提供了两个实现Entry的内部类:SimpleEntry和SimpleImmutableEntry。
定义了接口,接口中又有内部接口,然后有搞了个抽象类实现接口,抽象类里面又搞了两个内部类实现接口的内部接口,有没有点绕,为什么搞成这样呢?先不管了,先看HashMap吧。
HashMap中定义的属性(应该都能看明白,不过还是解释一下):
1 /**
2 * 默认的初始容量,必须是2的幂。
3 */
4 static final int DEFAULT_INITIAL_CAPACITY = 16;
5 /**
6 * 最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换)
7 */
8 static final int MAXIMUM_CAPACITY = 1 << 30;
9 /**
10 * 默认装载因子,这个后面会做解释
11 */
12 static final float DEFAULT_LOAD_FACTOR = 0.75f;
13 /**
14 * 存储数据的Entry数组,长度是2的幂。看到数组的内容了,接着看数组中存的内容就明白为什么博文开头先复习数据结构了
15 */
16 transient Entry[] table;
17 /**
18 * map中保存的键值对的数量
19 */
20 transient int size;
21 /**
22 * 需要调整大小的极限值(容量*装载因子)
23 */
24 int threshold;
25 /**
26 *装载因子
27 */
28 final float loadFactor;
29 /**
30 * map结构被改变的次数
31 */
32 transient volatile int modCount;
接着是HashMap的构造方法。
/**
*使用默认的容量及装载因子构造一个空的HashMap
*/
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);//计算下次需要调整大小的极限值
table = new Entry[DEFAULT_INITIAL_CAPACITY];//根据默认容量(16)初始化table
init();
}
/**
* 根据给定的初始容量的装载因子创建一个空的HashMap
* 初始容量小于0或装载因子小于等于0将报异常
*/
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)//调整最大容量
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
int capacity = 1;
//设置capacity为大于initialCapacity且是2的幂的最小值
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}
/**
*根据指定容量创建一个空的HashMap
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);//调用上面的构造方法,容量为指定的容量,装载因子是默认值
}
/**
*通过传入的map创建一个HashMap,容量为默认容量(16)和(map.zise()/DEFAULT_LOAD_FACTORY)+1的较大者,装载因子为默认值
*/
public HashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
putAllForCreate(m);
}
上面的构造方法中调用到了init()方法,最后一个方法还调用了putAllForCreate(Map<? extends K, ? extends V> m)。init方法是一个空方法,里面没有任何内容。putAllForCreate看方法名就是创建的时候将传入的map全部放入新创建的对象中。该方法中还涉及到其他方法,将在后面介绍。
先看初始化table时均使用了Entry,这是HashMap的一个内部类,实现了Map接口的内部接口Entry。
下面给出Map.Entry
补充:软件开发 , Java ,