HashMapソースコードの理解


HashMap対応のソースコードを見てみましょう.
1.クラス、インタフェース関係
public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

クローンとシーケンス化が分からないので、まずMapを見てください.
2.実装インタフェースMap
public interface Map<K,V> {
     //            ,    。
     int size();
     boolean isEmpty();
     boolean containsKey(Object key);
     boolean containsValue(Object value);
     V get(Object key);
     V put(K key, V value);
     V remove(Object key);
     void putAll(Map<? extends K, ? extends V> m);
     void clear();
     //            
     Set<K> keySet();
     //          
     Collection<V> values();
     Set<Map.Entry<K, V>> entrySet();
     boolean equals(Object o);
     int hashCode();
     //         ,       key-value  。
     interface Entry<K,V> {
     	K getKey();
        V getValue();
	V setValue(V value);
        boolean equals(Object o);
	int hashCode();
    }
}


3.継承された抽象クラスAbstractMap
この方法は700行あるから、いくつか選んで話します.
3.1構造方法
public abstract class AbstractMap<K,V> implements Map<K,V> {
    protected AbstractMap() {
    }
}

3.2いくつかの方法
簡単にいくつか紹介しても、実際の意味はありません.
//       ,     Map    ,  Set        key+value
public abstract Set<Entry<K,V>> entrySet();

//Map  
public int size() {
      return entrySet().size();
}

//    
public boolean isEmpty() {
	return size() == 0;
}

//       
public boolean containsValue(Object value) {
        //        ,        Set。
        //       for each,        jdk1.2,       。        。
	Iterator<Entry<K,V>> i = entrySet().iterator();
        //        。
	if (value==null) {
	    while (i.hasNext()) {
		Entry<K,V> e = i.next();
		if (e.getValue()==null)
		    return true;
	    }
	} else {
	    while (i.hasNext()) {
		Entry<K,V> e = i.next();
                //  :     equals,   ==
		if (value.equals(e.getValue()))
		    return true;
	    }
	}
	return false;
}

//     ,    Set
public V get(Object key) {
	Iterator<Entry<K,V>> i = entrySet().iterator();
	if (key==null) {
	    while (i.hasNext()) {
		Entry<K,V> e = i.next();
		if (e.getKey()==null)
		    return e.getValue();
	    }
	} else {
	    while (i.hasNext()) {
		Entry<K,V> e = i.next();
		if (key.equals(e.getKey()))
		    return e.getValue();
	    }
	}
	return null;
}

//          。
public V put(K key, V value) {
	throw new UnsupportedOperationException();
}

//       
public V remove(Object key) {
	Iterator<Entry<K,V>> i = entrySet().iterator();
	Entry<K,V> correctEntry = null;
        //key    null,    key        。
	if (key==null) {
	    while (correctEntry==null && i.hasNext()) {
		Entry<K,V> e = i.next();
		if (e.getKey()==null)
		    correctEntry = e;
	    }
	} else {
	    while (correctEntry==null && i.hasNext()) {
		Entry<K,V> e = i.next();
		if (key.equals(e.getKey()))
		    correctEntry = e;
	    }
	}
        //        
	V oldValue = null;
	if (correctEntry !=null) {
	    oldValue = correctEntry.getValue();
            //         。
	    i.remove();
	}
        //     value。
	return oldValue;
}

//    Map  ,      
public void putAll(Map<? extends K, ? extends V> m) {
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            put(e.getKey(), e.getValue());
}

//     set clear  
public void clear() {
	entrySet().clear();
}


AbstractMapはここを見ていますが、主にHashMapを見ています.
4.HashMap
4.0テキストの説明
HashMapはまず16空間のEntry(キー値ペア)配列を開き、
Entryクラスに含まれる
final K key;
V value;
Entry<K,V> next;
final int hash;
 
そして、格納されたkeyに対してhash演算を行い、1つの数字(1-15)を算出し、対応する位置に格納する.
対応する位置にデータがある場合は、現在のデータの代わりに、元のデータの参照を新しいデータ、すなわちnextに割り当てます.一定の値に保存すると、スペースが拡張されます.
クエリーの時にkeyを計算して(1-15)、それから対応する位置をクエリーしてnextに行って、データを取るか終わるかを知っています.
4.1コンストラクション関数.
public HashMap() {
        //     0.75f
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        //    16*0.75=12,    12           
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        //    16
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        //init      。
        init();
}

