当前位置:编程学习 > JAVA >>

java源码分析之HashMap

在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 ,
CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,