先看看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的源代码剖析了一遍,博客主人真乃牛人呀! 大家一定要去看看

HashMap二三事的更多相关文章

  1. Java并发编程二三事

    Java并发编程二三事 转自我的Github 近日重新翻了一下<Java Concurrency in Practice>故以此文记之. 我觉得Java的并发可以从下面三个点去理解: * ...

  2. linux杂记(十二?) 关于账号和密码的二三事

    关于密码的二三事 关于账号和密码的二三事 久了不更linux的相关知识,实在是懒得想内容点(纯粹是懒).那么今天就来谈谈关于linux密码和账号的重要概念. 假如你的主机遭到入侵,那么对方的第一个侵入 ...

  3. MySQL5.7关于密码二三事

    MySQL5.7关于密码二三事 第一个:update user set password=password('root') where user='root' and host='localhost' ...

  4. Java中的匿名内部类及内部类的二三事

    匿名内部类适合创建那些只需要使用一次的类,它的语法有些奇怪,创建匿名内部类会立即创建一个该类的实例,这个类定义立即消失,且不能重复使用. 定义匿名类的格式如下: new 实现接口() |父类构造器(实 ...

  5. Emacs 启动优化二三事

    Emacs 启动优化二三事 */--> div.org-src-container { font-size: 85%; font-family: monospace; } p {font-siz ...

  6. WinForm二三事(三)Control.Invoke&Control.BeginInvoke

    http://www.cnblogs.com/yuyijq/archive/2010/01/11/1643802.html 这个系列从2009年写到2010年,差点又成太监文.随着WPF/Silver ...

  7. iOS7下滑动返回与ScrollView共存二三事

    [转载请注明出处] = =不是整篇复制就算注明出处了亲... iOS7下滑动返回与ScrollView共存二三事 [前情回顾] 去年的时候,写了这篇帖子iOS7滑动返回.文中提到,对于多页面结构的应用 ...

  8. 一只代码小白git托管路上的二三事

    [经验]一只代码小白git托管路上的二三事 写在前面的话 寒假的时候,娄老师给我们布置了代码托管的作业,并要求把托管地址发给学委.因假期的时候没有带电脑回家,所以只是在手机上草草注册了,也稀里糊涂就将 ...

  9. java:Map借口及其子类HashMap二

    java:Map借口及其子类HashMap二 重点:所有的集合必须依赖Iterator输出 Map<String, Integer> map = new HashMap<String ...

随机推荐

  1. Eclipse中配置javap命令

    Run→External Tools→External Tools Configurations-进入如下图二所示的Program配置界面.也可以通过如下图一所示的工具栏按钮进入Program配置界面 ...

  2. CentOS6.7下安装MySQL

    第一步:到MySQL官网上下载linux版本的MySQL rpm 安装包. 第二步: 将该压塑包解压后,有如下文件: 第三步:安装文件,我们需要安装的文件有 MySQL-server-5.6.26-1 ...

  3. 协议系列之UDP协议

    上节说的TCP协议虽然提供了可靠的传输,但是也有一个缺点,发送速度慢.那么有没有一种协议能快速发送的呢?这节要讨论的就是UDP协议,它提供了更加快的发送速度,但也牺牲了可靠性,它是一种无连接的传输协议 ...

  4. [ExtJS5学习笔记]第六节 Extjs的类系统Class System命名规则及定义和调试

    本文地址: http://blog.csdn.net/sushengmiyan/article/details/38479079 本文作者:sushengmiyan ----------------- ...

  5. 因 URL 意外地以“/HelloWorld”结束,请求格式无法识别

    http://www.cnblogs.com/AngelLee2009/p/3540527.html

  6. FORM级别和数据库级别的Trace

     metalink上的文章较全的,中文的可参考我的博客EBS开发技术之trace http://blog.csdn.net/cai_xingyun/article/details/17250971 ...

  7. 应付模块的R12 TRACE 和 FND Debug 文件 / FND 日志 调试

     取得R12 TRACE: 1. 导航职责: 系统管理员> 配置文件> 系统> 查找 用户: 用户提交报表 配置: 初始化 SQL 语句 - 自定义 2. 点击用户栏位-编辑区域 ...

  8. RecyclerView下拉刷新上拉加载(三)—对Adapter的封装

    RecyclerView下拉刷新上拉加载(一) http://blog.csdn.net/baiyuliang2013/article/details/51506036 RecyclerView下拉刷 ...

  9. 09 ListView监听 ExpandableListView的使用总结

    1.ListView的滚动监听 >setOnScrollListener 监听 //ListVIew滚动监听 lv.setOnScrollListener(new OnScrollListene ...

  10. (一〇二)静态库(.a)的打包

    库是代码的集合,根据代码公开程度,分为开源库和闭源库. 其中闭源库主要包括静态库和动态库,是经过编译的二进制文件,看不到具体实现. 静态库的拓展名是.a或者.framework,动态库则是.dylib ...