Java集合之HashMap源码实现分析

Java集合之HashMap源码实现分析

本文转自:http://www.cnblogs.com/zhangminghui/p/4184489.html

  1.简介

  通过上面的一篇随笔我们知道了HashSet的底层是采用Map实现的,那么Map是什么?它的底层又是如何实现的呢?这下我们来分析下源码,看看具体的结构与实现。Map 集合类用于存储元素对(称作“键”和“值”),其中每个键映射到一个值。Map.Entry是其的内部类,描述Map中的按键/数值对。需要指出的是Map,允许null的键也允许null的值。它的实现主要有HashMap和sortedMap,其中SortedMap扩展了Map使按键保持升序排列,下面我们简要分析下HashMap的具体实现。首先给出一个应用举例:

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package  com.test.collections;
 
import  java.util.Collection;
import  java.util.HashMap;
import  java.util.Map;
import  java.util.Set;
 
public  class  HashMapTest {
 
     /**
      * @param args
      */
     public  static  void  main(String[] args) {
         // TODO Auto-generated method stub
         Map<String,String> map =  new  HashMap<String,String>();
         map.put( "A" "A" );
         map.put( "D" "D" );
         map.put( "S" "S" );
         map.put( "C" "C" );
         map.put( "B" "B" );
         map.put( "W" "W" );
         
         System.out.println(map.size());
         System.out.println(map.isEmpty());
         System.out.println(map.containsKey( "A" )); //boolean
         System.out.println(map.containsValue( "A" ));; //boolean
         System.out.println(map.get( "A" ));
         System.out.println(map.remove( "A" ));
         map.putAll(map);
         Set<String> keySet = map.keySet();
         Collection<String>  values = map.values();
         Set<Map.Entry<String, String>> entry = map.entrySet();
         map.clear();
     }
 
}

  2.继承结构

  HashMap直接继承了AbstractMap类,实现了Map<K,V>, Cloneable, Serializable接口,除了这些继承和实现外,它还有一些重要的属性值。简单看一下:

    DEFAULT_INITIAL_CAPACITY:默认的初始化容量(16);

    MAXIMUM_CAPACITY:能够允许的最大容量(1 << 30);

    DEFAULT_LOAD_FACTOR:缺省的加载因子(0.75);

    Entry[] table:存储具体的值。

    transient int size:记录Map的大小。

    final float loadFactor:加载因子。

上面的属性中除了一个Entry类型,这个是什么意思呢。原来这里面就是维护Map键值的类,用来存储Map的具体值的,让我们来看下它的具体实现结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
static  class  Entry<K,V>  implements  Map.Entry<K,V> {
        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;
        }
 
        public  final  K getKey() {
            return  key;
        }
 
        public  final  V getValue() {
            return  value;
        }
 
        public  final  V setValue(V newValue) {
        V oldValue = value;
            value = newValue;
            return  oldValue;
        }
 
        public  final  boolean  equals(Object o) {
            if  (!(o  instanceof  Map.Entry))
                return  false ;
            Map.Entry e = (Map.Entry)o;
            Object k1 = getKey();
            Object k2 = e.getKey();
            if  (k1 == k2 || (k1 !=  null  && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = e.getValue();
                if  (v1 == v2 || (v1 !=  null  && v1.equals(v2)))
                    return  true ;
            }
            return  false ;
        }
 
        public  final  int  hashCode() {
            return  (key== null    0  : key.hashCode()) ^
                   (value== null  0  : value.hashCode());
        }
 
        public  final  String toString() {
            return  getKey() +  "="  + getValue();
        }
 
        /**
         * This method is invoked whenever the value in an entry is
         * overwritten by an invocation of put(k,v) for a key k that's already
         * in the HashMap.
         */
        void  recordAccess(HashMap<K,V> m) {
        }
 
        /**
         * This method is invoked whenever the entry is
         * removed from the table.
         */
        void  recordRemoval(HashMap<K,V> m) {
        }
    }

  Entry<K,V> 实现了Map接口中的内部接口Map.Entry<K,V>,key,value分别用来存储键值的, Entry<K,V> next还指向了下一个节点的指针,说明Map的存储空间不是连续的可以使分散的。Hash属性说明键的位置是根据Hash值算出来的。需要注意的是这是作为HashMap的内部类出现的。此外还有内部类KeySet,Values、EntrySet、ValueIterator、KeyIterator、EntryIterator不过通过类名称就可以知道他们的具体作用了吧。

