首先,我们看看桶的主要源码

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        int hash;

        /**
         * Creates new entry.
         */
        Entry(int h, K k, V v, Entry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
}

在这里很多人都已经知道,其实就是一个链表

我们由外部的

transient Entry<K,V>[] table;

得知HashMap是数组加链表,我们为方便常常把这个链表看成桶

下面我们写一个测试用例来进行测试

package Map;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

public class HashMapTest {

	
	public static void main(String[] args) {
		Map<Integer,Integer> m=new HashMap<>();
		m.put(1, 1);
		m.put(1, 2);//由于1和1的hashcode相等,用equels判断又相等,所以直接替换
		m.put(3, 3);
		m.put(0, 3);
		Iterator iter=m.entrySet().iterator();
		while(iter.hasNext()){
			Map.Entry<Integer,Integer> entry=(Entry<Integer, Integer>) iter.next();
			System.out.println(entry.getKey()+":"+entry.getValue()+
					" hashCode="+entry.getKey().hashCode());
		}
		Map<Person,Integer> pm=new HashMap<>();
		Person p1=new Person("小明",19);
		Person p2=new Person("小强",19);
		Person p3=new Person("大雄",19);
		Person p4=new Person("安妮",5);
		pm.put(p1, 1);
		pm.put(p2, 2);//由于p1和p2的hashcode相等,用equels判断不相等,所以在p1的桶后面继续添加p2进桶
		pm.put(p3, 3);
		pm.put(p4, 4);
		Iterator piter=pm.entrySet().iterator();
		while(piter.hasNext()){
			Map.Entry<Person,Integer> entry=(Entry<Person, Integer>) piter.next();
			System.out.println(entry.getKey().name+":"+entry.getValue()+
					" hashCode="+entry.getKey().hashCode());
			//输出时发现小强在小明前面,证明小强在链头,而小明在后面
		}
	}
}
class Person{
	String name;
	int age;
	Person(String name,int age){
		this.name=name;
		this.age=age;
	}
	public int hashCode(){
		return age;
	}
	public boolean equals(Object o){
		if(o instanceof Person){
			return name.equals(((Person) o).name);
		}else{
			return false;
		}
	}
}

  

输出结果为:

0:3 hashCode=0
1:2 hashCode=1
3:3 hashCode=3
大雄:3 hashCode=19
小强:2 hashCode=19
小明:1 hashCode=19
安妮:4 hashCode=5

  从其中我们可以看出,HashMap在加入一个key的时候,先查看key的hashcode是否已存在,如果已存在,则通过equals判断key是否相等:如果相等,那么直接替换对应值;

如果不相等,则加入同一个桶内,并且放在链表的表头,将桶内所有数据后移一位。

我们观察HashMap添加数据时的源码

  public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        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;
    }

  我们发现在循环中做判断key是否相等,如果不相等,则通过addEntry来添加数据,源码如下:

    void addEntry(int hash, K key, V value, int bucketIndex) {
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

        createEntry(hash, key, value, bucketIndex);
    }

    void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

我们也可以发现HashMap并非完全无序,在链表部分是有序的,但是我们要尽可能做到减小链表的长度,最好链表长度为1,这样才能保证查询效率为O(1),不被链表O(n)所干扰。

 

posted on 2016-05-05 19:23  cxxbro  阅读(322)  评论(0)    收藏  举报