开始时间2020-11-18
HashSet
无序
package BUPT20201118;
import java.util.HashSet;
import java.util.Set;
public class HashSetTest {
public static void main(String[] args) {
Set<String> stringSet = new HashSet<>();
stringSet.add("hellow");
stringSet.add("world");
stringSet.add("Java");
stringSet.add("JDK15");
for (String s : stringSet) {
System.out.println(s);
}
}
}
TreeSet
package BUPT20201118;
import java.util.Set;
import java.util.TreeSet;
/*
无序不可重复,但给进去的数据会根据大小排序
*/
public class TreeSetTest01 {
public static void main(String[] args) {
Set<String> stringSet = new TreeSet<>();
stringSet.add("abc");
stringSet.add("ABC");
stringSet.add("EFG");
stringSet.add("efg");
for (String s : stringSet) {
System.out.println(s);
}
}
}
Map
类似于字典
所有的Key是一个Set集合
先复习一下静态内部类
package BUPT20201118;
import java.util.HashSet;
import java.util.Set;
public class MyClass {
private static class InnerClass {
public static void m1() {
System.out.println("静态内部类的m1方法执行");
}
public void m2() {
System.out.println("静态方法的内部实例类方法执行");
}
}
public static void main(String[] args) {
//类名是 MyClass.InnerClass
MyClass.InnerClass.m1();
//外部类.内部类.方法
MyClass.InnerClass myClass = new MyClass.InnerClass();
myClass.m2();
Set<MyClass.InnerClass> set = new HashSet<>();
//上面和下面都是类似的,泛型装的都是类,没有本质不同
//上面的MyClass可以省略
Set<String> stringSet = new HashSet<>();
Set<MyMap.MyEntry<Integer, String>> set1 = new HashSet<>();
}
}
class MyMap {
public static class MyEntry<K, V> {
}
}
package BUPT20201118;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/*
Map以Key和Value存东西,有点像字典
Map和Collection没有继承关系
Map中常用方法:
V put(K key,V value) 向Map增加键值对
void clear() 清空Map
boolean containsKey(Object key)判断是否包含某个key
*/
public class MapTest01 {
public static void main(String[] args) {
/*
Map集合通过entryset方法转换成的这个Set集合,Set集合中元素的类型是Map.Entry<K,V>
Map.Entry是静态内部类,都是一种类型的名字
*/
//创建Map集合对象
Map<Integer, String> map = new HashMap<>();
map.put(1, "锦到黑");
map.put(2, "jindaohei");
map.put(3, "JDH");
map.put(4, "HDJ");
//通过Key来获得Value
System.out.println(map.get(2));
//获取键值对数量
System.out.println("键值对数量为" + map.size());
//通过Key来删除键值对
map.remove(3);
System.out.println("键值对数量为" + map.size());
//判断是否包括某个Key
System.out.println(map.containsKey(4));
//判断是否包括某个Value
System.out.println(map.containsValue("锦到黑"));
//底层都重写了equals的,放心比较值
//之前给定了value的泛型为String,所以这里自动识别出来
Collection<String> values = map.values();
for (String s : values) {
System.out.println(s);
}
//清空map
map.clear();
//判断是否为空
System.out.println(map.isEmpty());
}
}
package BUPT20201118;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class MapTest02 {
public static void main(String[] args) {
//第一种方式,获取所有的Key,通过遍历Key,来遍历Value
Map<Integer, String> stringMap = new HashMap<>();
stringMap.put(1, "锦到黑");
stringMap.put(2, "jindaohei");
stringMap.put(3, "JDH");
stringMap.put(4, "HDJ");
Set<Integer> keys = stringMap.keySet();
Iterator<Integer> integerIterator = keys.iterator();
while (integerIterator.hasNext()) {
Integer key = integerIterator.next();
String value = stringMap.get(key);
System.out.println("键值对为" + key + ":" + value);
}
//不用迭代器
for (Integer key : keys) {
System.out.println(key + "=" + stringMap.get(key));
}
//第二种方法:Set<Map,Entry<K,V>> entrySet()
//以上把Map集合全部转为Set集合
//Set里面装的是Map.Entry,相当于装了一个键值对,这种效率较高
Set<Map.Entry<Integer, String>> set = stringMap.entrySet();//逼历set集合,每一次取出一个Node
// 迭代器
Iterator<Map.Entry<Integer, String>> it2 = set.iterator();
while (it2.hasNext()) {
Map.Entry<Integer, String> node = it2.next();
Integer key = node.getKey();
String value = node.getValue();
System.out.println(key + "=" + value);
System.out.println(node);
//直接输出也行:System.out.println(node);
}
}
}
散列表/哈希表
哈希表/散列表:―维数组,这个数组中每一个元素是一个单向链表。(数组和链表的结合体。
这一块是数据结构的概念,贴出之前大二学的时候的课堂笔记
数组:查询效率高,随机增删效率低
单向链表:随机增删效率低,查询效率高
哈希表结合二者
alt+ctrl+鼠标 可以跳转源码
HashMap底层是一个一维数组,存的是Node
Node类型存了hash值,key值,value值,下一个Node地址
Hash值是HashCode的执行结果,
Hash值通过哈希函数/算法可以转为数组下标
往里放:
map.put(k,v)实现原理:
第一步:先将k,v封装到Node对象当中。
第二步:底层会调用k的hashCode0方法得出hash值,
然后通过哈希函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上了。如果说下标对应的位置上有链表,此时会拿着k和链表上每一个节点中的k进行equals ,如果所有的equals方法返回都是false,那么这个新节点将会被添加到链表的末尾。如果其中有一个equals返回了true,那么这个节点的value将会被覆盖。
往外取:
v = map.get(k)实现原理:
先调用k的hashCode()方法得出哈希值,通过哈希算法转换成数组下标,通过数组下标快速定位到某个位置上,如果这个位置上什么也没有,返回null。如果这个位置上有单向链表,那么会拿着参数k和单向链表上的每个节点中的k进行equals ,如果所有equals方法返回false,那么get方法返回null,只要其中有一个节点的k和参数k equals的时候返回true,那么此时这个节点的value就是我们要找的value , get方法最终返回这个要找的value.
通过这个匹配机制,能缩小查找的次数和范围
Hash表随机增删和查询效率都高,因为增删在链表上,查询是部分扫描。
但是效率都比不上纯链表/数组
要得到HashMap集合中的key,那么HashCode和equals都要重写
Hash集合中的key无序不可重复,加入的顺序和输出的不一定一样
equals保证了HashMap集合的Key不可重复,如果重复了,value会覆盖
而HashMap的key放在了HashSet里面,所以HashSet里面的equals和HashCode也需要重写
同一个单向链表上所有节点的Hash相同,因为数组下标相同
但同一链表上k和k的equals方法不同,
如果所有HashCode返回值都固定为某个值或者不一样的值,那么会导致底层哈希表变成了纯单向链表或纯数组,这种情况称为散列分布不均匀
100个元素,10个单向链表,10个节点,那么分布很均匀
package BUPT20201121;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class HashMapTest01 {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(111, "张三");
map.put(12, "wangwu");
map.put(21, "李四");
map.put(21, "lisi");
//key重复,value覆盖,所以输出为3
System.out.println(map.size());
Set<Map.Entry<Integer, String>> set = map.entrySet();
for (Map.Entry<Integer, String> entry : set) {
System.out.println(entry.getKey() + "====" + entry.getValue());
//取出来顺序不固定,李四被lisi覆盖掉了
//HashMap的Key部分,无序不可重复
}
}
}
3
21====lisi
12====wangwu
111====张三
HashMap默认初始化容量为16,初始化容量必须是2的倍数,可以提高存取效率。默认加载因子为0.75
意思是当容量用掉75%时,数组开始扩容
同时重写equals和HashCode
只重写了equals
package BUPT20201121;
import java.util.Objects;
public class Student {
private String name;
public Student() {
}
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@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(name, student.name);
}
/*
@Override
public int hashCode() {
return Objects.hash(name);
}
*/
}
package BUPT20201121;
import java.util.HashSet;
import java.util.Set;
public class HashMapTest02 {
public static void main(String[] args) {
Student student = new Student("jindaohei");
Student student1 = new Student("jindaohei");
//这里重写了equals,所以返回了true
System.out.println(student.equals(student1));
//按理说,这两个都视为相同的了,但是HashCode又不同
System.out.println(student.hashCode());
System.out.println(student1.hashCode());
//本来相同的会覆盖掉,现在又不覆盖了,就不太科学
Set<Student> students = new HashSet<>();
students.add(student);
students.add(student1);
System.out.println(students.size());
}
}
输出
true
2129789493
668386784
2
这样不科学,把上面Student放开重写的HashCode
true
1791910440
1791910440
1
equals重写了,HashCode也必须重写,且两者的返回值一定要一致、
放在HashMap集合key部分的,以及放在HashSet 集合中的元素,需要同时重写hashCode方法和equals方法。
在JDK8之后,如果Hash表,单向链表的元素>=8,那么该单向链表会变成红黑树,当红黑树上的节点<6时,红黑树会变回单向链表。为了提高效率,二叉树检索会再次缩小检索范围,初始化容量16,默认加载因子0.75,扩容后容量为原容量2倍
对于哈希表数据结构来说:
如果o1和o2的hash值相同,一定是放到同一个单向链表上。
当然如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。
这个概念可以通过看我之前贴的数据结构笔记得出
结束时间2020-11-21