1.和Java的关系
- HashMap 和 HashSet 即 java 中利用哈希表实现的 Map 和 Set.
- java 会在冲突链表长度大于一定阈值后,将链表转变为搜索树(红黑树.
- java 中使用的是哈希桶方式解决冲突的.
- java 中计算哈希值实际上是调用的类的 hashCode 方法,进行 key 的相等性比较是调用 key 的 equals 方法。所以如果要用自定义类作为 HashMap 的 key 或者 HashSet 的值,必须覆写 hashCode 和 equals 方法,而且要做到 equals 相等的对象,hashCode 一定是一致的。
2.相关代码案例
public class HashBuck {
//此内部类描述哈希桶的节点
static class Node {
public int key;
public int val;
public Node next;
public Node(int key, int val) {
this.key = key;
this.val = val;
}
}
public Node[] array;
public int usedSize;
// 定义一个默认的负载因子为0.75
public static final double LOAD_FACTER = 0.75f;
public HashBuck( ) {
array = new Node[10];
}
public void put(int key, int val) {
/**
* 给定一个 key 用key 去模数组的长度 得到一个值给到index
* index为 数组的下标 数组里的每一个元素又分为一个链表
* 每个链表有三个节点 key val 和 next
*/
int index = key % array.length;
Node cur = array[index];
/**
* cur为链表头节点 如果这个链表不为空
* 如果给定的 key 等于 cur的key 就把val 给到cur的val 如果不等于 cur 往后走
* 如果在 cur链表中找不到给定的key 就把给定的 key val 采用头插法或尾插法 此处用头插法
*/
while(cur != null) {
if(cur.key == key) {
cur.val = val;
return;
}
cur = cur.next;
}
/**
* cur走到空了 说明当前链表中没有给定的key val 就头插
* 头插 先新建一个节点node 先该节点(node)的next指向原来链表的头结点 再将该节点放在array[index]充当头节点
* 有效长度加1 (插入一个usedsize++)
*/
Node node = new Node(key,val);
node.next = array[index];
array[index] = node;
usedSize++;
/**
* usedSize++ 后要判断此时的负载因子是否大于默认的负载因子
* 如果大于就要扩容了
*/
if(calculateLoadFactor() >= LOAD_FACTER ) {
// 扩容 通过resize方法扩容
resize();
}
}
/**
* 扩容一定要注意
* 每一个元素都要进行重新的哈希计算
* 因为它们可能不在原来的位置了
* 链表长度> = 8 && 数组长度>= 64 此时会变成红黑树(以后在看)
*/
private void resize() {
// 新的数组是旧的数组的2倍
Node[] newArray = new Node[2* array.length];
// 因为array数组的每一个下标都是一个链表
// 要想遍历每一个元素 先遍历数组 通过cur来指向每一个链表 通过while循环来遍历链表 然后全都放到newArray中
for(int i = 0;i< array.length;i++) {
Node cur = array[i];
while(cur != null) {
Node curNext = cur.next; //用curNext来记录下来cur.next
int index = cur.key % newArray.length;// 找到cur 的关键字在新数组的位置
cur.next = newArray[index]; // 因为cur指代的是链表 数组变化了 cur.next也要指向新的位置 头插
newArray[index] = cur; //头插
cur = curNext;//cur往后走 遍历链表的后一个...
}
}
// 链表 和数组改完后将新的数组给到array array就导表newArray
array = newArray;
}
// 计算负载因子
private double calculateLoadFactor() {
return usedSize*1.0 / array.length;
}
// get方法来获取到 key 对应的val值
public int get(int key) {
int index = key % array.length;
Node cur = array[index];
while(cur != null) {
if(cur.key == key) {
return cur.val;
}
cur = cur.next;
}
return -1;
}
}
import java.util.*;
/**
* Created with IntelliJ IDEA.
* Description:
* User: LIUQIANG
* Date: 2023-05-09
* Time: 10:42
*/
class Student {
private String id;
public Student(String id) { //构造方法
this.id = id;
}
//要想hashCode在同一个(例如"1234")下生成的哈希码一样 要以自己的方式去重写hashCode equals也一样
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(id, student.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString() { //toString方法
return "student{" +
"id='" + id + '\'' +
'}';
}
}
public class Test {
public static void main(String[] args) {
/**
* 统计10w个数据 求重复数据出现的次数 K-V模型
* 做法: 遍历数组: x -->map有 val+1 没有 val就是1
*/
int[] array = {1,2,2,3,4,5,6,8,4};
Map<Integer,Integer> map = new HashMap<>();
for (int x:array) {
if(map.get(x) == null) {
map.put(x,1);
} else {
int val = map.get(x);
map.put(x,val+1);
}
}
// map.entrySet() 是遍历map的一种方法
for (Map.Entry<Integer,Integer> entry: map.entrySet() ) {
if(entry.getValue() > 1) {
System.out.println("key: "+entry.getKey()+" val: "+entry.getValue() );
}
}
}
public static void main5(String[] args) {
int[] array = {1,2,2,3,4,5,6,8,4};
Set<Integer> set = new HashSet<>();
for (int x:array) {
//set用来去重
// 找到第一个重复数据,返回第一个重复元素
if(!set.contains(x)) {
set.add(x);
} else {
System.out.println(x);
return;
}
}
}
public static void main4(String[] args) {
int[] array = {1,2,2,3,4,5,6,8,4};
Set<Integer> set = new HashSet<>();
for (int x:array) {
//set用来去重
// 给定一组数据 并且数据有重复的 去重
set.add(x);
}
System.out.println(set);
}
public static void main3(String[] args) {
Student student1 = new Student("1234");
System.out.println(student1.hashCode());
Student student2 = new Student("1234");
System.out.println(student2.hashCode());
HashBuck2<Student,String> hashBuck2 = new HashBuck2<>();
hashBuck2.put(student1,"gaogao");
String val = hashBuck2.get(student2);
System.out.println(val);
}
public static void main2(String[] args) {
Student student = new Student("1234");
System.out.println(student.hashCode());
}
public static void main1(String[] args) {
HashBuck hashBuck = new HashBuck();
hashBuck.put(1,11);
hashBuck.put(2,22);
hashBuck.put(5,55);
hashBuck.put(8,88);
hashBuck.put(9,99);
hashBuck.put(14,33);
hashBuck.put(4,43);
hashBuck.put(6,25);
Integer val = hashBuck.get(51);
System.out.println(val);
}
}
虽然哈希表一直在和冲突做斗争,但在实际使用过程中,我们认为哈希表的冲突率是不高的,冲突个数是可控的, 也就是每个桶中的链表的长度是一个常数,所以,通常意义下,我们认为哈希表的插入/删除/查找时间复杂度是 O(1) 。