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 }
View Code

函数:

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 分析的主要内容,如果未能解决你的问题,请参考以下文章

Java集合源码分析HashMap

大厂面试必问!HashMap 怎样解决hash冲突?

Android 插件化VirtualApp 源码分析 ( 目前的 API 现状 | 安装应用源码分析 | 安装按钮执行的操作 | 返回到 HomeActivity 执行的操作 )(代码片段

Java提高篇——通过分析 JDK 源代码研究 Hash 存储机制

什么意思 在HashMap之前 ? Java中的泛型[重复]

分析轮子- HashMap.java 之概念梳理