码迷,mamicode.com
首页 > 其他好文 > 详细

HashMap源码学习笔记

时间:2015-08-17 17:18:56      阅读:129      评论:0      收藏:0      [点我收藏+]

标签:java   hashmap   

    HashMap的底层主要是基于数组和链表来实现的,它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置。HashMap中主要是通过key的hashCode来计算hash值的,只要hashCode相同,计算出来的hash值就一样。如果存储的对象对多了,就有可能不同的对象所算出来的hash值是相同的,这就出现了所谓的hash冲突。学过数据结构的同学都知道,解决hash冲突的方法有很多,HashMap底层是通过链表来解决hash冲突的。
    HashMap其实就是一个Entry数组,Entry对象中包含了键和值,其中next也是一个Entry对象,它就是用来处理hash冲突的,形成一个链表。数组的每个元素都是一个单链表的头节点,链表是用来解决冲突的,如果不同的key映射到了数组的同一位置处,就将其放入单链表中。

 Hashtable 与 HashMap类似,但是主要有6点不同。 
 1.HashTable的方法是同步的,HashMap未经同步,所以在多线程场合要手动同步HashMap这个区别就像Vector和ArrayList一样。  
 2.HashTable不允许null值,key和value都不可以,HashMap允许null值,key和value都可以。HashMap允许key值只能由一个null值,因为hashmap如果key值相同,新的key, value将替代旧的。  
 3.HashTable有一个contains(Object value)功能和containsValue(Object value)功能一样。  
 4.HashTable使用Enumeration,HashMap使用Iterator。  
 5.HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数

1、HashTable的方法是同步的,HashMap未经同步
    Hashtable 提供的几个主要方法,包括 get(), put(), remove() 等。每个方法本身都是 synchronized 的,会对整个对象进行加锁操作,不会出现两个线程同时对数据进行操作的情况,因此保证了线程安全性,但是也大大的降低了执行效率。

2、HashMap中hash数组的默认大小是16,而且一定是2的指数
  1. static int indexFor(int h, int length) {  
  2.        return h & (length-1);  
  3.    }  
    将key的二次hash值,与长度减一进行与操作,这一步可谓经典,通常我们会用取模的方式来定位数组中的某个位置,我们首先想到的就是把hashcode对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是,“模”运算的消耗还是比较大的,能不能找一种更快速,消耗更小的方式那?hashMap用这种方法,而且length即capacity的值,面capacity又是2的倍数,减1之后,表示成二进制就全部是1了,那么与全部为1的一个数进行与操作,速度会大大提升了。这就是为什么"capacity的值是2的倍数"

3、HashMap解决冲突的办法
    1)再哈希法
    2)拉链发
  1. public V put(K key, V value) {  
  2.        if (key == null)  
  3.            return putForNullKey(value);  
  4.        int hash = hash(key.hashCode());  
  5.        int i = indexFor(hash, table.length);  
  6.        for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  7.            Object k;  
  8.            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  9.                V oldValue = e.value;  
  10.                e.value = value;  
  11.                e.recordAccess(this);  
  12.                return oldValue;  
  13.            }  
  14.        }  
  15.   
  16.        modCount++;  
  17.        addEntry(hash, key, value, i);  
  18.        return null;  
  19.    }  
    根据key的hash值得到这个元素在数组中的位置(即下标),然后就可以把这个元素放到对应的位置中了。如果这个元素所在的位子上已经存放有其他元素了,那么在同一个位子上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。从hashmap中get元素时,首先计算key的hashcode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。
    调用int hash = hash(key.hashCode());这是hashmap的一个自定义的hash,在key.hashCode()基础上进行二次hash。再哈希法能够解决开放地址法的聚集现象,但是却增加了时间复杂度。
  1. static int hash(int h) {  
  2.         h ^= (h >>> 20) ^ (h >>> 12);  
  3.         return h ^ (h >>> 7) ^ (h >>> 4);  
  4.     }  
    改进传统的hash方法,而且尽量保证key的每一位都会影响到最后的hash值,以达到减少hash冲突的目的.

4、HashMap的构造函数
  1. if (initialCapacity < 0)  
  2.         throw new IllegalArgumentException("Illegal initial capacity: " +  
  3.                                            initialCapacity);  
  4.     if (initialCapacity > MAXIMUM_CAPACITY)  
  5.         initialCapacity = MAXIMUM_CAPACITY;  
  6.     if (loadFactor <= 0 || Float.isNaN(loadFactor))  
  7.         throw new IllegalArgumentException("Illegal load factor: " +  
  8.                                            loadFactor);  
  9.     // Find a power of 2 >= initialCapacity  
  10.     int capacity = 1;  
  11.     while (capacity < initialCapacity)  
  12.         capacity <<= 1;  
  13.     this.loadFactor = loadFactor;  
  14.     threshold = (int)(capacity * loadFactor);  
  15.     table = new Entry[capacity];  
  16.     init();  
  17. }  
    loadFactor :加载因子,加载因子与HashMap resize有关。默认为0.75
    capacity:容器大小,默认值为16 其大小为上面所说的数据结构中数组的长度。
    table:即为上面数据结构图中,X方向的数组(transient Entry[] table;)
    threshold :resize的临界值,即当HashMap中无素个数达到该值时,HashMap就会调用其resize方法,重新扩充大小。
    while (capacity < initialCapacity)
              capacity <<= 1;  
    这段代码说明什么:capacity的值是2的倍数,即使设置初始值是1000,hashmap也自动会将其设置为1024。

5、哈希表解决冲突的常用方法
    1)开放地址法
    2)拉链法
    3)再哈希法

拉链法的优点 ,与开放定址法相比,拉链法有如下几个优点: 
①拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
②由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
③开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
④在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。而对开放地址法构造的散列表,删除结点不能简单地将被删结 点的空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在 用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。
拉链法的缺点 
拉链法的缺点是:指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指

版权声明:本文为博主原创文章,未经博主允许不得转载。

HashMap源码学习笔记

标签:java   hashmap   

原文地址:http://blog.csdn.net/lyric_315/article/details/47727269

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!