1-Set集合
TreeSet、HashSet、LinkedHashSet
TreeSet
:排序操作
HashSet
:保证元素唯一性
LinkedHashSet
:
1.1-HashSet
-
存取有序?
-
元素唯一,去重原因?
保证元素唯一的结论:
-
需要同时重写对象中的
hashCode
方法和equals
方法
在Student类中重写
hashCode
方法返回值为1;4个对象,equals被执行6次
HashSet<Student>hs = new HashSet<>(); hs.add(new Student(name:"张三",age:23)); hs.add(new Student(name:"李四",age:23)); hs.add(new Student(name:"王五",age:23)) hs.add(new Student(name:"王五",age:23));
注:HashSet底层的数据结构是哈希表结构
哈希表:
JDK8版本之前:数组+链表
JDK8版本之后:数组+链表|红黑树
1.1.1-hashCode
方法和equals
方法的配合流程
-
当添加对象的时候,会先调用对象的
hashCode
方法计算出一个应该存入的索引位置,查看该位置上是否存在元素 -
不存在:直接存
-
存在:调用
equals
方法比较内容
false:存
true:不存
1.1.2-hashCode方法改进
@override
public int hashCode(){
//复杂原因:如果对象的属性不相同,返回的哈希值,尽量不相同
return Objects.hash(name,age);
}
注:重写
hashCode
方法的时候,应该让对象的属性,都参与到哈希值的计算
IDEA可用快捷键自动生成重写后的
hashCode
方法
1.1.3-测试
package com.lyl.hash;
import com.lyl.domain.Student;
import java.util.HashSet;
public class HashDemo1 {
public static void main(String[] args) {
HashSet<Student> hs = new HashSet<>();
hs.add(new Student("赵六",26));
hs.add(new Student("张三",23));
hs.add(new Student("李四",24));
hs.add(new Student("王五",25));
hs.add(new Student("李四",24));
System.out.println(hs);
}
}
@Override
//Student类中重写的equals方法
public boolean equals(Object o) {
System.out.println("equals方法被执行了...");
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
输出结果:
equals方法被执行了...
[Student{name = 张三, age = 23}, Student{name = 李四, age = 24}, Student{name = 王五, age = 25}, Student{name = 赵六, age = 26}]
1.1.4-哈希值
-
是JDK根据某种规则算出来的int类型的整数。
-
Object类的API
@IntrinsicCandidate public native int hashCode();
public int hashCode():调用底层C++代码计算出的一个随机数(常被人称作地址值)
所以我们需要重写hashCode
方法根据对象的属性值计算出哈希值
1.1.5-HsashSet原理解析
7版本原理解析:数组+链表+(结合哈希算法)
8版本之后:
-
底层结构:哈希表(数组、链表、红黑树的结合体)
①:创建
HashSet
集合,内部会存在一个长度为16个大小的数组publicpublic HashSet() { map = new HashMap<>(); }
②:调用集合的添加方法,会拿着对象的
hashCode
方法计算出应存入的索引位置(哈希值%数组长度)-
源码:HashSet的
add
方法return map.put(e, PRESENT)==null;
进到map的put
方法return putVal(hash(key), key, value, false, true);
static final int hash(Object key) { int h; //对哈希值扰动,进行二次哈希操作,可以一定程度的减少链表挂载的数量 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
-
为什么二次哈希?
-
如果一次哈希就模以16计算该存的位置,重复的索引就会有很多,挂载的元素就很多
-
如果第一次哈希之后,拿着值向右移动16位,再对原有哈希做一个异或运算(二次哈希),最后模以16,得到的结果就更散,元素就更散
但是底层不是直接%数组长度
-
③:判断索引位置元素是否是null
是:存入
不是:说明有元素,调用
equals
方法比较内容 -
如何能够提高查询性能?
1.扩容数组
-
扩容数组的条件:当数组中的元素个数到达了16*0.75(加载因子)=12
-
扩容原数组2倍的大小
-
链表挂载的元素超过了8(阈值)个且数组长度小于64
2.链表转红黑树
-
链表挂载的元素超过了8(阈值)个且数组长度达到了64
1.2-LinkedHashSet
少用
1.3-Collections集合工具类
addAll
:批量添加
shuffle
:将List集合中的元素乱序(洗牌)
sort
:可以对传入的List集合进行排序(升序),如果集合中存储的类型,没有实现过Comparable接口,将编译出错
积累思路:
只要涉及集合的排序操作,有两个可选方案
1.
TreeSet
2.
Collections.sort()
集合中存储的类型:
Java己经写好类(String,Integer,Double...):具有自然排序规则
如果自己想指定排序规则,就可以传入比较器
自己编写的类(Student,Phone,User,Goods):
方案1:让自己的类,实现Comparable接口,具有自然排序
方案2:在sort方法的第二个参数,指定比较器
2-Map集合
HashMap、LinkedHashMap、TreeMap
实际上我们使用的单列集合,底层都是依赖于双列集合
HashSet --- HashMap
LinkedHashSet --- LinkedHashMap
TreeSet --- TreeMap
底层的数据结构都是一样,只不过这些数据结构,只针对于双列集合的键有效,跟值没有关系的
2.1-Map—API
V put(K key,V value)
:添加元素
put方法的细节:向集合中添加元素,如果添加的元素(键,已经存在了)
此时,就不是添加了,而是修改了
返回值:被覆盖掉的元素
V remove(object key)
:根据键删除键值对元素,返回值为被删除的元素
void clear()
:移除所有的键值对元素
boolean isEmpty()
:判断集合是否为空
int size()
:集合的长度,也就是集合中键值对的个数
boolean containsKey(object key)
:判断集合是否包含指定的键
boolean containsValue(object value)
:判断集合是否包含指定的值
2.2-Map集合的三种遍历
2.2.1-键找值
package com.lyl.map;
import java.util.HashMap;
import java.util.Set;
public class MapDemo1 {
public static void main(String[] args) {
HashMap<String, String> hm = new HashMap<>();
hm.put("张三", "北京");
hm.put("李四", "上海");
hm.put("王五", "广州");
//1.调用map集合的keySet方法获取所有键
Set<String> keySet = hm.keySet();
//2.遍历keySet集合,获取每一个键
for (String key : keySet) {
//3.通过map的get方法获取对应的value
String value = hm.get(key);
//4.打印在控制台
System.out.println("通过keySet方法:" + key + "---" + value);
}
}
}
输出结果:
通过keySet方法:李四---上海
通过keySet方法:张三---北京
通过keySet方法:王五---广州
2.2.2-根据键值对对象,获取键和值
public Set<Map.Entry<K,V>>entrySet()
:返回集合中所有的键值对对象
Entry
:键值对对象,是Map
里面的一个内部类
package com.lyl.map;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapDemo2 {
public static void main(String[] args) {
HashMap<String, String> hm = new HashMap<>();
hm.put("张三", "北京");
hm.put("李四", "上海");
hm.put("王五", "广州");
//1.通过entrySet方法获取键值对对象
Set<Map.Entry<String, String>> entrySet = hm.entrySet();
//2.遍历entrySet集合获取每一个对象
for (Map.Entry<String, String> entry : entrySet) {
//3.获取每一个对象的键
String key = entry.getKey();
//4.获取每一个对象的值
String value = entry.getValue();
//5.打印在控制台
System.out.println("通过entrySet方法:" + key + "---" + value);
}
}
}
输出结果:
通过entrySet方法:李四---上海
通过entrySet方法:张三---北京
通过entrySet方法:王五---广州
2.2.3-forEach方法
package com.lyl.map;
import java.util.HashMap;
import java.util.function.BiConsumer;
public class MapDemo3 {
public static void main(String[] args) {
HashMap<String, String> hm = new HashMap<>();
hm.put("张三", "北京");
hm.put("李四", "上海");
hm.put("王五", "广州");
//用forEach方法遍历,传入BiConsumer接口,用匿名内部类实现(也可转换为Lambda表达式)
hm.forEach(new BiConsumer<String, String>() {
@Override
public void accept(String key, String value) {
System.out.println(key + "---" + value);
}
});
}
}
输出结果:
李四---上海
张三---北京
王五---广州
2.3-练习
需求:键盘录入一个字符串,统计出每一个字符出现的次数(键的位置要有顺序)格式为:a(3)b(4)c(6)
package com.lyl.map;
import java.util.Scanner;
import java.util.TreeMap;
public class MapTest1 {
/*
需求:键盘录入一个字符串,统计出每一个字符出现的次数(键的位置要有顺序)
格式为a(3)b(4)c(2)d(1)e(2)
*/
public static void main(String[] args) {
TreeMap<Character, Integer> tm = new TreeMap<>();
//1.键盘录入字符串
System.out.println("请输入:");
String content = new Scanner(System.in).nextLine();
//2.拆分为字符
char[] chars = content.toCharArray();
//3.遍历字符数组
for (char c : chars) {
//4.判断map集合中是否存在
//5.不存在,值为1
if (!tm.containsKey(c)) {
tm.put(c, 1);
} else {
//6.存在,值+1
tm.put(c, tm.get(c) + 1);
}
}
//7.创建一个字符串缓冲区(容器)
StringBuilder sb = new StringBuilder();
//8.遍历集合获取键和值
tm.forEach((key, value) -> {
//9.拼接为指定格式
sb.append(key).append("(").append(value).append(")");
});
System.out.println(sb);
}
}
输出结果:
请输入:
aaabbbdddccccww
a(3)b(3)c(4)d(3)w(2)
2.4-HashMap
键的位置底层是哈希表 :
别忘了重写
hashcode
和equals
2.5-LinkedHashMap
键的位置底层是哈希表 + 双向链表 : 能够保证存取顺序
2.6-TreeMap
键的位置底层是红黑树 : 可排序