数据结构

JDK1.7的HashMap采纳数组+单链表的数据结构,数组和链表存储的是一个个Entry对象

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

罕用办法

V get(Object key); //取得指定key的值V put(K key, V value);  //增加key-value对void putAll(Map<? extends K, ? extends V> m);  //将指定Map中的key-value对复制到此Map中V remove(Object key);  //删除该key-valueboolean containsKey(Object key); //判断是否存在该key的key-value对;是则返回trueboolean containsValue(Object value);  //判断是否存在该value的key-value对;是则返回true Set<K> keySet();  //独自抽取key序列,将所有key生成一个SetCollection<V> values();  //独自value序列,将所有value生成一个Collectionvoid clear(); // 革除HashMap中的所有key-value对int size();  // 返回HashMap中所有key-value对的数量boolean isEmpty(); // 判断HashMap是否为空,size == 0时示意为空 

应用

public class HashMapTest {    public static void main(String[] args) {      /**        * 1. 申明1个 HashMap的对象        */        Map<String, Integer> map = new HashMap<String, Integer>();      /**        * 2. 向HashMap增加数据(放入键-值对)        */        map.put("Java", 1);        map.put("HashMap", 2);        map.put("List",3);        map.put("set",4);       /**        * 3. 获取 HashMap 的某个数据        */        System.out.println("" + map.get("HashMap"));      /**        * 4. 遍历HashMap共有3种办法:别离针对Entry或key或value        * 步骤1:取得Entry或key或value的汇合        * 步骤2:遍历,应用for循环或迭代器Iterator        */        // 办法1:取得Entry的Set汇合再遍历        // 取得Entry的Set汇合        Set<Map.Entry<String, Integer>> entrySet = map.entrySet();        // 通过for循环遍历        for(Map.Entry<String, Integer> entry : entrySet){            System.out.print(entry.getKey());            System.out.println(entry.getValue());        }        // 通过迭代器遍历        // 先取得Entry的Iterator,再循环遍历        Iterator iter1 = entrySet.iterator();        while (iter1.hasNext()) {            // 遍历时,需先获取entry,再别离获取key、value            Map.Entry entry = (Map.Entry) iter1.next();            System.out.print((String) entry.getKey());            System.out.println((Integer) entry.getValue());        }        // 办法2:取得key的Set汇合再遍历        Set<String> keySet = map.keySet();        // 通过for循环        for(String key : keySet){            System.out.print(key);            System.out.println(map.get(key));        }        // 通过迭代器遍历        // 先取得key的Iterator,再循环遍历        Iterator iter2 = keySet.iterator();        String key = null;        while (iter2.hasNext()) {            // 遍历时,需先获取key,再获取value            key = (String)iter2.next();            System.out.print(key);            System.out.println(map.get(key));        }        // 办法3:取得value的汇合再遍历        Collection valueSet = map.values();        // 取得values的Iterator,再循环遍历        Iterator iter3 = valueSet.iterator();        while (iter3.hasNext()) {            System.out.println(iter3.next());        }    }}

对于遍历形式,举荐应用针对 key-value对(Entry)的形式:效率高

  1. 对于遍历keySet,valueSet,本质上遍历了2次:
    第1次,for/iterator迭代器遍历;
    第2次 从HashMap中取出key的value操作
  2. 对于遍历entrySet,本质遍历了1次for/iterator迭代器遍历,Entry曾经存储了key和 value

源码剖析

次要属性

//默认容量static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//最大容量static final int MAXIMUM_CAPACITY = 1 << 30;//加载因子static final float DEFAULT_LOAD_FACTOR = 0.75f;//扩容阈值 = 容量 x 加载因子,当哈希表的大小 ≥ 扩容阈值时,就会扩容哈希表int threshold//存储数据的Entry类型数组,长度=2的幂transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE//HashMap中存储的键值对的数量transient int size

构造方法

 //加载因子,容量可指定 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);        this.loadFactor = loadFactor;        threshold = initialCapacity;        init();    }     //加载因子等于默认值,容量可指定       public HashMap(int initialCapacity) {        this(initialCapacity, DEFAULT_LOAD_FACTOR);    }    //默认构造函数,加载因子,容量等于默认值    public HashMap() {        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);    }      //可传入一个map的构造函数    public HashMap(Map<? extends K, ? extends V> m) {        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);        inflateTable(threshold);        putAllForCreate(m);    }

put()办法

