一、TreeMap和HashMap比较
TreeMap | HashMap | |
---|---|---|
增加的平均时间复杂度 | O(log N) | O(1) |
删除的平均时间复杂度 | O(log N) | O(1) |
查询的平均时间复杂度 | O(log N) | O(1) |
而且TreeMap中存放的元素要求必须可以比较大小,而HashMap没有这个要求。
二、哈希表简介
其实利用哈希表操作数据的实现包含两步:1、利用哈希函数计算出对应key的索引;2、直接根据索查找到数组中的对应元素。
哈希函数的生成步骤:1、计算key的哈希值(int类型);2、将这个哈希值与数组长度进行相关运算得出一个索引。
1、哈希函数—计算哈希值
整数的哈希值:直接将整数值作为哈希值。
Integer a = 123;
System.out.println(a.hashCode());
//控制台输出为123
浮点数的哈希值:
将浮点数在内存地址中的二进制数字直接转换成十进制整数。单精度浮点型float是四个字节和int一样都是32位,不会溢出。然而双精度浮点型double是64位,所以来一波高32位和低32位异或扰动计算出哈希值并强转为int。
public static int getFloatHash(float x) {
return Float.floatToIntBits(x);
}
public static void main(String[] args) {
Float f = 45.8f;
System.out.println(f.hashCode());
System.out.println(getFloatHash(f));
}
//控制台输出都是1110913843
public static int getDoubleHash(double x) {
long value = Double.doubleToLongBits(x);
return (int) ((value >>> 32) ^ value);
}
public static void main(String[] args) {
Double b = 63.7;
System.out.println(b.hashCode());
System.out.println(getDoubleHash(b));
}
//控制台都是输出-640270333
字符串的哈希值:
5493十进制计算为:5x1000+4x100+9x10+3x1。对于字符串哈希值的计算类似,字符串中每个字符都有对应的ASCII值,还缺少一个类似进制数的n,jdk源码中用的是31,因为JVM会将31 x i优化为(i << 5) - i
。
public static int getStringHash(String s) {
int hash = 0;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
// hash = hash * 31 + c;
hash = (hash << 5) - hash + c;
}
return hash;
}
public static void main(String[] args) {
String str = "Hello World!";
System.out.println(str.hashCode());
System.out.println(getStringHash(str));
}
//控制台都是输出-969099747
2、哈希函数—计算索引值
要想生成的索引不会越界,最容易想到的就是将算出来的哈希值直接对数组的长度取模。但是取模运算比减慢,当数组长度设计成2的n次幂的时候,用位运算也可以起到相同的作用。
index = hashCode & (table.length - 1);
相当于直接丢弃高位上的数,效果也是一样的。
3、哈希冲突
当计算出的索引值相同的时候即发生了哈希冲突。jdk官方的解决措施是将发生冲突的结点用单向链表或红黑树串起来。当数组长度超过一定长度并且每个链表上存放的数据超过一定数量的时候,单向链表转换为红黑树。当数组长度和每个红黑树上的结点数减少到一定值的时候,红黑树再次转换为单向链表。
4、哈希表性能优化
① 扰动计算哈希值
② 增删查顺序
先直接比较哈希值来决定添加到树的哪个位置。
再用equals函数比较是不是同一个对象。
再判断是否是同一个类,是同一个类的话是否可比较。
如果哈希值相等而且不是同一个对象也不具有可比较性,那就遍历左右子树看是否已经存在,存在就覆盖应相的键值对。
如果还是找不到,就根据内存地址决定放左子树还是右子树。
③ 扩容
装填因子:结点总数量除以数组长度。
jdk源码是当装填因子大于0.75的时候,就会扩容为原来的两倍。
那原来数组上的所有结点需要挪到新的数组上面,不能直接挪根结点。如果直接把红黑树的根结点挪动过去的话,那就违背了扩容的初衷了,扩容本就是为了减少哈希冲突,你直接整棵树整棵树的挪动,相当于之前的结点还是分布在原来的数组上面,这是大错特错的。
正确做法就是遍历每颗红黑树,将树上的结点挨个计算新的索引,在放到新的数组上面。
新索引可以由位运算的性质,推出来,要么索引不变,要么增加了数组原来的长度。
④ 手动实现hashCode函数也可以减少相应的IO操作并起到优化哈希表性能的功效。Object中的hashCode函数默认是按照内存地址计算的哈希值,每次都要进行相应的IO操作读取内存地址,效率低。
三、HashMap
1、 HashMap简介
HashMap其实就是利用哈希表实现的映射,效率比红黑树高一半多(空间换时间而已),而且其中的元素没有可以比较大小的限制条件。不可以比较大小也会根据identityHashCode
存放。
2、写代码注意点
① 扩容的时候怎么挪动结点?
为了发挥哈希表的最大性能,明显扩容的时候要挨个遍历每棵红黑树,重新计算索引,再放到新的哈希表中。挪动结点的时候,不需要再从左右子树遍历查找是否已经有对应的key值存在了,因为既然在一棵树上,那就代表肯定不会有重复的key值。
② 如何减少哈希值的计算次数?将哈希值变为结点的成员变量即可。
3、实现
public class HashMap<K, V> implements Map<K, V> {
private static final int RED = 1;
private static final int BLACK = 0;
protected static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
private static final float FACTOR = 0.75f;
Node<K, V>[] table;
private int size = 0;
public HashMap() {
table = new Node[DEFAULT_INITIAL_CAPACITY];
}
protected static class Node<K, V> {
K key;
V value;
int hash;
int color = RED;
Node<K, V> left;
Node<K, V> right;
Node<K, V> parent;
public Node(K key, V value, Node<K, V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
int hash = key == null ? 0 : key.hashCode();
this.hash = hash ^ (hash >>> 16); //扰动计算哈希值
}
}
@Override
public int size() {
return this.size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public void clear() {
size = 0;
for (int i = 0; i < table.length; i++)
table[i] = null;
}
@Override
public V put(K key, V value) {
resize(); //哈希表扩容
K key1 = key;
int index = getIndex(key1);
Node<K, V> root = table[index];
Node<K, V> newNode = null;
if (root == null) {
newNode = createNode(key1, value, null);
table[index] = newNode;
afterPut(newNode);
fixPut(newNode);
size++;
return null;
}
Node<K, V> node = root;
Node<K, V> temp = node;
Node<K, V> result = null;
boolean isSearched = false;
int cmp = -1;
int h1 = hash(key1);
V old = null;
do {
int h2 = node.hash;
K key2 = node.key;
if (h1 < h2) {
cmp = -1;
} else if (h1 > h2) {
cmp = 1;
} else if (Objects.equals(key1, key2)) {
cmp = 0;
} else if (key1 != null && key2 != null
&& key1.getClass() == key2.getClass()
&& key1 instanceof Comparable
&& ((cmp = ((Comparable) key1).compareTo(key2)) != 0)) {
//哈希值相等并且不是同一个对象, 但是可以比较出结果
} else if (!isSearched) {
if ((node.left != null && (result = get(node.left, key1)) != null)
|| (node.right != null && (result = get(node.right, key1)) != null)) {
node = result;
cmp = 0;
} else {
cmp = System.identityHashCode(key1) - System.identityHashCode(key2);
isSearched = true;
}
} else {
cmp = System.identityHashCode(key1) - System.identityHashCode(key2);
}
temp = node;
if (cmp > 0)
node = node.right;
else if (cmp < 0)
node = node.left;
else
break;
} while (node != null);
newNode = createNode(key1, value, temp);
if (cmp > 0) {
temp.right = newNode;
} else if (cmp < 0) {
temp.left = newNode;
} else { //相等就覆盖
old = node == null ? null : node.value;
node.key = key1;
node.value = value;
node.hash = h1;
// afterPut(temp.right);
return old;
}
afterPut(newNode);
fixPut(newNode);
size++;
return null;
}
@Override
public V get(K key) {
int index = getIndex(key);
Node<K, V> node = get(table[index], key);
return node == null ? null : node.value;
}
@Override
public V remove(K key) {
return remove(getNode(key));
}
@Override
public boolean containsKey(K key) {
return getNode(key) == null ? false : true;
}
@Override
public boolean containsValue(V value) {
Queue<Node<K, V>> queue = new LinkedList<>();
for (int i = 0; i < table.length; i++) {
if (table[i] == null)
continue;
queue.offer(table[i]);
while (!queue.isEmpty()) {
Node<K, V> node = queue.remove();
if (Objects.equals(node.value, value))
return true;
if (node.left != null)
queue.offer(node.left);
if (node.right != null)
queue.offer(node.right);
}
}
return false;
}
@Override
public void traversal(Visitor<K, V> visitor) {
Queue<Node<K, V>> queue = new LinkedList<>();
for (int i = 0; i < table.length; i++) {
if (table[i] == null)
continue;
queue.offer(table[i]);
while (!queue.isEmpty()) {
Node<K, V> node = queue.remove();
visitor.visit(node.key, node.value);
if (node.left != null)
queue.offer(node.left);
if (node.right != null)
queue.offer(node.right);
}
}
}
protected Node<K, V> createNode(K key, V value, Node<K, V> parent) {
return new Node<>(key, value, parent);
}
protected void afterPut(Node<K, V> node) {
}
protected void exchangeNodes(Node<K, V> node1, Node<K, V> node2) {
}
private V remove(Node<K, V> node) {
int index = getIndex(node.key);
if (node == null)
return null;
V old = node.value;
if (node.left != null && node.right != null) { //删除的是度为2的结点
Node<K, V> des = getSuccessor(node);
node.key = des.key;
node.value = des.value;
node.hash = des.hash; //删除度为2结点的时候对应的hash值也要改变
node = des;
exchangeNodes(node, des);
}
if (node.left == null && node.right == null) { //删除度为0的结点
if (node.parent == null) {
table[index] = null;
} else if (node.parent.left == node) {
node.parent.left = null;
} else {
node.parent.right = null;
}
fixRemove(node);
} else { //删除度为1的结点
Node<K, V> replace = node.left == null ? node.right : node.left;
if (node.parent == null) {
table[index] = replace;
} else if (node.parent.left == node) {
node.parent.left = replace;
} else {
node.parent.right = replace;
}
replace.parent = node.parent;
fixRemove(replace);
}
size--;
afterRemove(node);
return old;
}
protected void afterRemove(Node<K, V> node) {
}
private int getIndex(K key) {
int hash = key == null ? 0 : key.hashCode();
return hash & (table.length - 1);
}
private int hash(K key) {
int hash = key == null ? 0 : key.hashCode();
return hash ^ (hash >>> 16);
}
private void moveNode(Node<K, V> oldNode) {
if (oldNode == null)
return;
//重置结点
oldNode.parent = null;
oldNode.left = null;
oldNode.right = null;
oldNode.color = RED;
K key1 = oldNode.key;
V value = oldNode.value;
int index = getIndex(key1); //哈希表新索引要么不变, 要么加上原来的容量
if (table[index] == null) {
table[index] = oldNode;
fixPut(oldNode);
return;
}
Node<K, V> node = table[index];
Node<K, V> temp = node;
int cmp = -1;
int h1 = key1 == null ? 0 : key1.hashCode();
do {
int h2 = node.hash;
K key2 = node.key;
if (h1 < h2) {
cmp = -1;
} else if (h1 > h2) {
cmp = 1;
} else if (Objects.equals(key1, key2)) {
cmp = 0;
} else if (key1 != null && key2 != null
&& key1.getClass() == key2.getClass()
&& key1 instanceof Comparable
&& ((cmp = ((Comparable) key1).compareTo(key2)) != 0)) {
} else {
cmp = System.identityHashCode(key1) - System.identityHashCode(key2);
}
temp = node;
if (cmp > 0)
node = node.right;
else if (cmp < 0)
node = node.left;
else
break;
} while (node != null);
if (cmp > 0) {
temp.right = oldNode; //直接将老的结点赋值给对应位置, 而不需要new新的结点
} else if (cmp < 0) {
temp.left = oldNode;
}
oldNode.parent = temp; //结点的父亲需要维护, 因为之前已经喝下孟婆汤重置过了
fixPut(oldNode);
}
/**
* 哈希表的扩容。
* 做法: 逐个遍历结点并添加到新的哈希表上面, 这样才能减少哈希冲突;
* 如果你是直接将结点挪到新的哈希表上面的话, 之前加入的结点相当于是没扩容之前加进去的, 无法最大限度地发挥哈希表的性能!
*/
private void resize() {
if (table.length == 0)
return;
if ((float) this.size / table.length <= FACTOR)
return;
Node<K, V>[] oldHashTable = table;
table = new Node[oldHashTable.length << 1];
Queue<Node<K, V>> queue = new LinkedList<>();
for (int i = 0; i < oldHashTable.length; i++) {
if (oldHashTable[i] == null)
continue;
queue.offer(oldHashTable[i]);
while (!queue.isEmpty()) {
Node<K, V> node = queue.remove();
if (node.left != null)
queue.offer(node.left);
if (node.right != null)
queue.offer(node.right);
moveNode(node); //必须放在左右儿子入队之后, 因为这个结点已经在函数中重置了
}
}
}
private void ToColor(Node<K, V> node, int color) {
node.color = color;
}
private void ToRed(Node<K, V> node) {
node.color = RED;
}
private void ToBlack(Node<K, V> node) {
node.color = BLACK;
}
private int colorOf(Node<K, V> node) {
return node.color;
}
private boolean isRed(Node<K, V> node) {
return node == null ? false : node.color == RED;
}
private boolean isBlack(Node<K, V> node) {
return node == null ? true : node.color == BLACK;
}
private void rotateLeft(Node<K, V> grandparent) {
int index = getIndex(grandparent.key);
Node<K, V> parent = grandparent.right;
Node<K, V> node = parent.right;
grandparent.right = parent.left;
parent.left = grandparent;
if (grandparent.right != null) //左旋转时parent的左子树可能为空
grandparent.right.parent = grandparent;
parent.parent = grandparent.parent;
if (table[index] == grandparent) //改变grandparent父结点的指向
table[index] = parent;
else if (grandparent.parent.left == grandparent)
grandparent.parent.left = parent;
else
grandparent.parent.right = parent;
grandparent.parent = parent;
}
private void rotateRight(Node<K, V> grandparent) {
int index = getIndex(grandparent.key);
Node<K, V> parent = grandparent.left;
Node<K, V> node = parent.left;
grandparent.left = parent.right;
parent.right = grandparent;
if (grandparent.left != null) //右旋转时parent的右子树可能为空
grandparent.left.parent = grandparent;
parent.parent = grandparent.parent;
if (table[index] == grandparent) //改变grandparent父结点的指向
table[index] = parent;
else if (grandparent.parent.left == grandparent)
grandparent.parent.left = parent;
else
grandparent.parent.right = parent;
grandparent.parent = parent;
}
private void fixPut(Node<K, V> node) {
Node<K, V> parent = node.parent;
int index = getIndex(node.key);
if (node == table[index] || parent == null) { //根结点直接染黑
ToBlack(node);
return;
}
if (isBlack(parent)) { //父结点是黑色直接添加
return;
}
Node<K, V> grandparent = parent.parent;
Node<K, V> uncle = grandparent.left == parent ? grandparent.right : grandparent.left;
if (isRed(uncle)) { //上溢
ToBlack(parent);
ToBlack(uncle);
ToRed(grandparent);
fixPut(grandparent);
return;
} else { //超级结点内旋
ToRed(grandparent);
if (grandparent.left == parent) {
if (parent.left == node) { //LL
ToBlack(parent);
rotateRight(grandparent);
} else { //LR
ToBlack(node);
rotateLeft(parent);
rotateRight(grandparent);
}
} else {
if (parent.left == node) { //RL
ToBlack(node);
rotateRight(parent);
rotateLeft(grandparent);
} else { //RR
ToBlack(parent);
rotateLeft(grandparent);
}
}
}
}
private Node<K, V> get(Node<K, V> node, K key) {
if (node == null)
return null;
K key1 = key;
int h1 = hash(key1);
Node<K, V> temp = node;
Node<K, V> result = null;
int cmp = -1;
while (temp != null) {
int h2 = temp.hash;
K key2 = temp.key;
if (h1 < h2) {
temp = temp.left;
} else if (h1 > h2) {
temp = temp.right;
} else if (Objects.equals(key1, key2)) {
return temp;
} else if (key1 != null && key2 != null
&& key1.getClass() == key2.getClass()
&& key1 instanceof Comparable
&& (cmp = ((Comparable) key1).compareTo(key2)) != 0) {
temp = cmp > 0 ? temp.right : temp.left;
} else if (temp.left != null && (result = get(temp.left, key1)) != null) { //递归往左子树找
return result;
} else { //往右子树找
temp = temp.right;
}
}
return null;
}
private Node<K, V> getNode(K key) {
Node<K, V> root = table[getIndex(key)];
return root == null ? null : get(root, key);
}
private void fixRemove(Node<K, V> node) {
if (isRed(node)) { //被删除的是红色结点或者替代结点为红色
ToBlack(node);
return;
}
if (node == null)
return;
Node<K, V> parent = node.parent;
if (parent == null)
return;
boolean removeOnLeft = node.parent.left == null || node.parent.left == node;
Node<K, V> sibling = removeOnLeft ? node.parent.right : node.parent.left;
if (removeOnLeft) { //被删除的结点在左边
if (isRed(sibling)) { //侄子转成兄弟
ToBlack(sibling);
ToRed(parent);
rotateLeft(parent);
sibling = parent.right;
}
if (isBlack(sibling.left) && isBlack(sibling.right)) { //下溢
ToRed(sibling);
if (isBlack(parent)) {
ToBlack(parent);
fixRemove(parent);
return;
} else {
ToBlack(parent);
}
} else if (isRed(sibling.right)) { //RR
ToColor(sibling, colorOf(parent));
ToBlack(parent);
ToBlack(sibling.right);
rotateLeft(parent);
} else { //RL
ToColor(sibling.left, colorOf(parent));
ToBlack(parent);
ToBlack(sibling);
rotateRight(sibling);
rotateLeft(parent);
}
} else { //被删除的结点在右边
if (isRed(sibling)) { //侄子转成兄弟
ToBlack(sibling);
ToRed(parent);
rotateRight(parent);
sibling = parent.left;
}
if (isBlack(sibling.left) && isBlack(sibling.right)) { //下溢
ToRed(sibling);
if (isBlack(parent)) {
ToBlack(parent);
fixRemove(parent);
return;
} else {
ToBlack(parent);
}
} else if (isRed(sibling.left)) { //LL
ToColor(sibling, colorOf(parent));
ToBlack(parent);
ToBlack(sibling.left);
rotateRight(parent);
} else { //LR
ToColor(sibling.right, colorOf(parent));
ToBlack(parent);
ToBlack(sibling);
rotateLeft(sibling);
rotateRight(parent);
}
}
}
private Node<K, V> getSuccessor(Node<K, V> node) {
if (node == null)
return null;
Node<K, V> p = node.right;
if (p != null) {
while (p.left != null) {
p = p.left;
}
return p;
}
Node<K, V> parent = node;
Node<K, V> temp = null;
while (parent != null) {
temp = parent;
parent = parent.parent;
if (parent.left == temp)
break;
}
return parent;
}
}
四、LinkedHashMap
1、LinkedHashMap简介
其实就是在HashMap得基础上增加了一个双向链表,用来维护结点添加的顺序,之前HashMap结点添加后遍历是按照index索引挨个查找的,相当于是有随机性的。LinkedHashMap在添加一个元素后,会将这个结点加到双向链表的尾部,删除元素的时候,也要将对应结点从双向链表中摘除。
2、写代码注意点
① 什么时候维护双向链表?对于添加元素的时候,最好在红黑树性质修复之前就将结点添加到双向链表尾部;删除元素的时候,在最后再删除相应结点,因为删除度为2结点的时候删除的并不是这个结点,而是删除它的前驱结点或者后继结点。
② 双向链表怎么维护结点删除?度为0或1的结点可以直接从双向链表中删除,而且这么做不会影响遍历顺序。但是当删除度为2结点的时候,需要将度为2结点和前驱或后继结点交换各自在双向链表中的位置,再删除前驱或后继结点。
3、双向链表交换结点图解
交换结点2和结点4的last指针:
交换结点2和结点4的next指针:
4、实现
public class LinkedHashMap<K, V> extends HashMap<K, V> {
private LinkedNode<K, V> head;
private LinkedNode<K, V> tail;
private static class LinkedNode<K, V> extends Node<K, V> {
LinkedNode<K, V> last;
LinkedNode<K, V> next;
public LinkedNode(K key, V value, Node<K, V> parent) {
super(key, value, parent);
}
@Override
public String toString() {
return "LinkedNode{" +
"last=" + last.key +
", next=" + next.key +
'}';
}
}
@Override
protected Node<K, V> createNode(K key, V value, Node<K, V> parent) {
return new LinkedNode<>(key, value, parent);
}
/**
* 新增元素的时候, 将结点连接到双向链表尾部, 最好放红黑树修复性质之前
*
* @param node
*/
@Override
protected void afterPut(Node<K, V> node) {
LinkedNode<K, V> linkedNode = (LinkedNode<K, V>) node;
if (head == null) {
head = linkedNode;
tail = linkedNode;
} else if (tail != null && linkedNode != null) {
linkedNode.next = null;
tail.next = linkedNode;
linkedNode.last = tail;
tail = linkedNode;
}
}
/**
* 删除元素的时候, 将结点从双向链表中删除, 最好放红黑树修复性质之前
*
* @param node 真正被删除的结点
*/
@Override
protected void afterRemove(Node<K, V> node) {
LinkedNode<K, V> linkedNode = (LinkedNode<K, V>) node;
LinkedNode<K, V> prevNode = linkedNode.last;
LinkedNode<K, V> nextNode = linkedNode.next;
if (prevNode == null) {
head = nextNode;
} else {
prevNode.next = nextNode;
}
if (nextNode == null) {
tail = prevNode;
} else {
nextNode.last = prevNode;
}
}
/**
* 度为2结点和前驱或后继结点交换在链表中的位置
*
* @param n1 度为2的结点
* @param n2 它的前驱或后继结点
*/
@Override
protected void exchangeNodes(Node<K, V> n1, Node<K, V> n2) {
LinkedNode<K, V> temp = null;
LinkedNode<K, V> node1 = (LinkedNode<K, V>) n1;
LinkedNode<K, V> node2 = (LinkedNode<K, V>) n2;
//交换两个结点的prev指针
temp = node1.last;
node1.last = node2.last;
node2.last = temp;
if (node1.last == null) { //交换后的处理
head = node1;
} else {
node1.last.next = node1;
}
if (node2.last == null) {
head = node2;
} else {
node2.last.next = node2;
}
//交换两个结点的next指针
temp = node1.next;
node1.next = node2.next;
node2.next = temp;
if (node1.next == null) { //交换后的处理
tail = node1;
} else {
node1.next.last = node1;
}
if (node2.next == null) {
tail = node2;
} else {
node2.next.last = node2;
}
}
@Override
public void clear() {
super.clear();
head = null;
tail = null;
}
/**
* 从双向链表中遍历所有结点
*
* @param visitor
*/
@Override
public void traversal(Visitor<K, V> visitor) {
LinkedNode<K, V> node = head;
while (node != null) {
visitor.visit(node.key, node.value);
node = node.next;
}
}
@Override
public boolean containsValue(V value) {
LinkedNode<K, V> node = head;
while (node != null) {
if (Objects.equals(node.value, value))
return true;
node = node.next;
}
return false;
}
}