标签:
一、数据结构 Map将实际数据存储在Entry类的数组中。 代码片段:
transient Entry[] table;//HashMap的成员变量,存放数据 static class Entry<K,V> implements Map.Entry<K,V> {//内部类Entry final K key; V value; Entry<K,V> next;//指向下一个数据 final int hash; /** * Creates new entry. */ Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; }
执行下面代码后,可能的存储内部结构是: Map map = new HashMap(); map.put("key1","value1"); map.put("key2","value2"); map.put("key3","value3"); 执行put方法时根据key的hash值来计算放到table数组的下标,如果hash到相同的下标,则新put进去的元素放到Entry链的头部,如上图所示。put方法的源码后面详细解释。
二、属性和构造方法
static final int DEFAULT_INITIAL_CAPACITY = 16;//默认的初始大小,如果执行无参的构造方法,则默认初始大小为16 static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量,1073741824 static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认的负载因子,如果没有通过构造方法传入负载因子,则使用0.75。 transient Entry[] table; //存放具体键值对的Entry数组 transient int size; //HashMap的大小 int threshold;//阀值 threshold = (int)(capacity * loadFactor); 即容量*负载因子,执行put方法时如果size大于threshold则进行扩容,后面put方法将会看到 final float loadFactor; //用户设置的负载因子 transient volatile int modCount;//HashMap实例被改变的次数,这个同ArrayList
构造方法一、
public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); table = new Entry[DEFAULT_INITIAL_CAPACITY]; init(); }
使用了默认的容量和默认的负载因子。 构造方法二、
public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); }
使用了用户设置的初始容量和默认的负载因子。 构造方法三、
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); // Find a power of 2 >= initialCapacity int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; this.loadFactor = loadFactor; threshold = (int)(capacity * loadFactor); table = new Entry[capacity]; init(); }
用户传入了初始容量和负载因子,这两个值是HashMap性能优化的关键,涉及到了HashMap的扩容问题。 HashMap的容量永远是2的倍数,如果传入的不是2的倍数则被调整为大于传入值的最近的2的倍数,例如如果传入130,则capacity计算后是256。是这段代码起的作用:
while (capacity < initialCapacity) capacity <<= 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);//计算Map的大小 putAllForCreate(m);//初始化 } private void putAllForCreate(Map<? extends K, ? extends V> m) { for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {//通过entryset进行遍历 Map.Entry<? extends K, ? extends V> e = i.next(); putForCreate(e.getKey(), e.getValue()); } }
根据传入的map进行初始化。
三、关键方法 1)put
public V put(K key, V value) { if (key == null) return putForNullKey(value);//单独处理,总是放到table[0]中 int hash = hash(key.hashCode());//计算key的hash值,后面介绍性能的时候再说这个hash方法。 int i = indexFor(hash, table.length);//将hash和length-1取&来得到数组的下表 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; } }//如果这个key值,原来已经则替换后直接返回。 modCount++; addEntry(hash, key, value, i); return null; } void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<K,V>(hash, key, value, e);//如果table[bucketIndex]中已经存在Entry则放到头部。 if (size++ >= threshold)//如果大于了阀值,则扩容到原来大小的2倍。 resize(2 * table.length); } void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable);//赋值到新的table中,注意转移后会重新hash,所以位置可能会跟之前不同,目的是均匀分不到新的table中。 table = newTable; threshold = (int)(newCapacity * loadFactor); }
2)get方法
public V get(Object key) { if (key == null) return getForNullKey(); int hash = hash(key.hashCode()); for (Entry<K,V> e = table[indexFor(hash, table.length)];//找到数组的下表,进行遍历 e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value;//找到则返回 } return null;//否则,返回null }
3)remove方法
public V remove(Object key) { Entry<K,V> e = removeEntryForKey(key); return (e == null ? null : e.value); } final Entry<K,V> removeEntryForKey(Object key) { int hash = (key == null) ? 0 : hash(key.hashCode()); int i = indexFor(hash, table.length); Entry<K,V> prev = table[i]; Entry<K,V> e = prev; while (e != null) {//Entry链未遍历完则一直遍历 Entry<K,V> next = e.next; Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; if (prev == e)//如果是第一个,则将table[i]执行e.next table[i] = next; else //否则将前一个的next指向e.next prev.next = next; e.recordRemoval(this); return e; } prev = e;//未找到则继续往后遍历 e = next; } return e; }
4)HashMap的遍历方法
Map map = new HashMap(); map.put("key1","value1"); map.put("key2","value2"); map.put("key3", "value3"); for(Iterator it = map.entrySet().iterator();it.hasNext();){ Map.Entry e = (Map.Entry)it.next(); System.out.println(e.getKey() + "=" + e.getValue()); } System.out.println("-----------------------------------------"); for(Iterator it = map.keySet().iterator();it.hasNext();){ Object key = it.next(); Object value = map.get(key); System.out.println(key+"="+value); } System.out.println("-----------------------------------------"); System.out.println(map.values()); 输出为: key3=value3 key2=value2 key1=value1 ----------------------------------------- key3=value3 key2=value2 key1=value1 ----------------------------------------- [value3, value2, value1]
四、性能相关 1)hash 如果总计算到相同的数组下标,则得进行Entry的遍历来取值和存放值,必然会影响性能。 所以HashMap提供了hash方法,来解决key的hashCode方法质量不高问题。
public V put(K key, V value) { ... int hash = hash(key.hashCode()); ... } static int hash(int h) { // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
2)初始容量和负载因子 因为put的时候可能需要做扩容,扩容会导致性能损耗,所以如果可以预知Map大小的话,可以设置合理的初始大小和负载因子来避免HashMap的频繁扩容导致的性能消耗。
void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<K,V>(hash, key, value, e); if (size++ >= threshold) resize(2 * table.length); }
五、同Hashtable的区别 1)HashMap允许key和value都可以为null,Hashtable都不可以为空。 HashMap的put方法,代码片段:
if (key == null) return putForNullKey(value);
Hashtable的put方法,代码片段:
if (value == null) { throw new NullPointerException(); } Entry tab[] = table; int hash = key.hashCode();
2)HashMap是非线程安全的,Hashtable是线程安全的。 Hashtable的put和get方法均为synchronized的。 六、ConcurrentHashMap ConcurrentHashMap是Doug Lea写的线程安全的HashMap实现,将HashMap默认划分为了16个Segment,减少了锁的争用,另外通过写时加锁读时不加锁减少了锁的持有时间,优雅的解决了高并发下锁的高竞争问题。
标签:
原文地址:http://www.cnblogs.com/tianxia12/p/4660418.html