疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

HashMap的创建与插入


 

 
在讲HashMap的创建之前,我们先看一个例子:
 
package test.hashmap;
 
import java.util.HashMap;
 
public class TestHashMap {
 
    public static void main(String[] args) {
        HashMap<String,Integer> hsTest= new HashMap<String,Integer>();
        hsTest.put("young", 22);
        hsTest.put("young", 23);
        hsTest.put(null, 0);
        System.out.println("the size of hsTest is " + hsTest.size());
    }
}
大家觉得,这个程序的输出结果是多少呢?
 
HashMap的创建
HashMap是基于数组创建的,HashMap最核心的结构是 transient Entry<K,V>[] table ,这个是真正存储HashMap数据的结构,也就是说,我们存在HashMap中的数据就是存在这个数组里头的,既然是数组,那么问题来了,数组的大小是多少呢?如果我们一直往里面增加数据的时候,那数组的容量够用吗? 
在new HashMap的时候,会调用HashMap的无参构造函数,而无参构造函数会调用另外一个具有两个参数的构造函数 public HashMap(int initialCapacity, float loadFactor),前一个参数是HashMap的初始化大小,也就是刚才提到的数组大小,即在创建HashMap的时候,会自动创建一个大小长度为initialCapacity的数组table,值得一提的是,如果没有指定数组table的初始化大小,则数组table的初始化大小默认为16,那么,细心的同学可能马上都注意到了,大小才16?没错,只有16,但是,请放心,我们再看后一个参数loadFactor,它是装载因子,和initialCapacity一样,可以在构造函数中指定大小,如果没有指定的话,则默认值为0.75,意思就是,如果当前数组中实际存储的元素个数与数组长度的比值达到0.75时,数组长度会翻一倍。也就是,当数组实际存储量达到0.75时,数组的长度就会自动扩充一倍,所以,我们不用担心,HashMap的长度会随着我们存储的数量增大而自动变长,那么长度最大是多少呢,答案是1<<32。
 
HashMap的插入
在使用HashMap时,我们要往其中插入数据,这个时候会用到put方法,这个方法可以往HashMap中插入键/值对,如果存在当前要插入的键,则用当前的键/值对替换之前的数据。接下来看下HashMap put 的源代码,如下:
 
 public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);  //解释(1)
        int hash = hash(key);  //解释(2)
        int i = indexFor(hash, table.length);  //解释(3)
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }//解释(4)
 
        modCount++;
        addEntry(hash, key, value, i);  //解释(5)
        return null;
    }
(1):在HashMap中,我们可以往其中插入为null的key,判断key是否为空,如果为空,则调用putForNullKey函数,putForNullKey的功能为是,查找原来HashMap中key为null的项,如果存在,则用当前的value,替换原来的value,并返回原来的value。如果不存在,则插入。之所以要单独处理,是因为如果不为null,我们需要对key进行hash映射,计算key的hash值,找到当前key在HashMap中的位置,而key为null的时候,直接默认key的hash值为0。 
(2):根据某种hash算法,计算key的hash值。 
(3):根据key的hash值与hash表的长度,计算当前这个key在hash表中的索引。 
(4):这个for循环的功能是:在原来的HashMap中,查找是否存在我现在正要插入的这个key,如果存在,则把此key对应的原来的value用当前这个value替换,并返回旧的value。值得注意的是,从for循环中可以发现,HashMap虽然是基于数组的,但同时也是基于链表的,因为我们知道,Hash表有可能会起冲突,即key的hash值一样,如果我们仅采用数组,那么当hash值一样时,在一个数组元素中,我们不可能存储两个值,因此,HashMap不仅基于数组,也基于链表,即先通过key的hash值,找到key在数组中的位置,然后每个hash相同的key又采用链表存储,因此,我们在用key的hash值定位到数组的位置时,仍然还要使用for循环来查找链表里是否真的存在当前这个key,从for中可以看到,e=e.next,就是找下一个节点。 
(5):前面的几个相当于,都是在做前期准备工作,判断key不是null,也确定当前这个key在HashMap中没有存储,接下来就是往这个HashMap插入这个键/值对。
 
讲解到了这里,上面那个例子就so easy了。 
1、可以在HashMap中插入key为null的键/值对。 
2、在HashMap中插入数据时,如果存在相同key,则用新的value替换旧的value 
因此,上面的输出结果为 2
 
顺便说一句,HashMap不是线程安全的。
 
小结
1)HashMap的初始大小为16,装载因子为0.75,当数组的装载量达到装载因子时,则HashMap的大小自动扩充一倍,可以达到的最大容量为1<<32 
2)HashMap可以插入key为null的键 
3)HashMap在插入的时候,如果遇到相同的key,则用新的value替换旧的value 
4)HashMap是基于数组的,不同的hash的key存在数组的不同位置,相同hash的key采用链表连接起来 
5)HashMap不是线程安全的