就是把任意长度的输入(又叫作预映射, pre-image),经过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间一般远小于输入的空间,不一样的输入可能会散列成相同的输出,因此不可能从散列值来肯定惟一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。经常使用HASH函数:直接取余法、乘法取整法、平方取中法java
经常使用hash算法的介绍:(1) MD4(2) MD5它对输入仍以512位分组,其输出是4个32位字的级联(3) SHA-1
CopyOnWriteArrayList
在多线程环境下,使用HashMap进行put
操做会引起多线程扩容,因为JDK1.7中HashMap的链表采用头插法,多线程扩容后会造成环形数据结构,一旦造成环形数据结构,Entry的next节点永远不为空,就会产生死循环获取Entry,致使在get
遍历查找元素的时候进入死循环,使得CPU利用率接近100%面试
HashMap之因此在并发下的扩容形成死循环,是由于,多个线程并发进行时,由于一个线程先期完成了扩容,将原Map的链表从新散列到本身的表中,而且链表变成了倒序,后一个线程再扩容时,又进行本身的散列,再次将倒序链表变为正序链表。因而造成了一个环形链表,当get表中不存在的元素时,形成死循环
①、HashMap 是线程不安全的,HashTable 是线程安全的算法
②、因为线程安全,因此 HashTable 的效率比不上 HashMap数组
③、HashMap最多只容许一条记录的键为null,容许多条记录的值为null,而 HashTable 不容许安全
④、HashMap 默认初始化数组的大小为16,HashTable 为 11,前者扩容时,扩大两倍,后者扩大两倍+1数据结构
⑤、HashMap 须要从新计算 hash 值,而 HashTable 直接使用对象的 hashCode多线程
ConcurrentHashMap 类(是 Java并发包 java.util.concurrent 中提供的一个线程安全且高效的 HashMap 实现)。并发
HashTable 是使用 synchronize 关键字加锁的原理(就是对对象加锁);函数
而针对 ConcurrentHashMap,在 JDK 1.7 中采用分段锁的方式;JDK 1.8 中直接采用了CAS(无锁算法)+ synchronized,也采用分段锁的方式并大大缩小了锁的粒度。高并发
除了加锁,原理上无太大区别
另外,HashMap 的键值对容许有null
(key为null放在table的0号位置),可是ConCurrentHashMap 都不容许
在数据结构上,红黑树相关的节点类
HashTable 使用一把锁(锁住整个链表结构)处理并发问题,多个线程竞争一把锁,容易阻塞
ConcurrentHashMap
JDK 1.7 中使用分段锁(ReentrantLock + Segment + HashEntry),至关于把一个 HashMap 分红多个段,每段分配一把锁,这样支持多线程访问。锁粒度:基于 Segment,包含多个 HashEntry。
JDK 1.8 中使用 CAS + synchronized + Node + 红黑树。锁粒度:Node(首结点)(实现 Map.Entry<K,V>)。锁粒度下降了。
JDK 1.7 中,采用分段锁的机制,实现并发的更新操做,底层采用数组+链表的存储结构,包括两个核心静态内部类 Segment 和 HashEntry。
①、Segment 继承 ReentrantLock(重入锁) 用来充当锁的角色,每一个 Segment 对象守护每一个散列映射表的若干个桶
②、HashEntry 用来封装映射表的键-值对
③、每一个桶是由若干个 HashEntry 对象连接起来的链表
JDK 1.8 中,采用Node + CAS + Synchronized来保证并发安全。取消类 Segment,直接用 table 数组存储键值对;当 HashEntry 对象组成的链表长度超过 TREEIFY_THRESHOLD 时,链表转换为红黑树,提高性能。底层变动为数组 + 链表 + 红黑树
①、重要的常量:
private transient volatile int sizeCtl;
当为负数时,-1 表示正在初始化,-N 表示 N - 1 个线程正在进行扩容
当为 0 时,表示 table 尚未初始化
当为其余正数时,表示初始化或者下一次进行扩容的大小
②、数据结构:
Node 是存储结构的基本单元,继承 HashMap 中的 Entry,用于存储数据
TreeNode 继承 Node,可是数据结构换成了二叉树结构,是红黑树的存储结构,用于红黑树中存储数据
TreeBin 是封装 TreeNode 的容器,提供转换红黑树的一些条件和锁的控制
③、存储对象时(put() 方法):
initTable()
方法来进行初始化addCount()
方法统计 size,而且检查是否须要扩容④、扩容方法 transfer()
:默认容量为 16,扩容时,容量变为原来的两倍
helpTransfer()
:调用多个工做线程一块儿帮助进行扩容,这样的效率就会更高
⑤、获取对象时(get()方法):
ForwardingNode.find()
方法,查找该结点,匹配就返回;1.7中程序运行时可以同时更新 ConccurentHashMap 且不产生锁竞争的最大线程数。默认为 16,且能够在构造函数中设置。当用户设置并发度时,ConcurrentHashMap 会使用大于等于该值的最小2幂指数做为实际并发度(假如用户设置并发度为17,实际并发度则为32)
1.8中并发度则无太大的实际意义了,主要用处就是当设置的初始容量小于并发度,将初始容量提高至并发度大小