3.源码解析

  a:构造函数

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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();
     }
public  HashMap( int  initialCapacity) {
         this (initialCapacity, DEFAULT_LOAD_FACTOR);
     }
 
    public  HashMap() {
         this .loadFactor = DEFAULT_LOAD_FACTOR;
         threshold = ( int )(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
         table =  new  Entry[DEFAULT_INITIAL_CAPACITY];
         init();
     }
 
    public  HashMap(Map<?  extends  K, ?  extends  V> m) {
         this (Math.max(( int ) (m.size() / DEFAULT_LOAD_FACTOR) +  1 ,
                       DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
         putAllForCreate(m);
     }
 
  void  init() {
     }
    private  void  putAllForCreate(Map<?  extends  K, ?  extends  V> m) {
         for  (Iterator<?  extends  Map.Entry<?  extends  K, ?  extends  V>> i = m.entrySet().iterator(); i.hasNext(); ) {
             Map.Entry<?  extends  K, ?  extends  V> e = i.next();
             putForCreate(e.getKey(), e.getValue());
         }
     }

  构造方法只需要说明第一个的即可,其他的都是传递一些缺省的参数值然后调用第一个构造方法实现具体的操作。重点看下第一个构造方法。它首先判断一下传入的容量是否合法以及加载因子是否合法。如果容量操作最大值是需要将它重置的,但是如果传入的值为负数是要抛出异常的。然后根据容量与加载因子的乘积得出临界值并且赋值给属性threshold,然后通过 table = new Entry[capacity]分配存储空间,完成了构造过程。Init()方法为空,不知道做了什么事情。

2.hash(int  h)

  

1
2
3
4
5
6
7
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 );
    }

  根据Hash 值确定键的位置,如果传入的为null,那么返回的hash值就是为0,索引值就是0.

3.size(),isEmpty()

1
2
3
4
5
6
7
8
9
10
11
12
public  int  size() {
      return  size;
  }
 
  /**
   * Returns <tt>true</tt> if this map contains no key-value mappings.
   *
   * @return <tt>true</tt> if this map contains no key-value mappings
   */
  public  boolean  isEmpty() {
      return  size ==  0 ;
  }

  Map大小就是直接返回属性值size的值,判断是否为空就是如果size为0说明为空否则不为空。

4.get(Object)

1
2
3
4
5
6
7
8
9
10
11
12
13
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,如果为空的话就调用getForNullKey()返回空键对应的值只会有一个。其实也就是返回Map的第一个值,因为null对应的hash值为0,存储位置就是第一个。然后调用hash()方法返回唯一对应的hash值,然后再循环遍历这个Map,如果发现Hash值相等就直接返回它的值,如果没有发现对应的值就返回null.

5.containsKey(Object )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public  boolean  containsKey(Object key) {
      return  getEntry(key) !=  null ;
  }
  final  Entry<K,V> getEntry(Object key) {
      int  hash = (key ==  null ) ?  0  : 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 !=  null  && key.equals(k))))
              return  e;
      }
      return  null ;
  }

  判断是否含有某个值就是首先获取这个值,如果获取的值不为空就说么这个对象是存在的。获取key的方法是根据getKey()方法来实现的。首先获取Hash值,然后遍历循环这个Entry数组,如果遇到键相同就返回否则就返回为null.

6.put(K,V)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public  V put(K key, V value) {
      if  (key ==  null )
          return  putForNullKey(value);
      int  hash = hash(key.hashCode());
      int  i = indexFor(hash, table.length);
      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;
          }
      }
 
      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);
      if  (size++ >= threshold)
          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 = newTable;
      threshold = ( int )(newCapacity * loadFactor);
  }

  我们是通过put(K,V)方法来添加对象到Map中的,首先判断传入的Key是否为null,如果为null就直接调用putForNullKey(value)方法将null的键对应上值。如果不为空就首先获取K对应的Hash值,然后遍历循环这个Map,如果值已经存在就更新覆盖value并且返回返回老的value,否则的话就调用addEntry(hash, key, value, i);插入新值。插入及很简单了,New一个Entry对象然后确定数组位置指定值即可。

4.其他

  HashMap的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。容量是哈希表中数据的数量,初始容量只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的数目。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值