前言
在之前我们介绍过TreeMap和TreeSet:
TreeMap+TreeSet 知识点梳理总结_Crystal_bit的博客-CSDN博客
也知道Key-Value和Key模型,但是我们可能还对Hash不太了解,这里我们对Hash了解之后再对HashMap和HashSet的基本使用了解一下。
目录
1. 哈希表的概念
2. 哈希冲突是什么?如何避免?解决方式?
3. HashMap和HashSet是什么?它们的使用?
1. 哈希表的概念
哈希表(实际上就是一个数组)是可以不用比较,一次就能搜索到待查找元素的一种存储结构。在哈希表中,通过哈希函数让元素存储位置与其关键码之间建立映射关系,那么我们就可以通过哈希函数+某个元素的关键码计算出元素存储位置,从而找到该元素。如图:
2. 哈希冲突是什么?如何避免?解决方式?
哈希冲突就是不同的关键码通过同一个哈希函数映射出同一个存储位置,就比如某个商品搞特价活动的时候,两个甚至是多个人对同一商品进行争夺,这就会发生冲突。对此我们需要一个好的哈希函数去尽可能的避免哈希冲突,一个好的哈希函数应该能让计算出来的地址均匀分布在哈希表中且不能太复杂。常见的哈希函数有直接定址法,除留余数法等。
针对冲突我们有两种解决方式,一种是闭散列法(线性探测+平方探测),一种是开散列法。
闭散列法:发生冲突时,如果哈希表还未满,就把待插入元素放到冲突位置的某个空位置去。但是这个空位置有两种确定方式,一种是线性探测,类似于有人占了我的座位,我就跑它旁边去坐着,如果它旁边也有人了,我就去旁边的下一个,总会找到一个空位置(哈希表未满)。如图:
但是大家可能会发现,线性探测会将发生冲突的元素堆在一起,所有为了解决这个问题,我们可以使用平方探测法——下一个空位置 = (发生冲突位置 + 或 - i (第 i 次冲突)的平方) % 哈希表大小。如图:
注:使用线性探测法存放元素,不能直接删除某个元素,不然就会导致元素查找有误,明明待查找元素存在于哈希表中,但是由于映射位置的元素已经被删除,所以就会被判定待查找元素不存在所以只能采用标记法删除哈希表中元素。
在Java中负载因子(已有元素个数 / 哈希表长度)默认0.75,它越小冲突率越小。负载因子超过0.75则只能对哈希表进行扩容去降低负载因子大小。大家可以看出使用闭散列方法往哈希表中存放元素,会导致空间利用率较低,比如十个位置,如果负载因子为0.75,那么我们放8个元素的时候,哈希表就需要扩容了。所以我们可以使用另一种方法——开散列方法 去解决冲突问题。
开散列法(链地址法):由数组+链表+红黑树组成一个哈希桶,数组每个位置存储一个哈希表,该哈希表中存的是在该数组下标发生冲突的元素,如图:
3. HashMap和HashSet是什么?它们的使用?
HashMap和HashSet是Java利用哈希表实现的Map和Set。
● HashMap的常用方法及使用示例
public class Test {
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>(); // 初始化一个空的HashMap
map.put("test",1); // 添加键值对
map.put("x",2);
int test = map.get("test"); //获取指定键的对应值
int x = map.get("x");
System.out.println(test + " " + x);
//获取指定键的对应值,如果map中没有指定键,则返回默认值-1
System.out.println(map.getOrDefault("find", -1));
System.out.println(map.isEmpty()); //判断map内容是否为空
Set<Map.Entry<String,Integer>> keys = map.entrySet(); // 获取所有的键对值
for (Map.Entry<String,Integer> item:keys) {
System.out.println(item.getKey() + ":" + item.getValue());
}
map.remove("test"); //删除指定键值对
System.out.println(map.get("test"));
System.out.println(map.size()); //map中的键值对数量
map.clear(); //清空map的所有内容
}
}
运行结果:
● HashSet的常用方法及使用示例
public class Test {
public static void main(String[] args) {
Set<String> hashSet = new HashSet<>(); // 初始化一个空的HashSet
hashSet.add("abc"); //往集合中添加元素
hashSet.add("dfe");
hashSet.add("add");
System.out.println(hashSet.contains("add")); //集合中是否包含指定元素
System.out.println(hashSet.isEmpty()); //集合是否为空
hashSet.remove("add"); //移除指定元素
Iterator<String> iterator = hashSet.iterator(); //返回集合中元素的迭代器
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
hashSet.clear(); //清空集合中所有内容
System.out.println(hashSet.size()); //获取集合中元素个数
}
}
运行结果:
注:Java中使用哈希桶方式解决冲突,所以其底层是数组+链表+红黑树的结构。当链表长度>= 8 && 数组长度 >= 64此时哈希表就会变成红黑树。
分享完毕~