JAVA API HashMap 分析
Posted 晚起的虫儿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA API HashMap 分析相关的知识,希望对你有一定的参考价值。
AbstractMap:
数据结构:
Entry<K,V> 是 Map接口内部的一个接口,在具体的实现类中会被实现成不同静态内部类,他们有不同的的键值对结构.
Set<K> keySet();
Collection<V> values();
transient volatile Set<K> keySet = null;
transient volatile AbstractCollection<V> values = null;
keySet,values被设置为第一次指向的对象.以后使用该对象通过public Set<K> keySet()和public Collection<V> values()方法获得,
从中也知道了键在集合中是以Set形式存在,值是以Collection形式存在,Entry<K,V>也是以Set形式存在,当然它们本身都是有各自的类型。
抽象方法:
1 public abstract Set<Entry<K,V>> entrySet(); 2 //下面的两个方法都是依靠了这个抽象方法进行的编程。 3 private Iterator<Entry<K,V>> i = entrySet().iterator();//返回的是实现类中内部类HashIterator这个对象。 4 public Set<K> keySet() { 5 if (keySet == null) { 6 keySet = new AbstractSet<K>() { 7 public Iterator<K> iterator() { 8 return new Iterator<K>() { 9 10 private Iterator<Entry<K,V>> i = entrySet().iterator(); 11 12 public boolean hasNext() { 13 return i.hasNext(); 14 } 15 16 public K next() { 17 return i.next().getKey(); 18 } 19 20 public void remove() { 21 i.remove(); 22 } 23 24 }; 25 } 26 27 public int size() { 28 return AbstractMap.this.size(); 29 } 30 31 public boolean isEmpty() { 32 return AbstractMap.this.isEmpty(); 33 } 34 35 public void clear() { 36 AbstractMap.this.clear(); 37 } 38 39 public boolean contains(Object k) { 40 return AbstractMap.this.containsKey(k); 41 } 42 }; 43 } 44 return keySet; 45 } 46 public Collection<V> values() { 47 if (values == null) { 48 values = new AbstractCollection<V>() { 49 public Iterator<V> iterator() { 50 return new Iterator<V>() { 51 private Iterator<Entry<K,V>> i = entrySet().iterator(); 52 53 public boolean hasNext() { 54 return i.hasNext(); 55 } 56 57 public V next() { 58 return i.next().getValue(); 59 } 60 61 public void remove() { 62 i.remove(); 63 } 64 }; 65 } 66 67 public int size() { 68 return AbstractMap.this.size(); 69 } 70 71 public boolean isEmpty() { 72 return AbstractMap.this.isEmpty(); 73 } 74 75 public void clear() { 76 AbstractMap.this.clear(); 77 } 78 79 public boolean contains(Object v) { 80 return AbstractMap.this.containsValue(v); 81 } 82 83 84 }; 85 } 86 return values; 87 }
方法:
1 public int size(); 2 public boolean isEmpty(); 3 public boolean containsValue(Object value); 4 public boolean containsKey(); 5 public abstract Set<Entry<K,V>> entrySet(); 6 //------------------------------------ 7 public V get(Object key); 8 //存入key返回的是对应的值。 9 //----------------------------------- 10 public V put(K key, V value); 11 //必须要重写不然直接抛出UnsupportedOperationException()异常。 12 //---------------------------------- 13 public V remove(Object key); 14 public void putAll; 15 public void clear(); 16 //------------------------------------------------- 17 public boolean equals(Object o); 18 //判断两个map是否相等,原理是把a中的key放到b的get()取得的value和a的value进行比较,一项不满足就返回false; 19 //------------------------------------------------- 20 public int hashCode(); 21 //把每个元素的hashCode()加起来返回:h += i.next().hashCode(); 22 //--------------------------------------- 23 public String toString(); 24 clone(); 25 //以上方法大多面向抽象的编程,很多函数里都有entrySet().iterator();
HashMap:
构造方法:
1 public HashMap() { 2 this.loadFactor =DEFAULT_LOAD_FACTOR;// all otherfields defaulted 3 } 4 public HashMap(intinitialCapacity) { 5 this(initialCapacity,DEFAULT_LOAD_FACTOR); 6 } 7 8 public HashMap(intinitialCapacity,floatloadFactor) { 9 10 } 11 public HashMap(Map<?extends K, ?extends V>m) { 12 13 }
1. capacity,表示的是HashMap中桶的数量,初始化容量initCapacity为16,第一次扩容会扩到64,之后每次扩容都是之前容量的2倍,所以容量每次都是2的次幂;
2. loadFactor,装载因子,衡量HashMap一个满的程度,初始化为0.75,实时装载因子是size/capacity;
3. threshold,HashMap扩容的一个标准,每当size大于这个标准时就会进行扩容操作,threshold等于capacity*loadFactor;
数据结构:
static final Entry<?,?>[] EMPTY_TABLE = {}; transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
集合特性:
private final class EntrySet extends AbstractSet<Map.Entry<K,V>> { public Iterator<Map.Entry<K,V>> iterator() { return newEntryIterator(); } public boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<K,V> e = (Map.Entry<K,V>) o; Entry<K,V> candidate = getEntry(e.getKey()); return candidate != null && candidate.equals(e); } public boolean remove(Object o) { return removeMapping(o) != null; } public int size() { return size; } public void clear() { HashMap.this.clear(); } }
迭代器:
1 private abstract class HashIterator<E> implements Iterator<E> { 2 Entry<K,V> next; // next entry to return 3 int expectedModCount; // For fast-fail 4 int index; // current slot 5 Entry<K,V> current; // current entry 6 7 HashIterator() { 8 expectedModCount = modCount; 9 if (size > 0) { // advance to first entry 10 Entry[] t = table; 11 while (index < t.length && (next = t[index++]) == null) 12 ; 13 } 14 } 15 16 public final boolean hasNext() { 17 return next != null; 18 } 19 20 final Entry<K,V> nextEntry() { 21 if (modCount != expectedModCount) 22 throw new ConcurrentModificationException(); 23 Entry<K,V> e = next; 24 if (e == null) 25 throw new NoSuchElementException(); 26 27 if ((next = e.next) == null) { 28 Entry[] t = table; 29 while (index < t.length && (next = t[index++]) == null) 30 ; 31 } 32 current = e; 33 return e; 34 } 35 36 public void remove() { 37 if (current == null) 38 throw new IllegalStateException(); 39 if (modCount != expectedModCount) 40 throw new ConcurrentModificationException(); 41 Object k = current.key; 42 current = null; 43 HashMap.this.removeEntryForKey(k); 44 expectedModCount = modCount; 45 } 46 }
HashIterator<Map.Entry<K,V>>是HashMap的内部类,其为抽象类,它没有实现next()方法,但是却添加了具有next()功能的函数nextEntry()。
while (index < t.length && (next = t[index++]) == null);
HashMap迭代的精髓就这句话。同样说明了HashMap不能保证元素的顺序。
HashMap的迭代器实现分成3种:
1 Iterator<V> newValueIterator() { 2 return new ValueIterator(); 3 } 4 private final class ValueIterator extends HashIterator<V> { 5 public V next() { 6 return nextEntry().value; 7 } 8 } 9 10 Iterator<K> newKeyIterator() { 11 return new KeyIterator(); 12 } 13 private final class KeyIterator extends HashIterator<K> { 14 public K next() { 15 return nextEntry().getKey(); 16 } 17 } 18 19 Iterator<Map.Entry<K,V>> newEntryIterator() { 20 return new EntryIterator(); 21 } 22 private final class EntryIterator extends HashIterator<Map.Entry<K,V>> { 23 public Map.Entry<K,V> next() { 24 return nextEntry(); 25 } 26 }
Map元素的迭代其实是其内部接口Entry<K,V>的实现的迭代,所以要迭代Map需要使用entrySet()方法取得Set<Entry<K,V>>类型的对象然后取得该对象的迭代器。
三个内部类中Values KeySet EntrySet。反而是EntrySet的迭代速度最快。
1 package test; 2 3 import java.util.Collection; 4 import java.util.HashMap; 5 import java.util.Iterator; 6 import java.util.Map; 7 import java.util.Map.Entry; 8 9 public class MapTest { 10 11 public static <K> void main(String[] args) { 12 13 Map<String,String> hashMap = new HashMap<String, String>(); 14 15 for(int i=0;i<100000;i++) 16 hashMap.put(i+"", i+"v"); 17 18 long time = System.currentTimeMillis(); 19 20 time = System.currentTimeMillis(); 21 System.out.println("==============方式2:通过遍历values()遍历HashMap的value"); 22 Collection<String> values = hashMap.values(); 23 for(Iterator<String> valIt = values.iterator();valIt.hasNext();){ 24 valIt.next(); 25 } 26 System.out.println("用时:"+(System.currentTimeMillis() - time)); 27 28 System.out.println("==============方式1:通过遍历keySet()遍历HashMap的value"); 29 Iterator<String> it = hashMap.keySet().iterator(); 30 while(it.hasNext()){ 31 it.next(); 32 //System.out.println(hashMap.get(it.next())); 33 } 34 System.out.println("用时:"+(System.currentTimeMillis() - time)); 35 36 time = System.currentTimeMillis(); 37 System.out.println("==============方式3:通过entrySet().iterator()遍历HashMap的key和映射的value"); 38 Iterator<Entry<String, String>> entryIt = hashMap.entrySet().iterator(); 39 while(entryIt.hasNext()){ 40 entryIt.next().getKey(); 41 entryIt.next().getValue(); 42 //System.out.println("key:"+entry.getKey()+" value:"+entry.getValue()); 43 } 44 System.out.println("用时:"+(System.currentTimeMillis() - time)); 45 } 46 47 }
函数:
final int hash(Object k); static int indexFor(int h, int length);
见JAVA API HashMap hash()函数解析.
private V getForNullKey(); private V getForNullKey() { if (size == 0) { return null; } for (Entry<K,V> e = table[0]; e != null; e = e.next) { if (e.key == null) return e.value; } return null; }
说明当key是null的时候会调用该函数,可以看到默认遍历的是table[0]的位置,默认key==null的hashCode为0.
1 public V put(K key, V value); 2 public V put(K key, V value) { 3 if (table == EMPTY_TABLE) { 4 inflateTable(threshold); 5 } 6 if (key == null) 7 return putForNullKey(value); 8 int hash = hash(key); 9 int i = indexFor(hash, table.length); 10 //Key的位置已经有旧的值了 则把新值给旧值 并返回旧值 11 for (Entry<K,V> e = table[i]; e != null; e = e.next) { 12 Object k; 13 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 14 V oldValue = e.value; 15 e.value = value; 16 e.recordAccess(this); 17 return oldValue; 18 } 19 } 20 21 modCount++; 22 //如果Key是新的则调用addEntry插入新的Key和value。 23 addEntry(hash, key, value, i); 24 return null; 25 }
put函数保证了EntrySet集合中元素的唯一性。
((k = e.key) == key || key.equals(k))可见即使对象不同只要两者的值相同就行,前提是重写了toString().
从上面代码我们同样可以看出,不同的key可能散列出形同的hashCode,不同的hashCode可能定位到相同的table[i],所以在比较的时候要要同时判断hashCode和key
如果遍历完链表还没有找到进入if语句的元素,说明可能发生了上面不同的key定位到了相同的table[i]的位置,所以就把这个新元素添加到表头。
1 void createEntry(int hash, K key, V value, int bucketIndex) { 2 Entry<K,V> e = table[bucketIndex]; 3 table[bucketIndex] = new Entry<>(hash, key, value, e); 4 size++; 5 }
当中的形式参数e被赋值给了next,意识就是新的头节点指向了旧的头节点。
Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; }
在这里String对象是特殊的,在hash()函数中有。
if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); }
即使是不同的String对象只要值是相同的就认为是相同的key。
以上是关于JAVA API HashMap 分析的主要内容,如果未能解决你的问题,请参考以下文章
Android 插件化VirtualApp 源码分析 ( 目前的 API 现状 | 安装应用源码分析 | 安装按钮执行的操作 | 返回到 HomeActivity 执行的操作 )(代码片段
Java提高篇——通过分析 JDK 源代码研究 Hash 存储机制