zoukankan      html  css  js  c++  java
  • HashMap早知道

    第一眼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("
    iterator 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(" iterator 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再次源代码分析,博客的主人真乃牛死! 我们必须去看看


  • 相关阅读:
    服务注册中心之Eureka使用
    微服务之服务注册中心
    Idea热部署功能
    微服务Cloud整体聚合工程创建过程
    微服务架构理论&SpringCloud
    关于母函数
    HDU 1028(母函数)整数划分
    1021 FIBERNACI
    1019
    1014 巧妙的gcd 生成元
  • 原文地址:https://www.cnblogs.com/hrhguanli/p/5039549.html
Copyright © 2011-2022 走看看