一、Map集合
双列集合,每个元素都包含一个Key值和Value值,键对象和值对象之间的关系称为映射。通过key找到value,
Map不能存基本数据类型,只能存引用数据类型
1、Map的实现类
2、Map接口的常用方法
重点:
put()方法,可以添加和修改内容使用该方法时会覆盖原有的值。
replace()的修改和put()有些类似,但是replace()只有替换的功能,如果key()不存在,则不替换。
重点掌握:get、put、remove
/**
* @document: Map集合的常用方法
* @Author:SmallG
* @CreateTime:2023/8/7+11:29
*/
public class Demo01 {
public static void main(String[] args) {
//创建一个Map集合对象 key是整型,value是字符串
Map<Integer, String> map = new HashMap<>();
//存数据,以键值对的形式存储
//key不能重复 ,value值可以重复
map.put(1, "Hello");
map.put(3, "Tom");
map.put(5, "SmallG");
//查看集合中键值对的个数(集合中数据的个数)
System.out.println("Map集合中元素的个数为:" + map.size());
//从map集合中取出一个数据
System.out.println(map.get(1)); //Hello
System.out.println(map.get(2)); //null 没有这个key,返回null值
//存放已经存在的key,用put方法可以覆盖之前的内容并返回替换的内容
String s = map.put(1, "World");
System.out.println("之前的value值为" + s); //之前的value值为Hello
System.out.println(map.get(1)); //World
//删除集合中的数据,返回删除掉的数据
String remove = map.remove(1);
System.out.println("删除的数据是:" + remove); //删除的数据是:World
System.out.println(map.get(1)); //null
}
}
3、哈希表
也称散列表,是一种常见的数据结构,用于存储和检索键值对。基于hash函数将key映射到数组索引,以便快速访问和操作数据。
(1)哈希冲突
两个不同的输入值,根据同一哈希函数计算出的索引相同的现象称为哈希冲突,也叫哈希碰撞,任何Hash函数都无法避免哈希冲突,常见的解决哈希冲突的方法有:
- 开放地址法:一旦发生了冲突,就去寻找下一个空的哈希地址,只要哈希表足够大,总能找到空的哈希地址,并将元素存入
- 再Hash法:当Hash地址发生冲突时使用其他函数计算另一个Hash函数地址,直到不再产生冲突为止
- 建立公共溢出区:将Hash表分为基本表和溢出表两部分,发生冲突的元素都放入溢出表
- 链地址法:将Hash表的每个单元作为链表的头结点,所有Hash地址为i的元素构成一个同义词链表,即发生冲突时就把该元素链接在该单元为头节点的链表的尾部
(2)HashMap
内部基于哈希表存储键值对数据,应用于缓存、索引、数据存储和快速查找等场景,内部使用了一个Node类存储在哈希桶里,map集合本身是一种无序的结构。
- final int hash:存储的哈希码,用于确定键值对在桶数组中的位置
- final K key:存储键的值
- V value:存储与键相关联的值
- Node
next:用于处理哈希冲突,存储下一个Node节点的引用,形成链表或红黑树结构
/**
* @document: HashMap遍历数组
* @Author:SmallG
* @CreateTime:2023/8/7+14:29
*/
public class Demo02 {
public static void main(String[] args) {
//创建一个map对象
Map<Integer, String> map = new HashMap<>();
//往map中加入数据
map.put(5, "Tom");
map.put(3, "Jerry");
map.put(9, "SmallG");
map.put(4, "hhhh");
map.put(1, "hhh");
//遍历方式一:先获取所有的key,然后用key找value
Set<Integer> integers = map.keySet();//获取map集合所有的key
for (Integer key : integers) {
//根据key获取value
String value = map.get(key);
System.out.println(key + " : " + value);
}
System.out.println("-------------------------------");
//遍历方式二:通过map集合的entrySet()方法,把map中的所有数据一次查询出来
//entrySet是map的一个内部类
Set<Map.Entry<Integer, String>> entries = map.entrySet();
for (Map.Entry<Integer, String> entry : entries) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
}
}
(3)HashCode方法
用于返回当前对象的hash值,返回值类型为int
(4)put方法的执行流程
- 计算Key的哈希值,如果key为null,则哈希值为0。如果key不为null,调用key的hashCode方法,计算Key的哈希值
- 如果内部数组没有被初始化,会先初始化内部数组
- 通过key的哈希值计算key在桶(数组)中的位置
- 如果桶中的目标位置没有元素,则创建Node对象,存储键值对数据,并将Node对象保存到桶中目标位置
- 如果桶中目标位置有元素(注意可能有多个),则将key与这些元素的key进行比较,如果key与某个元素的key相等,则使用新存入的value覆盖旧的value,如果不相等则创建新的node对象,并追加到链表中。
/**
* @document: put()方法
* @Author:SmallG
* @CreateTime:2023/8/7+15:12
*/
public class Demo03 {
public static void main(String[] args) {
//创建一个Key为包装类的map集合
Map<Integer, String> map1 = new HashMap<>();
map1.put(1, "Tom");
//相同的key会把之前的key替换掉
map1.put(1, "Jerry");
System.out.println(map1.get(1)); //Jerry
//创建一个使用自定义类作为key的map集合
Map<Student, String> map2 = new HashMap<>();
Student student1 = new Student("Tom", 12);
Student student2 = new Student("Tom", 12);
map2.put(student1, "Tom");
map2.put(student2, "Jerry");
System.out.println(map2.get(student1)); //Tom
System.out.println(map2.get(student2)); //Jerry
//两个key之所以不同,不是调用了equals方法
//是因为两个对象的hashCode值不同,导致判断两个对象不是同一个对象
System.out.println("student1 的hashCode值"+student1.hashCode());
//student1 的hashCode值990368553
System.out.println("student2 的hashCode值"+student2.hashCode());
//student2 的hashCode值1096979270
}
}
class Student {
String name;
Integer age;
public Student() {
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
}
(5)重写hashCode()方法
/**
* @document: put()方法
* @Author:SmallG
* @CreateTime:2023/8/7+15:12
*/
public class Demo03 {
public static void main(String[] args) {
//创建一个Key为包装类的map集合
Map<Integer, String> map1 = new HashMap<>();
map1.put(1, "Tom");
//相同的key会把之前的key替换掉
map1.put(1, "Jerry");
System.out.println(map1.get(1)); //Jerry
//创建一个使用自定义类作为key的map集合
Map<Student, String> map2 = new HashMap<>();
Student student1 = new Student("Tom", 12);
Student student2 = new Student("Tom", 12);
map2.put(student1, "Tom");
map2.put(student2, "Jerry");
System.out.println(map2.get(student1)); //Tom
System.out.println(map2.get(student2)); //Jerry
//两个key之所以不同,不是调用了equals方法
//是因为两个对象的hashCode值不同,导致判断两个对象不是同一个对象
System.out.println("student1 的hashCode值" + student1.hashCode());
//student1 的hashCode值990368553
System.out.println("student2 的hashCode值" + student2.hashCode());
//student2 的hashCode值1096979270
}
}
class Student {
String name;
Integer age;
public Student() {
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
System.out.println("equals方法被调用"); //默认情况下equals()方法没有被调用
Student stu = (Student) o;
if (this.name.equals(stu.name) && this.age.equals(stu.age)) {
return true;
} else {
return false;
}
}
@Override
public int hashCode() {
//重写了hashCode方法,map集合在判断对象是否相等时,会调用equals方法
//如果比较相等,就认为是同一个key
return Objects.hash(name, age);
}
}
(6)HashMap原理
HashMap的容量
HashMap的内部数组不是在创建HashMap对象时初始化,而是在首次存入元素时进行初始化,以减少对内存的占用。
官方规定table的长度总是2的N次方:目的是为了保证HashMap的速度足够快。
不论存操作还是取操作,HashMap都使用除留取余法通过key的哈希值计算key在桶中的索引(位运算)
HashMap的初始容量
有默认初始容量和手动指定容量两种情况,用无参构造器创建HashMap对象时,table的初始化长度为16(默认长度)
HashMap的扩容
自动将桶的长度扩容到原来的两倍,容量(HashMap的内部数组长度,默认为16),大小(HashMap中实际存储的元素个数,默认为0),负载因子(用来衡量HashSet"满”的程度,默认值为0.75f),临界值(当size超过临界值时,HashMap将扩容,threshold=容量×负载因子)。
(7)树化和退化
- HashMap中使用链地址法处理Hash冲突,当桶中的某个位置的链表过长时,会影响查询效率的情况。当链表长度大于等于阈值(默认为8),同时HashMap容器已达到64时,链表------->红黑树,从而减少查询操作的时间复杂度,这个过程叫树化。
- 当红黑树中的节点数量减少到一定程度(默认为6)时,HashMap将红黑树----->链表,这个过程称为退化。
4、LinkedHashMap
- HashMap无法保持元素添加顺序,而LinkedHashMap就能解决这一问题,但性能上没HashMap高。
- LinkedHashMap在HashMap的基础上维护了一个entry的双向链表,用来记录顺序。
/**
* @document: LinkedHashMap集合,相比于HashMap的无序集合,linkedList是个有序集合
* @Author:SmallG
* @CreateTime:2023/8/8+10:07
*/
public class Demo05 {
public static void main(String[] args) {
//创建对象
LinkedHashMap<Integer, String> linkedHashMap = new LinkedHashMap<>();
//存入数据
linkedHashMap.put(5, "Tom");
linkedHashMap.put(3, "Jerry");
linkedHashMap.put(9, "SmallG");
//存入相同的数据
linkedHashMap.put(3, "SmallY");
//删除一个数据
linkedHashMap.remove(5);
//查询一个数据
System.out.println(linkedHashMap.get(3));
//遍历LinkedHashMap集合
Set<Map.Entry<Integer, String>> entries = linkedHashMap.entrySet();
for (Map.Entry<Integer, String> entry : entries) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
}
}
二、Set集合
1、Set接口概述
- 继承Collection接口,与Collection接口中的方法基本一致,比Collection接口更加严格。
- 无序且不可重复
- 可以使用set不可重复的特性实现去重操作
/**
* @document: set集合,无序不可重复
* @Author:SmallG
* @CreateTime:2023/8/8+10:26
*/
public class Demo06 {
public static void main(String[] args) {
//创建对象
Set<Integer> set = new HashSet<>();
LinkedHashSet linkedHashSet = new LinkedHashSet();
//存数据
linkedHashSet.add(15);
linkedHashSet.add(30);
linkedHashSet.add(20);
linkedHashSet.add(10);
//存入数据
set.add(15);
set.add(30);
set.add(20);
set.add(10);
//尝试存入重复的数据
boolean add = set.add(20);//被忽略
System.out.println("是否添加成功:" + add); //false
System.out.println(set);
//set集合没有get方法,只能通过迭代器的方法才能遍历
//1、将set集合变成迭代器
Iterator<Integer> iterator = set.iterator();
//2、遍历迭代器
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
System.out.println("------------------");
//增强for循环
for (Integer set1 : set) {
System.out.println(set1);
}
System.out.println("------------------");
System.out.println(linkedHashSet);
System.out.println("------------------");
//利用set集合中的数据不可重复 可以去掉List集合中重复的数据,达到去重的目的
List<Integer> list = Arrays.asList(7, 8, 10, 9, 9, 21); //快速构建成一个list集合
System.out.println(list);
//利用set集合去重
Set<Integer> set1 = new HashSet<>(list);
System.out.println(set1);
}
}
2、Set常用方法
/**
* @document: 集合运算,求交集、并集、差集(补集)
* @Author:SmallG
* @CreateTime:2023/8/8+10:48
*/
public class Demo07 {
public static void main(String[] args) {
List<Integer> list1 = Arrays.asList(1, 2, 3, 4);
List<Integer> list2 = Arrays.asList(3, 4, 5, 6);
//转换为set集合
Set<Integer> set1 = new HashSet<>(list1);
Set<Integer> set2 = new HashSet<>(list2);
//查看原始数据
System.out.println(set1);
System.out.println(set2);
//求交集
set1.retainAll(set2);
System.out.println("set1和set2的交集是" + set1);
//求并集
//重置set集合
set1 = new HashSet<>(list1);
set1.addAll(set2);
System.out.println("set1和set2的并集是" + set1);
//求差集
set1 = new HashSet<>(list1);
set1.removeAll(set2);
System.out.println("set1和set2的差集是" + set1);
}
}
3、HashSet
底层用哈希表实现,用于存储唯一的元素
HashSet特点
- 基于哈希表,使用哈希函数将元素映射到对应的存储位置
- 无序(元素的插入顺序与遍历顺序不一致)
- 不允许重复元素(如果尝试添加重复元素会被忽略)
- HashSet允许使用null作为元素,但只能有一个,HashMap中key值只允许有一个null
- HashSet的插入、删除和查找操作时间复杂度最低(O(1))。
/**
* @document: HashSet集合
* @Author:SmallG
* @CreateTime:2023/8/8+11:24
*/
public class Demo08 {
public static void main(String[] args) {
HashSet<Student1> set1 = new HashSet<>();
//创建学生类对象
Student1 student1 = new Student1("Tom", 18);
Student1 student2 = new Student1("Tom", 18);
//将两个学生存入到set中,内容相同地址不同,可以存进去
set1.add(student1);
set1.add(student2);
System.out.println(set1); //两个对象通过equals方法判断两个不想等
//重写equals方法和hashCode方法,equals判断相等,只能成功保存一个数据
HashSet<Student2> set2 = new HashSet<>();
Student2 student3 = new Student2("Tom", 18);
Student2 student4 = new Student2("Tom", 18);
set2.add(student3);
set2.add(student4);
System.out.println(set2);
//尝试存入null值
set2.add(null);
set2.add(null);
set2.add(null);
set2.add(null);
set2.add(null);
System.out.println(set2); //只能存入一个null值
}
}
class Student1 {
String name;
int age;
@Override
public String toString() {
return "Student1{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public Student1() {
}
public Student1(String name, int age) {
this.name = name;
this.age = age;
}
}
class Student2 {
String name;
int age;
public Student2(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student2 student2 = (Student2) o;
return age == student2.age && Objects.equals(name, student2.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Student2{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
三、练习
1 统计一句话中各个字符的个数
现有字符串"good good study, day day up.",统计其中各个字符出现的次数,统计结果要求按字母在语句中出现的顺序输出。
程序运行效果如下所示:
/**
* @document: 统计一句话中各个字符的个数
* @Author:SmallG
* @CreateTime:2023/8/7+14:12
*/
public class StringCount {
public static void main(String[] args) {
System.out.println("请输入字符串:");
Scanner scanner = new Scanner(System.in);
String s = scanner.nextLine();
//创建hashmapkey存放key,value存放出现次数
LinkedHashMap<Character, Integer> hashMap = new LinkedHashMap<>();
//遍历字符串
for (int i = 0; i < s.length(); i++) {
if (hashMap.containsKey(s.charAt(i))) {
int a = hashMap.get(s.charAt(i));
hashMap.put(s.charAt(i), a + 1);
} else if (Character.isLetter(s.charAt(i))) { //判断字符是否为英文
hashMap.put(s.charAt(i), 1);
} else {
continue;
}
}
System.out.println(hashMap);
}
}
2 分析用户行为数据
电商平台会记在用户的行为数,用于隐式反馈推荐问题的研究。
UserBehavior.csv是阿里巴巴提供的一个淘宝用户行为数据集。
用户的行为数据字段描述如下:
其中,“行为类型”字段的描述如下所示:
UserBehavior-1000.csv是该数据集的一个子集,筛选了2017年11月26日中的1000行记录,数据内容如下所示(数据集中的字段顺序与上图中的数据字段说明顺序一致):
比如,上图中的第一行数据表示:用户ID为 1000228的客户对商品ID为2410458的商品(商品类目ID为4145813)进行了pv操作(浏览),操作时间戳为1511625804。
请综合运用之前所学知识,完成以下分析需求:
1、统计数据集中出现次数前5的商品类目,在控制台输出商品类目ID和出现次数,按出现次数降序排列;
2、统计11月26日每小时的用户行为数据量,在控制台输出时间区间和对应的行为数据数量,按时间区间升序排列。
提示:
1、将每一条用户数据封装成一个对象,可降低整体编码复杂度
2、借助AI工具学习如何实现集合中数据的排序
程序运行效果如下所示:
/**
* @document: 用户类
* @Author:SmallG
* @CreateTime:2023/8/8+7:48
*/
public class User {
private Integer userId; //用户ID
private Integer commodityId; //商品ID
private Integer comClassId; //商品类别Id
private String type; //行为类型
private long time; //时间戳
public User(Integer userId, Integer commodityId, Integer comClassId, String type, long time) {
this.userId = userId;
this.commodityId = commodityId;
this.comClassId = comClassId;
this.type = type;
this.time = time;
}
public User() {
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public Integer getCommodityId() {
return commodityId;
}
public void setCommodityId(Integer commodityId) {
this.commodityId = commodityId;
}
public Integer getComClassId() {
return comClassId;
}
public void setComClassId(Integer comClassId) {
this.comClassId = comClassId;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", commodityId=" + commodityId +
", comClassId=" + comClassId +
", type='" + type + '\'' +
", time=" + time +
'}';
}
}
主方法类:
/**
* @document:
* @Author:SmallG
* @CreateTime:2023/8/8+7:56
*/
public class Extended01 {
public static void main(String[] args) {
String path = "src/day02/homework/UserBehavior-1000.csv";
//读取文件,用arraylist接收
ArrayList<User> users = readUserBehavior(path);
//用hashmap存储出现次数key-商品id ,value-出现次数
HashMap<Integer, Integer> counts = counts(users);
//对存储的counts进行输出,按照value的降序进行排列,求前五
num5(counts);
//统计每天的行为次数,key--对应的小时数,value--该小时数的数据量
HashMap<Integer, Integer> hourCounts = hourCounts(users);
//对数据进行排序输出
printCount(hourCounts);
}
/**
* 输出行为次数
* @param hourCounts
*/
public static void printCount(HashMap<Integer, Integer> hourCounts) {
TreeMap<Integer, Integer> treeMap = new TreeMap<>(hourCounts);
//输出统计信息
System.out.println("----------每小时数据数量----------");
for (Map.Entry<Integer, Integer> entry : treeMap.entrySet()) {
int startHour = entry.getKey();
int endHour = startHour + 1;
System.out.println(startHour + "-" + endHour + ":" + entry.getValue());
}
}
/**
* 返回每天的行为次数
*
* @param users 传入参数为读取的用户数据
* @return 返回一个hashmap用于存储每天的行为次数
*/
public static HashMap<Integer, Integer> hourCounts(ArrayList<User> users) {
HashMap<Integer, Integer> hourCountMap = new HashMap<>();
//遍历用户行为数据
for (User user : users) {
//将时间戳转换为本地日期时间对象
LocalDateTime ldt = LocalDateTime.ofEpochSecond(user.getTime(), 0, ZoneOffset.ofHours(8));
//获取小时信息
int hour = ldt.getHour();
if (hourCountMap.containsKey(hour)) {
int newHourCount = hourCountMap.get(hour);
hourCountMap.put(hour, newHourCount + 1);
} else {
hourCountMap.put(hour, 1);
}
}
return hourCountMap;
}
/**
* 输出前五的商品(理解上存在问题)
*
* @param counts 传入一个hashMap存储的信息是商品id出现的次数
*/
public static void num5(HashMap<Integer, Integer> counts) {
//将出现的次数放到arraylist数组
ArrayList<Map.Entry<Integer, Integer>> cidCountList = new ArrayList<>(counts.entrySet());
//对数组进行排列
cidCountList.sort(new Comparator<Map.Entry<Integer, Integer>>() {
@Override
public int compare(Map.Entry<Integer, Integer> o1, Map.Entry<Integer, Integer> o2) {
return o2.getValue() - o1.getValue();
}
});
System.out.println("----------商品类目Top5----------");
//对前五进行输出
for (Map.Entry<Integer, Integer> entry : cidCountList.subList(0, 5)) {
System.out.println(entry);
}
}
/**
* 统计商品出现次数的方法
*
* @param users 传入参数为arraylist的数组
* @return 返回一个HashMap,key--商品id,value--该商品出现的次数
*/
public static HashMap<Integer, Integer> counts(ArrayList<User> users) {
//创建HashMap对象,用于存储商品出现的次数,当作返回值
HashMap<Integer, Integer> counts = new HashMap<>();
for (User user : users) {
if (counts.containsKey(user.getCommodityId())) {
//商品id已经存在
int a = counts.get(user.getCommodityId());
//将商品的出现次数加一
counts.put(user.getCommodityId(), a + 1);
} else {//首次出现,则将hashmap中key对应的value值设为1
counts.put(user.getCommodityId(), 1);
}
}
return counts;
}
/**
* 处理读取的数据方法
*
* @param str 读取的一行数据
* @return 返回一个User对象
*/
public static User readUser(String str) {
String[] strings = str.split(",");
User user = new User(Integer.parseInt(strings[0]),
Integer.parseInt(strings[1]),
Integer.parseInt(strings[2]),
strings[3], Long.parseLong(strings[4]));
return user;
}
/**
* 读取文件方法
*
* @param path 传入文件路径
* @return 返回封装好的用户信息
*/
public static ArrayList<User> readUserBehavior(String path) {
//存储读取信息,用于返回
ArrayList<User> users = new ArrayList<>();
try (
//字符输入流 读取数据
FileReader fr = new FileReader(path);
//字符输入缓冲流
BufferedReader br = new BufferedReader(fr)
) {
String line = br.readLine();
while (line != null) {
users.add(readUser(line));
line = br.readLine();
}
} catch (IOException e) {
e.printStackTrace();
}
return users;
}
}
1 条评论
《云雾之间》剧情片高清在线免费观看:https://www.jgz518.com/xingkong/62378.html