zoukankan      html  css  js  c++  java
  • HashMap详解

    【基本介绍】

    链接相关知识:Map, List, Set基本介绍。

       

    HashMap是一个散装桶(数组或链表),它存储的内容是键值对(key-value)映射。

    HashMap采用了数组和链表的数据结构,继承了数组的线性查找和链表的寻址修改。

       

    HashMap是非synchronized的,所以很快。

       

    HashMap可以接受null键和值,而HashTable不能。

       

    ConcurrentHashMapHashTable因为在多线程并发的情况下,put操作时无法分辨key是没找到为null,还是key值对应的valuenull,所以基于这类情况不允许键值为null

    HashMap是线程不安全的,也就无所谓了。

        

    【工作原理】

    HashMap是基于hashing的原理,咱们使用putkeyvalue)把对象存储到HashMap中,使用getkey)从HashMap中获取对象。

       

    当我们使用put方法传递键值对的时候,先对键调用hashCode方法,计算并返回的hashCode……

    HashMapbucket中存储键对象和值对象,作为Map.node

       

    以下是HashMap的初始化(简单模拟):

    Node table = new Node[16] //散列桶初始化,table

    class Node {

    hash; //hash

    key; //

    value; //

    Node next; //用于指向链表的下一层(产生冲突,用拉链法)

    }

       

    以下是具体的put过程(jdk1.8

    1. KeyHash值,然后再计算下标

       

    2. 如果没有碰撞,直接放入桶中(碰撞的意思是计算得到的Hash值相同,需要放到同一个bucket中)

       

    3. 如果碰撞了,则调用equals() 比较value,相同则替换旧值,不同则以链表的方式链接到后面

       

    4. 如果链表长度超过阀值( TREEIFY THRESHOLD==8),就把链表转成红黑树,链表长度低于6,就把红黑树转回链表

       

    5. 如果桶满了(容量16*加载因子0.75),就需要 resize(扩容2倍后重排)

       

    当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。

       

    【如何减少碰撞】

    扰动函数可以减少碰撞,原理是如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这就意味着存链表结构减小,这样取值的话就不会频繁调用equal方法,这样就能提高HashMap的性能。(扰动即Hash方法内部的算法实现,目的是让不同对象返回不同hashcode

       

    使用不可变的声明作final的对象,并且采用合适的equals()hashCode()方法的话,将会减少碰撞的发生。

       

    为什么String, Interger这样的wrapper类适合作为键?因为Stringfinal的,而且已经重写了equals()hashCode()方法了。

       

    不可变性是必须的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。

       

    hash函数的实现】

    hashmap中要找到某个元素,需要根据keyhash值来求得对应数组中的位置。如何计算这个位置就是hash算法。

       

    hashmap的数据结构是数组和链表的结合,所以我们希望这个hashmap里面的元素位置分布均匀些,尽量使得每个位置上的元素数量只有一个,那么当我们用hash算法求得这个位置的时候,马上就可以知道对应位置的元素就是我们要的,而不用再去遍历链表。

       

    所以我们首先想到的就是把hashcode对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。

       

    但是,"模"运算的消耗还是比较大的,咱们可以看一下jdk1.8怎么做的:

    static final int hash(Object key) {

    if (key == null){

    return 0;

    }

    int h;

    h=key.hashCode(); //返回散列值也就是hashcode

    // ^ :按位异或

    // >>>:无符号右移,忽略符号位,空位都以0补齐

    //其中n是数组的长度,即Map的数组部分初始化长度

    return (n-1)&(h ^ (h >>> 16));

    }

       

    简单来说

    1. 16bt不变,低16bit和高16bit做了一个异或(得到的hashcode转化为32位的二进制,前16位高16bit和后16位低16bit做了一个异或)

    2. (n-1)&hash=->得到下标

       

    【链表过深的问题讨论】

    拉链法会导致链表过深,所以选择红黑树作为替代。

    没有选择二叉查找树的原因是因为在部分情况下,二叉查找树会变成一条线性结构,不能从根本解决问题。

       

    红黑树属于平衡二叉树,但是为了保持平衡需要付出一定的代价,红黑树在插入新数据后,可能需要左旋,右旋,变色等操作保持平衡,但是总体来讲该代价较遍历线性数据要小。

    所以当长度超过8的时候,引入红黑树。

       

    【关于红黑树】

    ·每个节点非红即黑

    ·根节点总是黑色

    ·如果节点为红色,则其子节点是黑色

    ·每个叶子节点都是黑色(NIL节点)

    ·黑色高度一致(从根节点开始的每条路径包含的黑色节点相同)

       

    【解决Hash碰撞的其他方法】

    开放地址法

    当冲突发生时,使用某种探查方法在散列表中形成一个探查序列。沿此序列一直向下查找,直到查到有效的地址。

       

    【超过负载因子】

    默认的负载因子是0.75.

    也就是说,当一个map填满了75% bucket的时候,和别的集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,用以重新调整map的大小,并将原来的对象放入新的bucket数组中。

       

    这个过程叫做rehashing,最后的值只会有两种可能,原先的位置或者原先的位置+size

       

    rehashing

    重新调整HashMap大小的时候,有可能存在条件竞争,因为如果两个线程同时同时发现HashMap需要rehashing的时候,会同时尝试resize

    如果条件竞争发生了,那么就死循环了。

       

    rehashing的过程中,存储在链表中的元素的次序会反过来,这是由于为了避免尾部遍历HashMap不会将元素每次都放在尾部,而是放在头部。

       

    HashTable

    数组 + 链表方式存储

    默认容量:11(一般为质数)

    put

    索引计算 : key.hashCode() & 0x7FFFFFFF% table.length

    如果在链表中找到了,则替换旧值,未找到则继续

    当总元素个数超过容量*加载因子的时候,扩容为原来的2倍,重新散列

    新元素插入到链表头部

    修改HashTable内部共享数据的方法添加了 synchronized保证了线程安全。

       

    【区别】

    默认容量不同。

    线程安全性,HashTable是线程安全的。

    HashTable比较慢。

       

    CocurrentHashMap

    ConcurrentHashMap同步性能比HashTable更好,它仅仅根据同步级别,map的一部分进行上锁。

    ConcurrentHashMap可以用来替代HashTable,但是后者具有更好的线程安全性。

    HashTable的大小增加到一定程度的时候,性能会急剧下降,因为迭代时会锁定很长时间。

       

    但是ConcurrentHashMap引入了分割segmentation),无论它变得多大,都只需要锁定map的其中一部分,这时候其他线程可以访问其未锁定部分。

  • 相关阅读:
    Elasticsearch ES索引
    Eureka 服务注册与发现
    Cron 定时任务表达式
    如何在OS X上设置或更改默认的Java(JDK)版本?
    adb常用命令
    【charlse】charlse功能
    面试题
    【robotframework】robotframework基本使用
    【postman】postman使用教程
    【fiddler】fiddler基础
  • 原文地址:https://www.cnblogs.com/liuxia912/p/12968553.html
Copyright © 2011-2022 走看看