Java集合 3:HashMap,HashTable的实现和区别

一,先说说HashMap

HashMap的数据结构

transient Entry[] table;
static class Entry<K,V> implements Map.Entry<K,V>{
          final K key;
          V  value;
          Entry<K,V> next;
          final int hash;
      .....
//xwxw
}

HashMap存储函数的实现put(K key,V value):

源码有点长 就不在这里打出了 需要的话可以在eclispse中自行查看

根据源码可以看出,当程序试图将一个键值对放入HashMap中时,首先会计算这个key的hashCode值,然后对这个值进行再哈希,再把rehash过的值和(arr.length-1)进行按位与操作,得到存储的数组下标,如果该位置无链表节点,那就把这个键值对放到该位置,但是若该位置有节点,那么就对链表遍历,看是否有hash,key和要放入节点相同的节点,如果有,就替换掉该节点的Value值,如果没有相同的,就创建节点放入值,并使用头插法将该节点插入到链表表头。

主要用到以下几个方法:

public V put(K key,V value);
void addEntry(int hash,K key,V value,int backetIndex);
void resize(int newCapacity);
void tranfer(Entry[] newTable);
static indexFor(int h,int length);
//xwxw

再哈希的目的是为了减少哈希冲突(后面会说到),是元素能均匀分布在数组中,提高存取效率。

问:为什么数组长度设置为2的N次方呢?

     1.因为上面说到hashmap是通过hash值和(arr.length-1)按位与来得到存储的数组下标,而若数组的长度都是2的N次方,那么(arr.length-1)得到的二进制数每个位上的值都为1,那么与全部为1 的一个数进行与操作,速度会很快。

     2.当(arr.length)总是2的N次方时,hash值&(arr.length-1)的操作等价于对数组长度取模,但是&比%的效率更高。

     3.当(arr.length)为2的N次方时,不同的key算出来的index相同的机率更小,碰撞的机率也就更小。

 

HashMap的度函数的时间get(Object key):

    hashmap的get方法,是先通过key的两次哈希后的值与(arr.length-1)进行与操作,得到index,然后再对该数组下的链表进行遍历,因为是在数组中查找是O(1)所以速度是很快的,但是如果hash值相同的元素过多得话,就会造成该链表中数据过多,而在链表中查找数据又是通过遍历得到的,是O(n)的,就会影响到查找速度,特别注意:当通过get得到null值时,你无法判断是没找到指定元素,还是在hashmap中存在一个value为null的值,原因是hashmap中允许value的值为null。

HashMap中的扩容机制:

     当hashmap中节点个数大于arr.length*loadfactor(加载因子)时就会进行扩容,loadfactor的值默认为0.75,例如默认情况下数组长度为16,加载因子为0.75,也就是当节点个数大于16*0.75=12时,就进行扩容,由16扩展为32,增大一倍。然后再次重新计算每个元素在数组中的位置并放进去,注意!这个操作非常消耗性能。

 

多线程下hash容易出现的问题:

1,多线程下put操作,进行get操作时会出现死锁的情况,导致cpu满载,原因是当多线程进行put操作时,如果同时出发了rehash操作,会导致扩容后的hashmap中出现循环节点,再进行get操作,就会死循环。

2.多线程进行put操作可能会导致元素丢失。

二,Hashtable

实现基本与hashmap相同,就不在此赘述了。

三,HashMap和HashTable的区别:

1,hashtable线程安全,方法都是synchronized的,但效率较低。

    hashmap线程不安全,效率稍高,若要在的多线程下使用,需要手动同步,Collections.synchronsizedMap()。

2,hashmap的key和value都可以为null,hashtable的key和value都不能为null。

3,hashmap数组的默认大小是16,且一定是2的n次,扩容后值之前的两倍;

     hashtable的数组默认大小是11,扩容后是之前数组的两倍+1。

4,hashmap进行再hash作为hash值并通过hash&length-1来计算数组下标;

      hashtable直接使用对象的hashcode值作为hash值,并直接与数组长度进行与操作。

5,hashmap中null可以作为键,但是只能由一个,null也可以作为值,可以有多个,所以不能用get判断hashmap中是否存在某个键,需要用containsKey()方法来判断;

    hashtable由于键值都不能为null所以可以用get来判断。

 

在此附上HashMap的遍历方式,这是较快的一种 希望大家学习

 Map map = new HashMap();
  Iterator iter = map.entrySet().iterator();
  while (iter.hasNext()) {
  Map.Entry entry = (Map.Entry) iter.next();
  Object key = entry.getKey();
  Object val = entry.getValue();
  }
//xwxw

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章