HashMap二三事

来源:互联网 发布:js如何实现弹窗 编辑:程序博客网 时间:2024/05/18 02:31

先看看hashmap在整个Collection中的位置


HashMap中存储数据的结构是

    /**     * The table, resized as necessary. Length MUST Always be a power of two.     */    transient Entry<K,V>[] table;

上面的英文就不用说了。
原来基础的存储结构式Entry的数组!
至于Entry是HashMap的一个内部类
  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;        }    .....}

看到里面的这个参数Entry<K,V> next大家应该都明白了,HashMap中每个Entry键值对都是一个链表!!!
下面我们看看map的put,get,iterator方法及遍历

put方法

 
    public V put(K key, V value) {        if (key == null)            return putForNullKey(value);    //计算key的hash  里面的实现比较麻烦 可以不用理会        int hash = hash(key);    //由hash码得到存储位置 计算方法是hash与table.length-1相与 这样的好处就是能保证要存放的位置肯定不会超过table的范围    //前面的hash方法与indexFor 我没有仔细研究 不过大家可以认为 两个不同的hash会对应不同的存储位置        int i = indexFor(hash, table.length);    //e.next 链表    //如果i的位置上已经有元素了 继续看for循环    //否则就new一个新的Entry        for (Entry<K,V> e = table[i]; e != null; e = e.next) {            Object k;        //如果要存储的key的hash值与已经存在在那个位置元素的key的hash值相等 并且两个key的内容也相等        //话说这里我看的不是太懂  e本身是一个新的对象            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {                V oldValue = e.value;                e.value = value;        //以下两行代码到底想干什么?                e.recordAccess(this);   //把要加入的值 给了e 是什么意思?                return oldValue;        //返回的是之前已经存在的那个键值对里的value            }        }        modCount++;        addEntry(hash, key, value, i);        return null;    }   void addEntry(int hash, K key, V value, int bucketIndex) {    //如果相应的位置已经有东西了 并且总的容量也到了 就扩容    //size threshold 这里面有点概念性的知识 大家看看源码就知道了        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) {        //之前的那个位置的数据(不管有没有)转放到e里面    //现在有个问题 什么时候table[bucketIndex]里面才有数据呢?        Entry<K,V> e = table[bucketIndex];    //看构造函数就知道 把e作为新entry的next 拉链法!!!        table[bucketIndex] = new Entry<>(hash, key, value, e);        size++;    }    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;        }    .....    }


    什么时候table[bucketIndex]里面才有数据呢? 换句话说拉链法是怎么实现的呢?我们先看下面的遍历。

hashmap的遍历

package iterator;import java.util.Map;import java.util.Map.Entry;import java.util.Random;import java.util.Iterator;import java.util.HashMap;import java.util.Collection;/* * @desc 遍历HashMap的测试程序。 *   (01) 通过entrySet()去遍历key、value,参考实现函数: *        iteratorHashMapByEntryset() *   (02) 通过keySet()去遍历key、value,参考实现函数: *        iteratorHashMapByKeyset() *   (03) 通过values()去遍历value,参考实现函数: *        iteratorHashMapJustValues() * * @author skywang */public class HashMapIteratorTest {    public static void main(String[] args) {        int val = 0;        String key = null;        Integer value = null;        Random r = new Random();        HashMap<String, Integer> map = new HashMap<String, Integer>();        map.put("ss",12);  //第一次 会调用addEntry(hash, key, value, i);        map.put("ss",13);  //第二次 会进入put方法里的for循环 然后直接return        map.put("ss",12);  //第三次 会进入put方法里的for循环 然后直接return 那么什么时候才会使用拉链法呢                       //当若干个key的内容不同但是hashCode相同时        for (int i=0; i<12; i++) {            // 随机获取一个[0,100)之间的数字            val = r.nextInt(10);                        key = String.valueOf(val);            value = r.nextInt(5);            // 添加到HashMap中            map.put(key, value);                   }                     // 通过entrySet()遍历HashMap的key-value        iteratorHashMapByEntryset(map) ;                // 通过keySet()遍历HashMap的key-value        iteratorHashMapByKeyset(map) ;                // 单单遍历HashMap的value        iteratorHashMapJustValues(map);            }        /*     * 通过entry set遍历HashMap     * 效率高!     */    @SuppressWarnings("unchecked")    private static void iteratorHashMapByEntryset(HashMap<String, Integer> map) {        if (map == null)            return ;        System.out.println("\niterator HashMap By entryset");        String key = null;        Integer integ = null;        Iterator<?> iter = map.entrySet().iterator();        while(iter.hasNext()) {            Map.Entry<String, Integer> entry = (Entry<String, Integer>)iter.next();             key = (String)entry.getKey();            integ = (Integer)entry.getValue();            System.out.println(key+" -- "+integ.intValue());                   }    }    /*     * 通过keyset来遍历HashMap     * 效率低!     */    private static void iteratorHashMapByKeyset(HashMap<String, Integer> map) {        if (map == null)            return ;        System.out.println("\niterator HashMap By keyset");        String key = null;        Integer integ = null;        Iterator<String> iter = map.keySet().iterator();        while (iter.hasNext()) {            key = iter.next();            integ = map.get(key);            System.out.println(key+" -- "+integ.intValue());        }    }        /*     * 遍历HashMap的values     */    private static void iteratorHashMapJustValues(HashMap<String, Integer> map) {        if (map == null)            return ;                Collection<Integer> c = map.values();        Iterator<Integer> iter= c.iterator();        while (iter.hasNext()) {            System.out.println(iter.next());       }    }}

拉链法

上面已经说了使用entrySet的方式效率高,大家以后就采用这个吧,另外还提到了什么时候用拉链法看下面这个例子
        HashMap<Person, Integer> map = new HashMap<Person, Integer>();        map.put(new Person("dlf", 14),12);        map.put(new Person("dlf", 15),12);        map.put(new Person("sdfe", 16),12);


对于person这个类,我重写了hashCode方法,但是没有重写equals方法;
person的hashCode方法:
    public int hashCode() {        int h = 0;        if (name.equals("dlf")) {            return 123456789;        }        if (h == 0 && value.length > 0) {            char val[] = value;            for (int i = 0; i < value.length; i++) {                h = 31 * h + val[i];            }            hash = h;        }        return h;    }

大家再看看hashMap里面的put方法
     
  for (Entry<K,V> e = table[i]; e != null; e = e.next) {            Object k;        //如果要存储的key的hash值与已经存在在那个位置元素的key的hash值相等 并且两个key的内容也相等        //话说这里我看的不是太懂  e本身是一个新的对象            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {                V oldValue = e.value;                e.value = value;        //以下两行代码到底想干什么?                e.recordAccess(this);   //把要加入的值 给了e 是什么意思?                return oldValue;        //返回的是之前已经存在的那个键值对里的value            }        }
当我
        map.put(new Person("dlf", 14),12);        map.put(new Person("dlf", 15),12);

第二个person的hashcode与第一个person的hashcode是一样的,但是看看上面if条件句的第二部分
(k = e.key) == key || key.equals(k)
此时调用Object的equals方法也就是==,那么==比较的是什么呢?栈里面存储的地址值,两个新new处理的对象地址那自然不同的,所以if条件不满足,跳出for循环;
最后的结果是

iterator HashMap By entryset
dlf 15 -- 12
dlf 14 -- 12
sdfe 16 -- 12

iterator HashMap By keyset
dlf 15 -- 12
dlf 14 -- 12
sdfe 16 -- 12
12
12

12

get方法

public V get(Object key) {    if (key == null)        return getForNullKey();    // 获取key的hash值    int hash = hash(key.hashCode());    // 在“该hash值对应的链表”上查找“键值等于key”的元素             大家看到了 是在hash对应的位置查找,而不是查找整个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;}

iterator方法

我们就看效率最高的entrySet方法

        Set<Entry<Person, Integer>> set=map.entrySet();     //为了更清楚些 我分开写        Iterator<?> iter = set.iterator();

最开始调用entrySet方法的时候,entrySet对象还为null,会调用new EntrySet;
待得到set集合后,会再次调用iterator方法

  public Set<Map.Entry<K,V>> entrySet() {        return entrySet0();    }    private Set<Map.Entry<K,V>> entrySet0() {        Set<Map.Entry<K,V>> es = entrySet;        return es != null ? es : (entrySet = new EntrySet());    }    private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {        public Iterator<Map.Entry<K,V>> iterator() {            return newEntryIterator();        }        .......    }    Iterator<Map.Entry<K,V>> newEntryIterator()   {        return new EntryIterator();    }

大家看到了最后返回的Iterator是一个EntryIterator。
看EntryIterator的代码,它是继承了HashIterator;
 
   private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {        public Map.Entry<K,V> next() {            return nextEntry();             //这个方法在HashIterator中定义        }    }
随后我们在程序里调用
 
  Map.Entry<Person, Integer> entry = (Entry<Person, Integer>)iter.next();

   
   
 //以下为HashIterator类     //构造函数       //在我们new EntryIterator的时候 就已经调用这个其父类HashIterator的构造函数了     //HashIterator的成员变量在构造函数里面 就已经指到table里第一个元素了       HashIterator() {            expectedModCount = modCount;            if (size > 0) { // advance to first entry                Entry[] t = table;                while (index < t.length && (next = t[index++]) == null)                    ;            }        }     final Entry<K,V> nextEntry() {            if (modCount != expectedModCount)                throw new ConcurrentModificationException();            Entry<K,V> e = next;                     //这个next是构造函数里面就指到table里第一个元素了(第一个不为null的元素)            if (e == null)                throw new NoSuchElementException();            // 先让next=e.next 然后才判断next是否为空            if ((next = e.next) == null) {                     //从第一行调用来看        //如果table的第一个Entry(其实就是一个单链表) 就只有一个元素(其next为空)        //让next找到table的下一个元素                Entry[] t = table;                while (index < t.length && (next = t[index++]) == null)                    ;            }            current = e;            return e;        }


有了上面的if ((next = e.next) == null) 这一行,我们就不仅能遍历整个table,还能将table中某个entry中的所有元素也遍历了!不重复,不遗漏。



参考资料

http://www.cnblogs.com/skywang12345/p/3310835.html

我多说两句,上面的博客把java的整个Collection的源代码剖析了一遍,博客主人真乃牛人呀! 大家一定要去看看


0 0
原创粉丝点击