java 容器之 Hashtable、HashMap、ConcurrentHashMap 原理简述

HashMap

  • 非线程安全容器
  • 内部维护一个散列表(数组)
    key经过hash算法 & 数组长度 获得散列表数组的下标

hash算法

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

这里将 key的 hash值 与 hash值 右移16位后的 值进行抑或运算(丰富低位随机性)。 目的是使hash大于 2的16次方时增强hash值地位的碰撞几率(因为 hash值过高时 低位的碰撞几率会很高),有人测试 这种方法可以降低 10%的碰撞几率。
取到hsah值后这样获取散列表下标
(n - 1) & hash // 舍弃散列表低位获得一个 小于数组长度的下标(n:数组长度, 数组最大下标 永远取 2n-1, 二进制有效位 永远是1 很好的低位掩码

hashMap的hash运算详解

散列表

当散列表的 掩码运算出现碰撞时怎么办呢? 没关系 散列表默认存储的是一个 双向链表
当发生碰撞时会不断向链表尾部插入数据


but 当链表过长时 检索数据会是个问题。
所以当链表长度大于8时 hashMap扩展出一个平衡的树结构。红黑树
树结构的检索效率要高得多
but 还会保留链表结构 why?为了 forEach。。。。

散列表扩容

散列表默认初始长度 16, 可通过构造器指定初始长度(链表实际长度会根据 输入的长度尽行计算 自动算出选择一个合适的长度)

/**
  * 这里的代码有时间在做深度研究
  * Returns a power of two size for the given target capacity.
  */
 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;
 }

当散列表内 数据数量大于 16 * 0.75 时会进行扩容 每次 * 2(可指定)
当哈希表中的容量大于 MIN_TREEIFY_CAPACITY = 64;这个值时,表中的桶才能进行树形化
否则桶内元素太多时会扩容,而不是树形化


Hashtable

  • 线程安全 (通过synchronized锁实现)
  • 内部结构 类似 hashMap 散列表+链表 不过 jdk8中没有升级红黑树, 可能 hashTable 这种串行线程安全容器已被遗弃 ConcurrentHashMap 与之对比有着绝对的优势

ConcurrentHashMap

  • 线程安全
  • 内部存储结构 与hashMap 大致相同(jdk8以前为分段锁)
  • 使用cas(比较并替换)+volatile + synchronized 关键字 实现安全并发
  • synchronized 的使用很少 只有在对table上单个的链表进行操作时 使用此关键字(仅排斥其他变更(remove||put) 不影响get)
  • 悲观锁与乐观锁:
    悲观锁是指如果一个线程占用了一个锁,而导致其他所有需要这个锁的线程进入等待,一直到该锁被释放,换句话说就是这个锁被独占,比如说典型的就是synchronized;乐观锁是指操作并不加锁,而是抱着尝试的态度去执行某项操作,如果操作失败或者操作冲突,那么就进入重试,一直到达到目的为止
hash值计算略有不同:
 static final int spread(int h) {
   return (h ^ (h >>> 16)) & HASH_BITS; // 生成一个 小于long int 最大值的hash
 }
对于ConcurrentHashMap 的浅读 只到这里

ConcurrentHashMap 涉及到并发 理解起来比较复杂。 后续单独开贴更新

发布了17 篇原创文章 · 获赞 24 · 访问量 28万+

猜你喜欢

转载自blog.csdn.net/qq_22956867/article/details/79414338