要说说hashMap和hashTable的区别就要说说他们出现的时间顺序了:hashTable出现的比较早,hashMap出现的比较晚了;所以hashMap就对与hashTable进行了一些改进;
主要有一下方面:
- hashMap是非线程安全的,所以效率比较高;
- hashMap允许key和value为空;
- 将key和value的null值改为0进行存储;如果不改的话存储null值时,用equals()方法时,会有空指针异常;
- hashMap改原来的contains()方法为containsKey和containsValue();
- hashTable()默认大小是11,扩容是2 * old + 1;而hashMap默认大小是16,且容量只能是2的幂;扩容2*old;
- hashMap根据hashCode ^ hashcode >>> 16计算hash值,后取模数组的长度;而hashTable直接使用hashcode进行取模;
hashMap的数据结构
数组+链表+红黑树:
当冲突次数大于8时,首先考虑数组的扩容,当数组的长度大于64时,会将(冲突)链表改为红黑树;
当数组使用了75%的时候,进行数组扩容;
- 为什么是8:
- 泊松分布:定位到同一个位置的概率,在达到8此后,再次定位到此位置的概率为0;
- 为什么是2的幂:
- 扩容后重新定位方便
- 0.75:是在0.5和1中取得中庸值;
几个关键阈值
- 数组默认初始大小为16,初始化大小必须是2的幂次;
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
- 最大容量,即数组的初始容量不能大于 2的30次方;
static final int MAXIMUM_CAPACITY = 1 << 30;
- 负载因子,当数组使用了75%时,进行数组扩容;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
*桶的树化阈值: 当链表长度大于8时,链表转红黑树;
static final int TREEIFY_THRESHOLD = 8;
- 桶的链表还原阈值:当链表长度小于6时,红黑树转链表;
static final int UNTREEIFY_THRESHOLD = 6;
- 最小树形化容量阈值:
static final int MIN_TREEIFY_CAPACITY = 64;
- 扩容过程:当冲突次数大于8时,首先考虑数组的扩容,当数组的长度大于64时,会将(冲突)链表改为红黑树;当数组使用了75%的时候,进行数组扩容;
线程安全的 map
当我们需要HashMap是线程安全的时,怎么办呢?我们可以通过Collections.synchronizedMap(hashMap)来进行处理,亦或者我们使用线程安全的ConcurrentHashMap。ConcurrentHashMap虽然也是线程安全的,但是它的效率比Hashtable要高好多倍。因为ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定。