    public V put(K key, V value) {        //1.若entry数组为空,则初始化数组        if (table == EMPTY_TABLE) {            inflateTable(threshold);        }        //2.若key == null,则将该键-值 寄存到entry数组中的第1个地位,即table[0]        if (key == null)            return putForNullKey(value);        //3.若 key ≠ null,则计算寄存entry数组中的地位        //3.1依据键值key计算hash值        int hash = hash(key);        //3.2依据hash值 最终取得 key对应寄存的entry数组中地位        int i = indexFor(hash, table.length);                4. 通过遍历以该数组元素为头结点的链表,判断该key值是否已存在        for (Entry<K,V> e = table[i]; e != null; e = e.next) {            Object k;            4.1 若该key已存在,则用新value替换旧value            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {                V oldValue = e.value;                e.value = value;                e.recordAccess(this);                return oldValue;            }        }        modCount++;        //4.2 若该key不存在,则将key-value增加到entry数组中,这里采纳头插法        addEntry(hash, key, value, i);        return null;    }

addEntry resize() transfer()

    void addEntry(int hash, K key, V value, int bucketIndex) {        // 若不足够,则进行扩容(2倍),从新计算Hash值、从新计算key在扩容后的entry数组下标         if ((size >= threshold) && (null != table[bucketIndex])) {            resize(2 * table.length);            hash = (null != key) ? hash(key) : 0;            bucketIndex = indexFor(hash, table.length);        }        //若容量足够,则创立1个新的数组元素Entry并放入到entry数组中        createEntry(hash, key, value, bucketIndex);    }            void resize(int newCapacity) {        Entry[] oldTable = table;        int oldCapacity = oldTable.length;        if (oldCapacity == MAXIMUM_CAPACITY) {            threshold = Integer.MAX_VALUE;            return;        }                //依据新容量(2倍容量)新建1个entry数组,newtable         Entry[] newTable = new Entry[newCapacity];        //将旧entry数组上的entry数据转移到newtable中,从而实现扩容        transfer(newTable, initHashSeedAsNeeded(newCapacity));        //新数组table援用到HashMap的table属性上        table = newTable;        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);    }            //旧entry数组上的entry数据转移到newtable中,从而实现扩容,按旧链表的正序遍历链表,在新链表的头部顺次插入,        //即扩容前 = 1->2->3,扩容后 = 3->2->1,此时若(多线程)并发执行 `put()操作,一旦呈现扩容状况,则容易呈现环形链表,从而在获取数据、遍历链表时造成死循环Infinite Loop        void transfer(Entry[] newTable, boolean rehash) {        int newCapacity = newTable.length;        for (Entry<K,V> e : table) {            while(null != e) {                Entry<K,V> next = e.next;                if (rehash) {                    e.hash = null == e.key ? 0 : hash(e.key);                }                int i = indexFor(e.hash, newCapacity);                e.next = newTable[i];                newTable[i] = e;                e = next;            }        }    }

get()办法

    public V get(Object key) {        //当key == null时,则到以entry数组中的第1个元素(即table[0])为头结点的链表去寻找对应 key == null的键        if (key == null)            return getForNullKey();        //当key ≠ null时,去取得对应值            Entry<K,V> entry = getEntry(key);        return null == entry ? null : entry.getValue();    }          final Entry<K,V> getEntry(Object key) {        if (size == 0) {            return null;        }        // 1. 依据key值,通过hash()计算出对应的hash值        // 2. 依据hash值计算出对应的数组下标        // 3. 遍历以该数组下标的数组元素为头结点的链表所有节点,寻找该key对应的值        int hash = (key == null) ? 0 : hash(key);        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;    }

源码总结

1.JDK1.7的HashMap采纳数组+单链表的数据结构,数组和链表存储的是一个个Entry对象
2.增加key-value时会依据key值计算出对应的hash值,再依据hash值计算出对应的数组下标,遍历这个数组中的Entry链表,如果存在有雷同的key的Entry,用新的value值替换旧的value值,若不存在则用头插法退出到链表中。
3.在增加key-value到数组中时有扩容的判断,但hashmap中Entry的数量,或者说key-value的数量大于扩容阈值 = 以后容量 x 加载因子,新建一个数组,容量时是数组的2倍,将旧entry数组上的entry数据转移到newtable中,让以后的数组指向新数组,从而实现扩容。
4.线程不平安问题:
在扩容过程中,在将旧数组上的数据转移到新数组上时,按旧链表的正序遍历链表,在新链表的采纳头插法插入,即扩容前 = 1->2->3,扩容后 = 3->2->1,此时若(多线程)并发执行 `put()操作,一旦呈现扩容状况,则容易呈现环形链表,从而在get()办法获取数据、遍历链表时造成死循环Infinite Loop