以下是学习恋上数据结构与算法的记录要内容是哈希表
◼哈希表(Hash Table)
由于TreeMap特点:●Key 必须具备可比较性●元素的分布是有顺序的
在实际应用中,很多时候的需求,Map 中存储的元素不需要讲究顺序,Map 中的Key 不需要具备可比较性,不考虑顺序、不考虑Key 的可比较性,Map 有更好的实现方案,平均时间复杂度可以达到O(1),那就是采取哈希表来实现Map。
◼哈希表也叫做散列表(hash 有“剁碎”的意思)
添加、搜索、删除的流程都是类似的:
1.利用哈希函数生成key 对应的index【O(1)】
2.根据index 操作定位数组元素【O(1)】
●哈希表是【空间换时间】的典型应用●哈希函数,也叫做散列函数
●哈希表内部的数组元素,很多地方也叫Bucket(桶),整个数组叫Buckets 或者Bucket Array
◼哈希冲突(Hash Collision)
哈希冲突也叫做哈希碰撞:2 个不同的key,经过哈希函数计算出相同的结果,key1 ≠ key2 ,hash(key1) =hash(key2)
◼解决哈希冲突的常见方法
1.开放定址法(Open Addressing),按照一定规则向其他地址探测,直到遇到空桶
2.再哈希法(Re-Hashing),设计多个哈希函数
3.链地址法(Separate Chaining),比如通过链表将同一index的元素串起来
◼JDK1.8的哈希冲突解决方案
●默认使用单向链表将元素串起来
●在添加元素时,可能会由单向链表转为红黑树来存储元素,比如当哈希表容量≥ 64 且单向链表的节点数量大于8 时
●当红黑树节点数量少到一定程度时,又会转为单向链表
◼JDK1.8中的哈希表是使用链表+红黑树解决哈希冲突,这里为什么使用单链表?因为每次都是从头节点开始遍历,而单向链表比双向链表少一个指针,可以节省内存空间
◼哈希函数:
●良好的哈希函数,会让哈希值更加均匀分布→减少哈希冲突次数→提升哈希表的性能
●哈希表中哈希函数的实现步骤大概如下
1.先生成key 的哈希值(必须是整数并且可以进一步处理,扰动计算)
2.再让key 的哈希值跟数组的大小进行相关运算,生成一个索引值
●为了提高效率,可以使用& 位运算取代% 运算【前提:将数组的长度设计为2 的幂(2n)】
private int hash(K key) {//哈希值
if(key==null) return 0;
int hash = key.hashCode(); //该hashCode方法为JDK计算哈希值的方法
return hash ^ (hash >>> 16);//扰动计算:hash ^ (hash >>> 16)
}
private int index(K key) {//索引值
return hash(key) & (table.length-1);
}
◼如何生成key的哈希值
●key 的常见种类可能有:整数、浮点数、字符串、自定义对象,不同种类的key,哈希值的生成方式不一样,但目标是一致的
✓尽量让每个key 的哈希值是唯一的✓尽量让key 的所有信息参与运算
●在Java中,HashMap 的key 必须实现hashCode、equals 方法,也允许key 为null
◼整数
整数值当做哈希值:比如10 的哈希值就是10
◼浮点数
将存储的二进制格式转为整数值
◼Long和Double的哈希值
●>>> 和^ 的作用是?
高32bit 和低32bit 混合计算出32bit 的哈希值,充分利用所有信息计算出哈希值
◼字符串的哈希值
●字符串是由若干个字符组成的:比如字符串jack,由j、a、c、k 四个字符组成(字符的本质就是一个整数),因此,jack 的哈希值可以表示为j∗n3+a∗n2+c∗n1+k∗n0,等价于[(j∗n+a)∗n+c]∗n+k
◼关于31的探讨
●31 * i= (2^5 –1) * i= i * 2^5 –i = (i << 5) –i
●31不仅仅是符合2^n –1,它是个奇素数(既是奇数,又是素数,也就是质数)
素数和其他数相乘的结果比其他方式更容易产成唯一性,减少哈希冲突,最终选择31是经过观测分布结果后的选择
◼自定义对象的哈希值
◼自定义对象作为key,最好同时重写hashCode 、equals 方法
●equals :用以判断2 个key 是否为同一个key
✓自反性:对于任何非null 的x,x.equals(x)必须返回true
✓对称性:对于任何非null 的x、y,如果y.equals(x) 返回true,x.equals(y) 必须返回true
✓传递性:对于任何非null 的x、y、z,如果x.equals(y)、y.equals(z) 返回true,那么x.equals(z) 必须返回true
✓一致性:对于任何非null 的x、y,只要equals 的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y) 就会一致地返回true,或者一致地返回false
✓对于任何非null 的x,x.equals(null) 必须返回false。
●hashCode :必须保证equals 为true的2 个key 的哈希值一样,
反过来hashCode 相等的key,不一定equals 为true。
●不重写hashCode 方法只重写equals 会有什么后果?
✓可能会导致2 个equals 为true 的key 同时存在哈希表中
◼装填因子(Load Factor):
●装填因子计算:节点总数量/ 哈希表桶数组长度,也叫做负载因子
●在JDK1.8的HashMap中,如果装填因子超过0.75,就扩容为原来的2倍
◼TreeMapvs HashMap
●何时选择TreeMap?元素具备可比较性且要求升序遍历(按照元素从小到大)
●何时选择HashMap?无序遍历
红黑树实现哈希表,不采用单向链表
package com.bj.map;
import java.util.LinkedList;
import java.util.Objects;
import java.util.Queue;
@SuppressWarnings({"unchecked", "rawtypes"})
public class HashMap<K, V> implements Map<K, V> {
private static final boolean RED=false;
private static final boolean BLACK=true;
private int size;
private Node<K,V> [] table;
private static final int DEFAULT_CAPACITY=1<<4;//16
//装填因子
private static final float DEFAULT_LOAO_FACTOR=0.75f;
public HashMap() {
table=new Node[DEFAULT_CAPACITY];
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public void clear() {
if(size==0) return;
size=0;
for(int i = 0;i<table.length;i++) {
table[i] = null;//把存储索引的数组清空即可
}
}
public V put(K key, V value) {
resize();
int index = index(key);//索引位置
// 取出index位置的红黑树根节点
Node<K, V> root = table[index];
if(root ==null) {
root = new Node<>(key, value, null);
table[index] = root;
size++;
fixAfterPut(root);
return null;
}
// 添加新的节点到红黑树上面
Node<K, V> parent = root;
Node<K, V> node = root;
int cmp =0;
K k1 = key;
int h1 = k1==null?0:k1.hashCode();
Node<K, V> result= null;
boolean searched = false;// 是否已经搜索过这个key
do {
parent = node;//记录父节点
K k2 = node.key;
int h2 = k2.hashCode();
if (h1 > h2) {
cmp = 1;
} else if (h1 < h2) {
cmp = -1;
} else if (Objects.equals(k1, k2)) {
cmp = 0;
} else if (k1 != null && k2 != null
&& k1.getClass() == k2.getClass()
&& k1 instanceof Comparable
&& (cmp = ((Comparable) k1).compareTo(k2)) != 0) {
}else if(searched) { // 已经扫描了
cmp = System.identityHashCode(k1) - System.identityHashCode(k2);//内存地址计算大小
}else { // searched == false; 还没有扫描,然后再根据内存地址大小决定左右
if((node.left!=null &&(result = node(node.left,k1))!=null) ||
(node.right != null && (result = node(node.right, k1)) != null)) {
node=result;
cmp=0;
}else {//不存在这个key
searched = true;
cmp = System.identityHashCode(k1) - System.identityHashCode(k2);
}
}
if(cmp>0) {
node=node.right;
}else if(cmp<0) {
node=node.left;
}else {//0 相等覆盖
V oldValue= node.value;//记录旧值
node.key=key;
node.value=value;
node.hash=h1;
return oldValue;
}
}while(node!=null);
// 看看插入到父节点的哪个位置
Node<K, V> newNode = new Node<>(key, value, parent);//创建新节点
if (cmp > 0) {
parent.right = newNode;
} else {
parent.left = newNode;
}
size++;
// 新添加节点之后的处理
fixAfterPut(newNode);
return null;
}
public V get(K key) {
Node<K, V> node = node(key);
return node!=null?node.value:null;
}
public V remove(K key) {
return remove(node(key));
}
public boolean containsKey(K key) {
return node(key) != null;
}
public boolean containsValue(V value) {
if(size==0) return false;
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.poll();
if(Objects.equals(value, node.value)) return true;
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
}
return false;
}
public void traversal(Visitor<K, V> visitor) {
if (size == 0 || visitor == null) return;
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.poll();
if (visitor.visit(node.key, node.value)) return;
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
}
}
private void resize() {
// 装填因子 <= 0.75,,节点总数量/ 哈希表桶数组长度,也叫做负载因子,如果装填因子超过0.75,就扩容为原来的2倍
if(size/table.length<=DEFAULT_LOAO_FACTOR) return;
Node<K, V>[] oldTable = table;
table=new Node[oldTable.length<<1];//扩容为原来的2倍
Queue<Node<K, V>> queue = new LinkedList<>();
for(int i = 0;i<oldTable.length;i++) {
if(oldTable[i]==null) continue;
queue.offer(oldTable[i]);
while(!queue.isEmpty()) {
Node<K, V> node = queue.poll();
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
// 挪动代码得放到最后面
moveNode(node);
}
}
}
private void moveNode(Node<K, V> newNode) {
// 重置
newNode.parent = null;
newNode.left = null;
newNode.right = null;
newNode.color = RED;
int index = index(newNode);
// 取出index位置的红黑树根节点
Node<K, V> root = table[index];
if (root == null) {
root = newNode;
table[index] = root;
fixAfterPut(root);
return;
}
// 添加新的节点到红黑树上面
Node<K, V> parent = root;
Node<K, V> node = root;
int cmp = 0;
K k1 = newNode.key;
int h1 = newNode.hash;
do {
parent = node;
K k2 = node.key;
int h2 = node.hash;
if (h1 > h2) {
cmp = 1;
} else if (h1 < h2) {
cmp = -1;
} else if (k1 != null && k2 != null
&& k1 instanceof Comparable
&& k1.getClass() == k2.getClass()
&& (cmp = ((Comparable)k1).compareTo(k2)) != 0) {
} else {
cmp = System.identityHashCode(k1) - System.identityHashCode(k2);
}
if (cmp > 0) {
node = node.right;
} else if (cmp < 0) {
node = node.left;
}
} while (node != null);
// 看看插入到父节点的哪个位置
newNode.parent = parent;
if (cmp > 0) {
parent.right = newNode;
} else {
parent.left = newNode;
}
// 新添加节点之后的处理
fixAfterPut(newNode);
}
private Node<K, V> node(K key){
Node<K, V> root = table[index(key)];
return root == null?null:node(root,key);
}
private Node<K, V> node(Node<K, V> node, K k1) {
int h1 = hash(k1);
//存储查找结果
Node<K, V> result = null;
int cmp = 0;
while (node!=null) {
K k2 = node.key;
int h2 = node.hash;
//先比较哈希值
if(h1>h2) {
node=node.right;
}else if(h1<h2) {
node=node.left;
}else if(Objects.equals(k1, k2)) {//比较equals
return node;
}else if(k1 !=null && k2!=null
&& k1.getClass()==k2.getClass()//同一种类型并且具备可比较性
&& k1 instanceof Comparable
&& (cmp = ((Comparable) k1).compareTo(k2)) != 0) {
node=cmp>0?node.right:node.left;
}else if(node.right!=null && (result = node(node.right,k1))!=null) {//扫描右子树
return result;
}else {
// 只能往左边找
node = node.left;
}
// } else if (node.left != null && (result = node(node.left, k1)) != null) {
// return result;
// } else {
// return null;
// }
}
return null;
}
/**哈希函数实现步骤:一先生成Key的哈希值(必须是整数),二再让key的哈希值跟数组的大小进行相关运算,生成索引值
* 根据key生成对应的索引(在桶数组中的位置)
* (hash ^ (hash >>> 16))为扰动计算
* & 位运算,取代%运算(前提是数组长度为2的幕2^n),
*/
private int hash(K key) {
if(key==null) return 0;
int hash = key.hashCode();
return hash ^ (hash >>> 16);
}
private int index(K key) {
return hash(key) & (table.length-1);
}
private int index(Node<K, V> node) {
return node.hash & (table.length-1);
}
private V remove(Node<K, V> node) {
if (node == null) return null;
size--;
V oldValue = node.value;
if (node.hasTwoChildren()) { // 度为2的节点
// 找到后继节点
Node<K, V> s = successor(node);
// 用后继节点的值覆盖度为2的节点的值
node.key = s.key;
node.value = s.value;
node.hash = s.hash;
// 删除后继节点
node = s;
}
// 删除node节点(node的度必然是1或者0)
Node<K, V> replacement = node.left != null ? node.left : node.right;
int index = index(node);
if (replacement != null) { // node是度为1的节点
// 更改parent
replacement.parent = node.parent;
// 更改parent的left、right的指向
if (node.parent == null) { // node是度为1的节点并且是根节点
table[index] = replacement;
} else if (node == node.parent.left) {
node.parent.left = replacement;
} else { // node == node.parent.right
node.parent.right = replacement;
}
// 删除节点之后的处理
fixAfterRemove(replacement);
} else if (node.parent == null) { // node是叶子节点并且是根节点
table[index] = null;
} else { // node是叶子节点,但不是根节点
if (node == node.parent.left) {
node.parent.left = null;
} else { // node == node.parent.right
node.parent.right = null;
}
// 删除节点之后的处理
fixAfterRemove(node);
}
return oldValue;
}
private Node<K, V> successor(Node<K, V> node) {
if (node == null) return null;
// 前驱节点在左子树当中(right.left.left.left....)
Node<K, V> p = node.right;
if (p != null) {
while (p.left != null) {
p = p.left;
}
return p;
}
// 从父节点、祖父节点中寻找前驱节点
while (node.parent != null && node == node.parent.right) {
node = node.parent;
}
return node.parent;
}
private void fixAfterRemove(Node<K, V> node) {
// 如果删除的节点是红色
// 或者 用以取代删除节点的子节点是红色
if (isRed(node)) {
black(node);
return;
}
Node<K, V> parent = node.parent;
if (parent == null) return;
// 删除的是黑色叶子节点【下溢】
// 判断被删除的node是左还是右
boolean left = parent.left == null || node.isLeftChild();
Node<K, V> sibling = left ? parent.right : parent.left;
if (left) { // 被删除的节点在左边,兄弟节点在右边
if (isRed(sibling)) { // 兄弟节点是红色
black(sibling);
red(parent);
rotateLeft(parent);
// 更换兄弟
sibling = parent.right;
}
// 兄弟节点必然是黑色
if (isBlack(sibling.left) && isBlack(sibling.right)) {
// 兄弟节点没有1个红色子节点,父节点要向下跟兄弟节点合并
boolean parentBlack = isBlack(parent);
black(parent);
red(sibling);
if (parentBlack) {
fixAfterRemove(parent);
}
} else { // 兄弟节点至少有1个红色子节点,向兄弟节点借元素
// 兄弟节点的左边是黑色,兄弟要先旋转
if (isBlack(sibling.right)) {
rotateRight(sibling);
sibling = parent.right;
}
color(sibling, colorOf(parent));
black(sibling.right);
black(parent);
rotateLeft(parent);
}
} else { // 被删除的节点在右边,兄弟节点在左边
if (isRed(sibling)) { // 兄弟节点是红色
black(sibling);
red(parent);
rotateRight(parent);
// 更换兄弟
sibling = parent.left;
}
// 兄弟节点必然是黑色
if (isBlack(sibling.left) && isBlack(sibling.right)) {
// 兄弟节点没有1个红色子节点,父节点要向下跟兄弟节点合并
boolean parentBlack = isBlack(parent);
black(parent);
red(sibling);
if (parentBlack) {
fixAfterRemove(parent);
}
} else { // 兄弟节点至少有1个红色子节点,向兄弟节点借元素
// 兄弟节点的左边是黑色,兄弟要先旋转
if (isBlack(sibling.left)) {
rotateLeft(sibling);
sibling = parent.left;
}
color(sibling, colorOf(parent));
black(sibling.left);
black(parent);
rotateRight(parent);
}
}
}
private void fixAfterPut (Node<K, V> node) {
Node<K, V> parent = node.parent;
// 添加的是根节点 或者 上溢到达了根节点
if (parent == null) {
black(node);
return;
}
// 如果父节点是黑色,直接返回
if (isBlack(parent)) return;
// 叔父节点
Node<K, V> uncle = parent.sibling();
// 祖父节点
Node<K, V> grand = red(parent.parent);
if (isRed(uncle)) { // 叔父节点是红色【B树节点上溢】
black(parent);
black(uncle);
// 把祖父节点当做是新添加的节点
fixAfterPut(grand);
return;
}
// 叔父节点不是红色
if (parent.isLeftChild()) { // L
if (node.isLeftChild()) { // LL
black(parent);
} else { // LR
black(node);
rotateLeft(parent);
}
rotateRight(grand);
} else { // R
if (node.isLeftChild()) { // RL
black(node);
rotateRight(parent);
} else { // RR
black(parent);
}
rotateLeft(grand);
}
}
private void rotateLeft(Node<K, V> grand) {
Node<K, V> parent = grand.right;
Node<K, V> child = parent.left;
grand.right = child;
parent.left = grand;
afterRotate(grand, parent, child);
}
private void rotateRight(Node<K, V> grand) {
Node<K, V> parent = grand.left;
Node<K, V> child = parent.right;
grand.left = child;
parent.right = grand;
afterRotate(grand, parent, child);
}
private void afterRotate(Node<K, V> grand, Node<K, V> parent, Node<K, V> child) {
// 让parent称为子树的根节点
parent.parent = grand.parent;
if (grand.isLeftChild()) {
grand.parent.left = parent;
} else if (grand.isRightChild()) {
grand.parent.right = parent;
} else { // grand是root节点
table[index(grand)] = parent;
}
// 更新child的parent
if (child != null) {
child.parent = grand;
}
// 更新grand的parent
grand.parent = parent;
}
private Node<K, V> color(Node<K, V> node, boolean color) {
if (node == null) return node;
node.color = color;
return node;
}
private Node<K, V> red(Node<K, V> node) {
return color(node, RED);
}
private Node<K, V> black(Node<K, V> node) {
return color(node, BLACK);
}
private boolean colorOf(Node<K, V> node) {
return node == null ? BLACK : node.color;
}
private boolean isBlack(Node<K, V> node) {
return colorOf(node) == BLACK;
}
private boolean isRed(Node<K, V> node) {
return colorOf(node) == RED;
}
protected static class Node<K,V>{
int hash;
K key;
V value;
boolean 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;
int hash = key == null ? 0 : key.hashCode();
this.hash = hash ^ (hash >>> 16);//提前生死哈希值,减少其他地方的计算次数
this.value = value;
this.parent = parent;
}
public boolean hasTwoChildren() {
return left != null && right != null;
}
public boolean isLeftChild() {
return parent != null && this == parent.left;
}
public boolean isRightChild() {
return parent != null && this == parent.right;
}
public Node<K, V> sibling() {
if (isLeftChild()) {
return parent.right;
}
if (isRightChild()) {
return parent.left;
}
return null;
}
@Override
public String toString() {
return "Node_" + key + "_" + value;
}
}
}