一,先说说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