zoukankan      html  css  js  c++  java
  • HashMap是怎样存储和快速查找的

    参考:廖雪峰老师的java教程
    我们都知道Map是一种键值对映射表,可以通过key快速查找对应的value.

    以HashMap为例,观察下面的代码:

            Map<String ,Integer> map = new HashMap<>();
            map.put("apple",12);
            map.put("pear",10);
            map.put("origin",5);
            map.get("apple");  //12
    

    HashMap之所以能根据key直接拿到value,,原因是它内部通过空间换时间的方法,用一个大数组存储所有的value,并根据key直接计算出value应该存储在那个索引:

    ┌───┐
    0 │ │
    ├───┤
    1 │ ●─┼───> Person("Xiao Ming")
    ├───┤
    2 │ │
    ├───┤
    3 │ │
    ├───┤
    4 │ │
    ├───┤
    5 │ ●─┼───> Person("Xiao Hong")
    ├───┤
    6 │ ●─┼───> Person("Xiao Jun")
    ├───┤
    7 │ │
    └───┘
    如果key的值为"a",计算得到的索引总为 1 ,因此返回value为Person("Xiao Ming"),如果key的值为 "b",计算得到的索引总为5,因此返回value为Person("Xiao Hong"),这样就不必遍历整个数组,即可以直接读取key对应的value.

    当我们使用 key 存取value的时候,就会引起一个问题:

    我们放入map的可以是字符串 "a",但是,当我们获取Map的value时,不一定就是放入的那个key对象.

    换句话讲,两个key应该是内容相同,但不一定是同一个对象.

    @Test
        public void testHashMap(){
            String key1 = "a";
            Map<String,Integer> map = new HashMap<>();
            map.put(key1,123);
            String key2 = new String("a");
            int i = map.get(key2);
            System.out.println(i);  //123
            System.out.println(key1 == key2); // false  说明key1和key2是两个对象
            System.out.println(key1.equals(key2));  //true    说明key1的内容和key2相同
        }
    

    因为在Map内部,对key做比较是通过equals()实现的,这一点和List查找元素需要正确覆写equals()是一样的,即正确的Map必须保证:作为key的对象必须正确覆写equals方法.

    我们经常使用String 作为 key,因为String 已经正确覆写equals方法.但如果我们放入的key 是一个自己写的类,就必须保证正确覆写equals方法.

    我们再思考一下 HashMap 为什么能通过 key 直接计算出 value 存储的索引.相同的key 对象(使用equals判断时返回true)必须要计算出相同的索引,否则,相同的key每次取出value就不一定对.

    通过key计算索引的方式就是调用key对象的hashCode()方法,它返回一个int整数.HashMap正是通过这个方法直接定位key对应的value的索引,继而直接返回value.

    因此,正确使用Map必须保证:

    • 作为key的对象必须正确覆写equals()方法,相等的两个key实例调用equals()必须返回True;
    • 作为key的对象还必须正确覆写hashCode()方法,且hashCode()方法要严格遵循一下规范:
      • 如果两个对象相等,则两个对象的hashCode()必须相等;
      • 如果两个对象不相等,则两个对象的hashCode()尽量不要相等.

    扩展

    既然HashMap内部使用了数组,通过计算key的HashCode()直接定位value所在的索引,那么第一个问题就来了:hashCode()返回的int返回高达±21亿,先不考虑负数,HashMap内部使用的数组得有多大?

    实际上 HashMap初始化默认的数组大小只有 16,任何key,无论它的hashCode()有多大,都可以简单地通过:

    int index = key.hashCode() & 0xf;  //0xf = 15
    

    把索引确定为0~15之间,即永远不会超出数组范围,上述算法只是一种最简单的实现.

    第二个问题:如果添加超过16个key-value到HashMap,数组不够用怎么办?

    添加超过一定数量的key-value时,HashMap会在内部自动扩容,每次扩容一倍,即长度为16的数组扩展为长度为32,相应的,需要重新计算hashCode() 索引位置.例如:对长度为32的数组计算hashCode()对应的索引,计算方式要改为:

    int index  = key.hashCode() & 0x1f; // 0x1f = 31
    

    由于扩容会导致重新分布已有的key-value,所以,频繁扩容对HashMap的性能影响很大.如果我们确定要使用一个10000个key-value的HashMap,更好的方式是创建HashMap时就指定容量:

    Map<String,Integer> map = new HashMap<>(10000);
    

    虽然指定容量是10000,但是HashMap内部的数组长度总是 (2^n),因此,实际数组长度被初始化为比10000大的16384((2^14))

    最后一个为题:如果两个不相同的key,例如"a" 和"b",他们的hashCode()恰好是相同的(这种情况是完全有可能的,因为不相等的两个实例,只要求hashCode()尽量不相等),那么,当我们放入:

    map.put("a", new Person("Xiao Ming"));
    map.put("b", new Person("Xiao Hong"));
    

    由于计算出的数组索引相同,后面放入"Xiao Hong"会不会把"Xiao Ming"覆盖了?

    当然不会!使用Map的时候,只要key不相同,他们映射的value就不会互不干扰.但是,在hashMap内部,确实可能存在不同的key,映射到相同的hashCode(),即相同的数组索引上怎么办?

    我们就假设"a" 和"b" 这两个key 最终计算出的索引都是5,那么,在HashMap的数组中,实际存储的不是一个Person实例,而是一个List,它包含 两个Entry,一个是"a"的映射,一个是"b"的映射:

    ┌───┐
    0 │ │
    ├───┤
    1 │ │
    ├───┤
    2 │ │
    ├───┤
    3 │ │
    ├───┤
    4 │ │
    ├───┤
    5 │ ●─┼───> List<Entry<String, Person>>
    ├───┤
    6 │ │
    ├───┤
    7 │ │
    └───┘
    在查找的时候,例如:

    Person p = map.get("a");
    

    HashMap内部通过"a"找到的实际上是List<Entry<String,Person>>,它还需要遍历这个list,并找到一个Entry,它的key字段是"a",才能返回对应的Person实例.

    我们把不同的key具有相同的hashCode()的情况称之为哈希冲突.在冲突的时候,一种最简单的解决办法是用List存储hashCode()相同的key-value.显然冲突的概率越大,这个list就越长,map的get()方法效率就越低,这就是为什么要尽量满足条件二:

    如果两个对象不相等,则两个对象的hashCode()尽量不要相等
    

    hashCode()方法编写得号,HashMap的工作效率就越高.

  • 相关阅读:
    MS CRM 2011 RC中的新特性(4)——活动方面之批量编辑、自定义活动
    最近的一些有关MS CRM 2011的更新
    MS CRM 2011 RC中的新特性(6)——连接
    MS CRM 2011 RC中的新特性(7)—仪表板
    参加MS CRM2011深度培训课程——第一天
    MS CRM 2011插件调试工具
    MS CRM2011实体介绍(四)——目标管理方面的实体
    MS CRM 2011 RC中的新特性(3)——客户服务管理方面
    MS CRM 2011 RC中的新特性(8)—数据管理
    ExtAspNet 登陆
  • 原文地址:https://www.cnblogs.com/liuzhidao/p/13746648.html
Copyright © 2011-2022 走看看