HashMap
前提
二叉树的定义:每个节点最多有两个“叉”,分别是左子节点和右子节点。不要求每个节点都有两个子节点,有的节点只有左子节点,有的节点只有右子节点,并且二叉树的每个节点的左子树和右子树也分别满足二叉树的定义。
二叉搜索树(BST,Binary Search Tree)又名二叉查找树,有序二叉树。树中的任意一个节点,它的左子树中每个节点的值都要小于这个节点的值,而右子树节点的值都大于这个节点的值。在极端情况下会退化为链表,左右子树极度不平衡,查找元素的时间复杂度为O(n),正常情况下O(logN)
红黑树:一种自平衡的二叉搜索树
【HashMap中最重要的数据结构】散列表:红黑树+链表
散列表(Hash Table)又称为哈希表,是根据Key直接访问在内存存储位置value,它是由数组演化而来的,利用了数组支持按照下标进行随机访问数据的特性。
散列冲突,又称为哈希冲突、哈希碰撞。指多个key映射到同一个数组下标位置。
我们通常采用拉链法:数组的每个下标位置称之为桶bucket,每个桶会对应一个链表,当hash冲突后的元素都放到相同槽位对应的链表中或红黑树中。
HashMap的实现原理?
计算key的值通过hashCode()方法,判断两个key是否相同则通过equals()方法。
底层使用hash表数据结构,即数组+(链表 | 红黑树),在添加数据时,计算key的值确认元素在数组中的下标:
key相同则直接替换,不同则存入链表或红黑树中。
Jdk1.7和1.8的区别?
JDK1.7之前采用的是拉链法,数组+ 链表。
JDK1.8及之后采用数组+链表+红黑树,链表长度大于等于8且数组长度大于等于64,则从链表转化为红黑树。
同时1.7采用的是头插法,当发生哈希碰撞的时候,新的元素会被插入到与该哈希值相对应的哈希桶的头部,这种方法插入效率高,但在多线程环境下可能会导致一些问题。
1.8采用的是尾插法,并且如果链表长度超过某个阈值(默认为8),链表会被转换为红黑树,新元素会被插入到红黑树中。这种改变时为了在高并发情况下减少扩容时链表元素原有的顺序,从而在扩容时减少元素的移动嘛。
JDK1.7的put方法,如果定位到的数组没元素则直接插入,如果有元素,则遍历当前元素为头结点的链表,依次和插入的key比较【通过equals方法】,如果相同则直接覆盖,不同则采用头插法。
public V put(K key, V value)
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) { // 先遍历
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i); // 再插入
return null;
}
JDK1.8
如果定位到的数组位置没有元素 就直接插入。
如果定位到的数组位置有元素就和要插入的 key 比较,如果 key 相同就直接覆盖,如果 key 不相同,就判断 p 是否是一个树节点,如果是就调用e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value)
将元素添加进入。如果不是就遍历链表插入(插入的是链表尾部)。
HashMap和ConcurrentHashMap区别?
HashMap不是线程安全的。如果多个线程同时访问HashMap,并且至少有一个线程修改了map,则必须外部同步(如添加Synchronized关键字),即如果都是读操作,则没事。
ConcurrentHashMap是线程安全的,内置了分段锁或其他形式的并发控制机制,允许多个线程并发读写,而不需要外部同步。
性能上,ConcurrentHashMap优于同步的HashMap,因为ConcurrentHashMap的锁分段技术减少了锁竞争。
同步机制:如果需要在多线程环境下使用HashMap,因为没有内置同步机制,我们必须通过外部同步来保证线程安全,如Collections.synchronizedMap方法包装HashMap。
ConcurrentHashMap通过内部锁机制,(java8之前是分段锁,8及以后是CAS操作和volatile变量)来实现同步,从而提供了更高的并发性能。