HashMap(JDK1.7以前:数组+链表;JDK1.8:加了红黑树)

  • HashMap时存储key-value的集合。每一个键值对都是一个Entry数组。这些Entry数组默认为空。

  • HashMap由数组+链表组成,数组时HashMap的主体,而链表是解决hash冲突而存在的。

  • 当链表的长度过长时,就会降低查询效率。所以当链表长度大于8时就会转换成红黑树

    链表长度大于8时转换成红黑树遵循松柏分布。用松柏分布的公式计算出链表中的元素和概率的对照表,得出当链表长度为8时的值已经非常小了。

    因为红黑树的平均查找长度为log(n),当长度为8时红黑树的查找长度为3;而链表的查找长度为4,所以才有了转换成红黑树的必要。

    但是如果链表长度小于等于6时,虽然红黑树的效率也很快,但是要考虑转换成红黑树所消耗的时间

  • HashMap的扩容机制

    HashMap的初始大小为16,达到扩容阈值后会进行二倍扩容。当数组长度达到负载因子(0.75)的时候就会进行二倍扩容。扩容的时候要重新计算原数组的位置并放进新的数组

    • 设置负载因子为0.75的原因:

      如果设置的太大了,虽然保证了空间的充足,减少了哈希碰撞,查询效率也很高,但是会消耗大量的资源,越到后期消耗的资源越多

      如果设置了太小了,如果等到数组要被填满了在扩容,虽然空间的利用率增大了,但是会产生大量的hash碰撞,也会产生大量的链表。

      所以折中取0.75

    7.23笔记(HashMap和HashTable)_红黑树

  1. put方法的原理:

    • 首先把key和value封装到Node对象中

    • 然后底层会调用hashCode()方法

    • 第三步会调用哈希函数,把哈希值转换成数组下标,如果这个下标没有任何元素就直接添加,要是这个下标有链表。就会用这个key和链表上的每一个节点进行equal。如果equal的结果时false,就把这个新的节点添加到链表的末尾;如果equal的结果时true,这个节点的value就会被覆盖(通常hasMap的链表出现次数越少,性能越好)

    哈希函数:

     static final int hash(Object key) {
           int h;
           return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
      }

    意思就是,获取key的hash值,然后和其他的低16位进行异或运算,从而得到散列值。当关键字hash得到的地址已经存储过数据,这是就会出现哈希冲突,而hashMap采用了链地址法(数组+链表)

  2. get方法的原理

    • 先调用hashCode方法算出key的哈希值,并通过哈希算法得到数组的下标

    • 转换成下标后,就可以快速定位到数组的位置

      • 如果这个位置上上为空,就返回null

      • 如果这个位置上有单向链表,就会拿着key在单向链表上进行equals。如果equals的结果为false,就get到null;如果equals的结果为true,就把这个value返回

HashTable

HashTable和HashMap的区别:

  • HashMap是非同步的,没有并发读写的保护措施,所以是线程不安全的,在多线程并发条件下会倒置数据不一致的情况;HashTable是线程同步的,所有的读写操作都进行了锁的保护,是线程安全的,但是非常影响读写的效率

  • HashMap中是允许保存null的;但是HashTable不允许空值的存在

    HashTable原理

    和HashMap的非常相似,不同点在于所有操作都是通过synchronized保护的,只有获得了对应的锁,才能进行读写的操作

 

  1. HashTable的put方法:

    • 先获取synchronized锁,put的值是不允许为null的。如果为null直接抛出异常

    • 计算key的哈希值,并通过哈希值算出数组的下标

    • 遍历对应下标的链表,并遍历链表。如果存在和key的节点就更新value;否则就把节点插入到链表的头部

  2. HashTable的get方法:

    • 先获取synchronized锁

    • 计算key的哈希值,然后使用哈希函数得到数组的下标

    • 在对应下标的链表下面寻找具有相同hash值的key节点,返回该节点的value;没找到就返回null