zoukankan      html  css  js  c++  java
  • HashTable & HashMap & ConcurrentHashMap 原理与区别

    一.三者的区别

     
      HashTable HashMap ConcurrentHashMap
    底层数据结构 数组+链表 数组+链表 数组+链表
    key可为空
    value可为空
    线程安全
    默认初始容量 11 16 16
    扩容方式 (oldSize << 1)+1 oldSize << 1 桶的扩容
    扩容时间 size超过(容量*负载因子) size超过(容量*负载因子) 桶超数超过(容量*负载因子)
    hash key.hashCode() (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16) (key.hashCode() ^ (key.hashCode() >>> 16)) & 0x7fffffff
    index计算方式 (hash & 0x7FFFFFFF) % tab.length (tab.length - 1) & hash (tab.length - 1) & hash
    默认负载因子 0.75f 0.75f 0.75f

    二.源码剖析

    1.HashTable

    构造函数解读:
    public Hashtable() { this(11, 0.75f);}//默认容量和负载因子
    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);//给定初始容量
    }
    
    public Hashtable(int initialCapacity, float loadFactor) {
    //初始容量校验
            if (initialCapacity < 0)
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
    //负载因子校验
            if (loadFactor <= 0 || Float.isNaN(loadFactor))
                throw new IllegalArgumentException("Illegal Load: "+loadFactor);
    
            if (initialCapacity==0)
                initialCapacity = 1;
            this.loadFactor = loadFactor;
    //初始化表
            table = new Entry<?,?>[initialCapacity];
            threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        }

    重点方法解读:
    //直接对方法加锁(锁这个表)
    public synchronized V put(K key, V value) {
    // Make sure the value is not null
    if (value == null) {//value不能为空
    throw new NullPointerException();
    }

    // Makes sure the key is not already in the hashtable.
    Entry<?,?> tab[] = table;
    int hash = key.hashCode();//key不能为空
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    //获取当前Entry
    Entry<K,V> entry = (Entry<K,V>)tab[index];
    for(; entry != null ; entry = entry.next) {
    //key-hash碰撞,查找是否有相同的key值
    if ((entry.hash == hash) && entry.key.equals(key)) {
    V old = entry.value;
    entry.value = value;//覆盖旧值
    return old;//完成
    }
    }
    addEntry(hash, key, value, index);//添加新值(重点是这个方法)
    return null;
    }

    private void addEntry(int hash, K key, V value, int index) {
    modCount++;//此哈希表在结构上被修改的次数+1
    Entry<?,?> tab[] = table;
    if (count >= threshold) {//如果超过阈值,则重新刷新表
    // Rehash the table if the threshold is exceeded
    rehash();//扩容入口

    tab = table;
    hash = key.hashCode();
    index = (hash & 0x7FFFFFFF) % tab.length;//刷新后重新计算index
    }

    // Creates the new entry.
    @SuppressWarnings("unchecked")
    Entry<K,V> e = (Entry<K,V>) tab[index];
    tab[index] = new Entry<>(hash, key, value, e);执行添加,将新增节点作为根节点
    count++;//完成,数量+1
    }

    //内部类Entry构造方法
    protected Entry(int hash, K key, V value, Entry<K,V> next) {
    this.hash = hash;
    this.key = key;
    this.value = value;
    this.next = next;//设置next节点,与上面衔接
    }

    下面说一下扩容入口rehash方法
    //此方法没有加锁,但由于只被addEntry调用,而调用addEntry的方法均添加了方法锁,所以不存在线程安全问题
    protected void rehash() {
    int oldCapacity = table.length;
    Entry<?,?>[] oldMap = table;

    // overflow-conscious code
    int newCapacity = (oldCapacity << 1) + 1;//扩容
    if (newCapacity - MAX_ARRAY_SIZE > 0) {//超出最大值
    if (oldCapacity == MAX_ARRAY_SIZE)//非首次超出最大值
    // Keep running with MAX_ARRAY_SIZE buckets
    return;
    newCapacity = MAX_ARRAY_SIZE;//首次扩容后超出最大值则设置为最大值
    }
    Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];//新建Entry

    modCount++;//表结构修改次数+1
    threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);//设置阈值
    table = newMap;

       //将原表数据复制到新表
    for (int i = oldCapacity ; i-- > 0 ;) {
    for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {//遍历每一个Entry
    Entry<K,V> e = old;
    old = old.next;

    int index = (e.hash & 0x7FFFFFFF) % newCapacity;//重新计算index
    e.next = (Entry<K,V>)newMap[index];
    newMap[index] = e;
    }
    }
    }

    2.HashMap

    构造函数解读
    public HashMap() {
        //设置默认的负载因子
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted }
    //初始化容量
    public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
    public HashMap(int initialCapacity, float loadFactor) {
      //初始化容量校验
    if (initialCapacity < 0)
    throw new IllegalArgumentException("Illegal initial capacity: " +
    initialCapacity);
    //初始化容量校验
      if (initialCapacity > MAXIMUM_CAPACITY)
    initialCapacity = MAXIMUM_CAPACITY;
    //负载因子校验
      if (loadFactor <= 0 || Float.isNaN(loadFactor))
    throw new IllegalArgumentException("Illegal load factor: " +
    loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);//设置阈值
    }
    //返回给定目标容量的二次幂
    static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
    重要方法解读:
    public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);//实际调用方法
    }
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
    boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
    n = (tab = resize()).length;//未初始化,则执行初始化容量
    if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);//当前插入表位置为空,则直接插入
    else {
        //当前插入表位置已存在元素,则在基础上新增
    Node<K,V> e; K k;
    if (p.hash == hash &&
    ((k = p.key) == key || (key != null && key.equals(k))))//已存在相同key值
    e = p;
    else if (p instanceof TreeNode)
           当前节点属于红黑树节点,则执行红黑树新增逻辑
    e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
    else {
    for (int binCount = 0; ; ++binCount) {
    if ((e = p.next) == null) {//查找链表尾节点
    p.next = newNode(hash, key, value, null);//在链表尾部新增
    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
    treeifyBin(tab, hash);//超过红黑树转化阈值,则转为红黑树
    break;
    }
    if (e.hash == hash &&
    ((k = e.key) == key || (key != null && key.equals(k))))
    break;//如果next节点匹配到key值/key为空,则直接跳出
    p = e;
    }
    }
         //是否已存在key值,e!=null则表示存在
    if (e != null) { // existing mapping for key
    V oldValue = e.value;//获取旧值
    if (!onlyIfAbsent || oldValue == null)
    e.value = value;//覆盖值,条件:可覆盖 或 旧值为空
    afterNodeAccess(e);//将节点移动到最后
    return oldValue;//返回旧值
    }
    }
    ++modCount;//结构修改次数+1
    if (++size > threshold)//当前大小超出阈值
    resize();//执行扩容
    afterNodeInsertion(evict);//移除最年长的,依据代码逻辑分析是删除当前key值为空的
    return null;
    }

    3.ConcurrentHashMap

    构造函数解读
    public
    ConcurrentHashMap() {}//无参构造
    给定初始化容量
    public ConcurrentHashMap(int initialCapacity) {
    if (initialCapacity < 0)
    throw new IllegalArgumentException();//总量参数校验
    int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?//是否超出最大容量的一半
    MAXIMUM_CAPACITY ://当前容量设为最大容量
    tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));//获取下次扩容容量大小 :如果当前容量为2的n次方+x,则下下次扩容容量=2的n+1次方
    this.sizeCtl = cap;
    }
    重置方法:
    private static final int tableSizeFor(int c) {
    int n = c - 1;
    n |= n >>> 1;//有些不明白,为什么这么做
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;//如果容量为负值,则容量置为1
    }
    //初始化元素
    public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
    this.sizeCtl = DEFAULT_CAPACITY;//下次扩容容量,后面添加的时候会被修改
    putAll(m);//添加所有,至于添加,会在后面分析
    }
    设置容量和负载因子
    public ConcurrentHashMap(int initialCapacity, float loadFactor) {
    this(initialCapacity, loadFactor, 1);//并发级别设置为1级
    }

    public ConcurrentHashMap(int initialCapacity,
    float loadFactor, int concurrencyLevel) {
    if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)//负载因子和并发级别校验
    throw new IllegalArgumentException();
    if (initialCapacity < concurrencyLevel) // Use at least as many bins(容量至少等于并发级别)
    initialCapacity = concurrencyLevel; // as estimated threads
    long size = (long)(1.0 + (long)initialCapacity / loadFactor);//计算大小 容量/负载因子+1
    int cap = (size >= (long)MAXIMUM_CAPACITY) ?
    MAXIMUM_CAPACITY : tableSizeFor((int)size);//获取下次扩容容量大小(同上)
    this.sizeCtl = cap;
    }
    重要方法解读:
    public V put(K key, V value) {
    return putVal(key, value, false);//可覆盖
    }
    final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();//键值空校验
    int hash = spread(key.hashCode());//hash计算
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
    Node<K,V> f; int n, i, fh;
    if (tab == null || (n = tab.length) == 0)
    tab = initTable();//表为空则初始化表
    else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {//获取表中位置元素
    if (casTabAt(tab, i, null,
    new Node<K,V>(hash, key, value, null)))//空处理
    break; // no lock when adding to empty bin
    }
    else if ((fh = f.hash) == MOVED)//哈希相同
    tab = helpTransfer(tab, f);//协助传输
    else {
    V oldVal = null;
    synchronized (f) {//对象锁
    if (tabAt(tab, i) == f) {
    if (fh >= 0) {//hash值大于0
    binCount = 1;
    for (Node<K,V> e = f;; ++binCount) {
    K ek;
    if (e.hash == hash &&
    ((ek = e.key) == key ||
    (ek != null && key.equals(ek)))) {//发生碰撞
    oldVal = e.val;//获取旧值
    if (!onlyIfAbsent)
    e.val = value;//设置新值
    break;
    }
    Node<K,V> pred = e;
    if ((e = e.next) == null) {
    pred.next = new Node<K,V>(hash, key,
    value, null);//执行新增
    break;
    }
    }
    }
    else if (f instanceof TreeBin) {//属于红黑树
    Node<K,V> p;
    binCount = 2;
    if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
    value)) != null) {//添加树节点
    oldVal = p.val;
    if (!onlyIfAbsent)
    p.val = value;
    }
    }
    }
    }
    if (binCount != 0) {
    if (binCount >= TREEIFY_THRESHOLD)//超出树转化阈值
    treeifyBin(tab, i);//转化
    if (oldVal != null)
    return oldVal;
    break;
    }
    }
    }
    addCount(1L, binCount);//数量+1
    return null;
    }
    hash计算
    static final int spread(int h) {
    return (h ^ (h >>> 16)) & HASH_BITS;
    }
    初始化表空间
    private final Node<K,V>[] initTable() {
    Node<K,V>[] tab; int sc;
    while ((tab = table) == null || tab.length == 0) {//空表执行初始化
    if ((sc = sizeCtl) < 0)//扩容小于0
    Thread.yield(); // lost initialization race; just spin 放弃初始化权利,让初cpu
    else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {//CAS获取执行权限
    try {
    if ((tab = table) == null || tab.length == 0) {//再次校验
    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
    @SuppressWarnings("unchecked")
    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];//初始化容量
    table = tab = nt;
    sc = n - (n >>> 2);//取半
    }
    } finally {
    sizeCtl = sc;
    }
    break;
    }
    }
    return tab;
    }
    
    
    注:
    U.compareAndSwapInt方法说明:
    改方法是一个原子方法,通过反射根据字段偏移去修改对象的
    此这个方法有四个参数
      第一个参数为需要改变的对象
      第二个为偏移量(即之前求出来的valueOffset的值)
      第三个参数为期待的值
      第四个为更新后的值。
    整个方法的作用即为若调用该方法时,value的值与expect这个值相等,那么则将value修改为update这个值,并返回一个true,
    如果调用该方法时,value的值与expect这个值不相等,那么不做任何操作,并返回false

    Kevin原创

  • 相关阅读:
    table固定头部,tbody内容滚动
    js 中json遍历 添加 修改 类型转换
    SEO优化
    JS对字符串的操作,截取
    移动端 去掉滚动栏
    JS 引擎的执行机制
    Uncaught SyntaxError: Unexpected token ILLEGAL
    利用css 画各种三角形
    js文本公告滚动展示,图片轮播....
    js判断手指的上滑,下滑,左滑,右滑,事件监听
  • 原文地址:https://www.cnblogs.com/sunshinekevin/p/10625516.html
Copyright © 2011-2022 走看看