一、HashMap类
- 数据结构:由数组、链表和红黑树(JDK1.8开始)构成;
- 存储和查找效率很高,但是是线程不安全的类,不适用于并发环境中。若想在高并发的情况下使用,可以使用Collections.syncronizedMap()来进行包装,或使用HashTable类、ConcurrentHashMap类;(在JDK1.7及以上版本,建议使用ConcurrentHashMap)
- 当链表长度大于8是,会生成红黑树,相对于只使用链表的结构,有更高的读写效率;
- 重要的参数值:
- 初始容量是16;
- 默认的加载因子(loadFactor)是0.75,加载因子的设置是在权衡时间和空间基础上进行设置的;该值可以大于1,在时间需求高的情况下可以,设置更小的填充因子;在空间需求高而时间需求不高的情况下,设置更大的填充因子;
- 扩容因子是2,当capacity需要被扩容时,容量翻倍;
- HashMap的容量:一定是2的n次方,当输入的值不是2的n次方的时候,会通过tableSizeFor方法,获得比输入值大的2的n次方;
/** * 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; }
- Hash算法:取key的hashCode值、高位运算、取模运算
不管增加、删除、查找键值对,定位到哈希桶数组的位置都是很关键的第一步。
我们的目的是希望HashMap里面的元素位置尽量分布均匀些,尽量使得每个位置上的元素数量只有一个,那么当我们用hash算法求得这个位置的时候,马上就可以知道对应位置的元素就是我们要的,不用遍历链表,大大优化了查询的效率。HashMap定位数组索引位置,直接决定了hash方法的离散性能
HashMap的容量是2的N次方,当length总是2的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。//方法一: static final int hash(Object key) { //jdk1.8 & jdk1.7 int h; // h = key.hashCode() 为第一步 取hashCode值 // h ^ (h >>> 16) 为第二步 高位参与运算 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } //方法二: static int indexFor(int h, int length) { //jdk1.7的源码,jdk1.8没有这个方法,但是实现原理一样的 return h & (length-1); //第三步 取模运算 }
在JDK1.8的实现中,优化了高位运算的算法,通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度、功效、质量来考虑的,这么做可以在数组table的length比较小的时候,也能保证考虑到高低Bit都参与到Hash的计算中,同时不会有太大的开销。
学习参考:https://tech.meituan.com/2016/06/24/java-hashmap.html
二、HashTable类
学习HashMap就不得不同时对比一下HashTable类(虽然该类已经过时了)
- HashTable类是一个线程安全的类,每个方法都实现了syncronized同步,因此多个线程对于该类的访问是串行化的。特别是HashTable中键值对超过了一定的数量时,执行效率会非常低。
- 数据结构:由数组和链表构成;
- 重要的参数值:
- 初始容量是13;
- 默认的加载因子(loadFactor)是0.75与HashMap一样;
- 默认扩容是原来容量的两倍加1;
- 与HashMap不同的是HashTable不可以接受null值(HashMap可以接受一个key为空,多个值为空);
三、ConcurrentHashMap类
该类是JDK1.7之后最常用的键值对并发容器
- 数据结构:syncronized+CAS+HashEntry+红黑树;
- 性能:因为锁粒度远远小于HashTable类,只有当数组中发生hash碰撞时,才对HashEntry加同步锁。并不会影响其他HashEntry对象的数据的读写;