zoukankan      html  css  js  c++  java
  • Java集合包(六)——Map实现类之HashMap、HashTable 原理分析

    转载请注明原文地址:https://www.cnblogs.com/ygj0930/p/13565926.html 

    一:HashMap特征

      1、HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。

      2、HashMap是无序的。

      3、HashMap是非线程安全的,只适合在单线程环境下使用。

      4、HashMap的key和value允许为空。

      

    二:HashMap继承与实现关系

      1、继承层次

    java.lang.Object
       ↳     java.util.AbstractMap<K, V>
             ↳     java.util.HashMap<K, V>

      2、类定义与实现

    public class HashMap<K,V>
        extends AbstractMap<K,V>
        implements Map<K,V>, Cloneable, Serializable { }

      继承AbstractMap,实现了Map、Cloneable、java.io.Serializable接口,具备了通用的键值对操作能力、克隆能力、序列化能力。

    三:HashMap原理

      HashMap的重要成员与概念解析

      1、table数组:一个Entry[]数组,每个数组位置称之为一个“桶”,每个桶位都对应一个hash值,每个桶位存储一个Entry实例。而Entry实例实际上是一个单向链表,每个链表节点存储一个键值对。

             在jdk1.8之前,Entry数组中存储的是链表的表头,一个链表存储hash值相同的键值对们。键值对的hash值相同称之为“hash冲突”,解决hash冲突有多种方法,HashMap采用的是拉链法。

            1)拉链法

               这种方法的思路是:把同一个散列槽(在我们这里就是数组的每一个桶位)中的所有元素放到一个链表中,也就是说:把hash值相同的键值对,保存到同一个链表中。

              往HashMap中put一个元素时:首先通过散列函数计算键值对的hash值,得到其在table数组中的桶位。如果该桶位中没有元素,则生成一个Entry实例保存键值对后插入该桶位;

                           如果有元素,则检查key值的唯一性:如果key值和我们要插入的数据不一样,则生成一个Entry实例保存键值对后,插入到该桶位的链表中;如果存在和我们要插入数据相同key的键值对,我们就把value进行更换即可。

            2)再哈希法

              这种方法的思路是:当出现hash值相同的键值对时,进行再次hash计算,直到出现不冲突的位置为止。这样会导致时间成本增加,不采用。

            3)开放地址法

              这种方法的思路是:插入一个元素的时候,先通过哈希函数进行判断,若是发生哈希冲突,就以当前地址为基准,根据再寻址的方法(探查序列),去寻找下一个地址,若发生冲突再去寻找,直至找到一个为空的地址为止。

              所以这种方法又称为再散列法。

           jdk1.8引入了新的实现方式来解决hash冲突,当某个entry链表长度大于8时,会转化成红黑树进行存储;当某个entry树的小于6个元素时,又会转回链表存储。

            这样做的原因是:

            1)原因:
            红黑树的平均查找长度是log(n),当长度为8,查找长度为log(8)=3;

            链表的平均查找长度为n/2,当长度为8时,平均查找长度为8/2=4,此时开始有转换成树的必要;

            链表长度如果是小于等于6,6/2=3,因此小于等于6时链表形式的速度已足够快,虽然红黑树的速度会更快,但是转化为树结构也需要时间开销,因此最终性能表现差距不大,无谓再转成红黑树。

            2)选择6和8的原因是:
            中间有个差值7可以防止链表和树之间频繁的转换。

            3)引入红黑树而不是AVL平衡二叉树的原因

            红黑树相比avl树,在检索的时候效率其实差不多,都是通过平衡来二分查找。

            但对于插入删除等操作,红黑树不像avl树一样追求绝对的平衡,他允许局部很少的不完全平衡,省去了很多没有必要的平衡调整操作,因此性能更优。

      2、初始容量:容量 是指哈希表中桶的数量,一个桶存储一个键值对。最大容量必须是2的幂且小于2的30次方,传入容量过大将被这个值替换。

           初始容量 指哈希表在创建时的一开始的容量,默认的初始容量是16,必须是2的幂。

      3、加载因子loadFactor:指触发哈希表扩容的一个当前大小的临界值。

           当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作进行扩容,将容量扩大一倍。

              默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本。

      4、阈值threshold:用于判断是否需要调整HashMap的容量,threshold="容量*加载因子",当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。

      5、size:存储当前键值对的数量,即HashMap的大小。

      6、modCount:实现fail-fast机制的判断依据。

    四:HashMap关键方法

      1、遍历键值对

    Integer integ = null;
    Iterator iter = map.entrySet().iterator();
    while(iter.hasNext()) {
        Map.Entry entry = (Map.Entry)iter.next();
        // 获取key
        key = (String)entry.getKey();
            // 获取value
        integ = (Integer)entry.getValue();
    }

      第一步:根据entrySet()获取HashMap的“键值对”的Set集合,并获取其迭代器。
      第二步:通过Iterator迭代器遍历键值对的Set集合,并通过entry提供的getKey和getValue方法提取键、值。

      2、遍历键

    Iterator iter = map.keySet().iterator();
    while (iter.hasNext()) {
            // 获取key
        key = (String)iter.next();
    }

      第一步:根据keySet()获取HashMap的“键”的Set集合。
      第二步:通过Iterator迭代器遍历“第一步”得到的集合。

      3、遍历值

    Collection c = map.values();
    Iterator iter= c.iterator();
    while (iter.hasNext()) {
        value = (Integer)iter.next();
    }

      第一步:根据value()获取HashMap的“值”的Collection集合。【此处是Collection而不是Set,因为值是允许重复的】
      第二步:通过Iterator迭代器遍历“第一步”得到的集合。

    五:HashTable【已过时】

      1)HashTable也是存储键值对和操作键值对的,其底层数据结构原理与HashMap几乎一样,也有容量、加载因子、自动扩容机制等,也是通过拉链法解决哈希冲突,用链表保存键值对。

      2)HashTable的key和value不允许为空,这是与HashMap的不同之处。

      3)HashTable提供的键值对操作方法大多与HashMap一致,不同的地方在于它的方法都被 synchronized 修饰,从而实现线程安全。

      4)HashTable继承了Dictionary类,因此其迭代方式比HashMap多出一种选择:除了使用Iterator迭代器,还可以使用Enumeration枚举类迭代。

      Iterator和Enumeration的不同之处:

      Enumeration:

    package java.util;
    
    public interface Enumeration<E> {
    
        boolean hasMoreElements();
    
        E nextElement();
    }

      Iterator:

    package java.util;
    
    public interface Iterator<E> {
        boolean hasNext();
    
        E next();
    
        void remove();
    }

      可见:

      1)函数接口不同

            Enumeration只有2个函数接口。通过Enumeration,我们只能读取集合的数据,而不能对数据进行修改。
            Iterator只有3个函数接口。Iterator除了能读取集合的数据之外,也能数据进行删除操作。

      2)Iterator支持fail-fast机制,而Enumeration不支持。

      3)Enumeration 比 Iterator 的遍历速度更快

      因为,Iterator添加了对fail-fast机制的支持,所以遍历要慢一点点。

      

  • 相关阅读:
    Django对静态文件的处理——部署阶段
    使用Django来处理对于静态文件的请求
    Django1.7如何配置静态资源访问
    Spring WebSocket中403错误解决
    FastJSON JSONObject 字段排序 Feature.OrderedField
    国际化(i18n) 各国语言缩写
    【转】java.io.Closeable接口
    【转】spring bean 卸载
    This content should also be served over HTTPS
    Failed to close the ServletOutputStream connection cleanly, Broken pipe
  • 原文地址:https://www.cnblogs.com/ygj0930/p/13565926.html
Copyright © 2011-2022 走看看