4.2 putメソッド
public V put(K key, V value) {
        //  key  ,       。
        if (key == null)
            //  key null  ,        。
            return putForNullKey(value);
        //  hash ,       
        int hash = hash(key.hashCode());
        //  hash          
        int i = indexFor(hash, table.length);
        //    。      
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            //    key  hash    。
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                //        value   。
                V oldValue = e.value;
                e.value = value;
                //          ,         。
                e.recordAccess(this);
                //   value  。
                return oldValue;
            }
        }
        //      key       map。
        //    
        modCount++;
        //  
        addEntry(hash, key, value, i);
        return null;
}
//      key。      0  。
private V putForNullKey(V value) {
        //          ,
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            //  key    
            if (e.key == null) {
                //    。
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                //        。
                return oldValue;
            }
        }
        modCount++;
        //    0   。
        addEntry(0, null, value, 0);
        return null;
}
//       ,    hash      。
void addEntry(int hash, K key, V value, int bucketIndex) {
        //            ,
	Entry<K,V> e = table[bucketIndex];
        //    ,  hash ,          。       ,              。
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        //  size     。
        if (size++ >= threshold)
            //          
            resize(2 * table.length);
}
//     
void resize(int newCapacity) {
        Entry[] oldTable = table;
        //      
        int oldCapacity = oldTable.length;
        //     。
        if (oldCapacity == MAXIMUM_CAPACITY) {
            //    int    。
            threshold = Integer.MAX_VALUE;
            //  。
            return;
        }
        //      ,        。
        Entry[] newTable = new Entry[newCapacity];
        //     
        transfer(newTable);
        // table  。
        table = newTable;
        //     。
        threshold = (int)(newCapacity * loadFactor);
}
//            ,             。
void transfer(Entry[] newTable) {
        //        。
        Entry[] src = table;
        int newCapacity = newTable.length;
        //      ,
        for (int j = 0; j < src.length; j++) {
            Entry<K,V> e = src[j];
            //             
            if (e != null) {
                src[j] = null;
                do {
                    Entry<K,V> next = e.next;
                    //  hash         。
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
}

4.3 getメソッド
//  key     。
public V get(Object key) {
        //key null  
        if (key == null)
            //   0      。
            return getForNullKey();
        //  key   hash。
        int hash = hash(key.hashCode());
        //  indexFor     table   ,      。        。
        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;
}
//  key  null value。
private V getForNullKey() {
        //   0  ,     next,    key null        ,  。
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
}

4.4一般的な方法
4.4.1 public boolean containsKey(Object key)
キーをhash計算し、キー値ペアを見つけて結果を返します.
4.4.2 public boolean containsValue(Object value)
配列を巡ってvalueが見つかったことを知っています.性能がかかる.
4.4.3  public void clear()
配列を巡り、すべてnullに設定します.
4.4.4 public V remove(Object key)
対応keyを見つけ、削除し、関連値を更新します.
4.4.5 public Set keySet()
配列全体を返し、このSetは単独で実装されたクラスで、反復器、size、含む、クリア、反復の結果をMapのkeyに返します.実は完全なMapコンテンツを返しました.余分なスペースが開かれていないので、この方法はすぐにできます.
4.4.6 public Collection values()
これは前述と同様に,Mapの全体データを直接返し,Collectionを反復器,size,含む,消去し,反復の結果をMapのvalueに返す.
 
5.使用するhashアルゴリズム
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
        return h & (length-1);
}

計算はすべてhashCodeで計算されるので、HashMapに格納されたオブジェクトはhashCodeを1つの値に書かないほうがいいです.hashCodeの各クラスには独自の生成方法があり、Objectはデフォルトでメモリ位置のようですが、IntegerやStringが上書きされているのを見たばかりです.
intは32ビットを占め,符号なし左シフト20ビット,空孔補0,符号なし右シフト20ビットである.まあ、このバイナリ演算子は系統的に見て、まずこのようにします.
6.終了
ここを見ると、HashMapの構造とhashアルゴリズムを理解するのが主です.
同時に二つの疑問が生じたので、先にメモしておきます.
疑問1:抽象クラスAbstractMapだけを実現してはいけませんか?なぜimplements Mapが必要なのか、繰り返さないのか.
疑問2:transient volatileが変数をこのように宣言したのはなぜですか.