一、HashSet 集合类
HashSet 集合:底层采取哈希表存储数据(哈希表是一种对于增删改查数据性能都较好的结
构)
HashSet 特点:保证元素唯一性
此特点是hashCode方法和equals方法的配合来完成的
流程:
当添加对象的时候, 会先调用对象的hashCode方法计算出一个应该存入的索引位置, 查看该位置上是否存在元素
① 不存在:直接存
② 存在:调用equals方法比较内容
false : 存 true : 不存
自定义类需要重写了类的hashCode和equals方法
快捷方法:在类中右键选择Generate选项,如下图
HashSet 遍历: 迭代器, 增强for, foreach方法
HashSet 语法:
HashSet<String> hs = new HashSet<>();
hs.add("b");
hashCode方法:
根据对象的属性值计算出的哈希值
哈希值是JDK根据某种规则算出来的int类型的整数
HsashSet原理解析
1.JDK 7 : 数组 + 链表
① 创建一个默认长度16的数组,数组名table
② 根据元素的哈希值跟数组的长度求余计算出应存入的位置( 哈希值 % 数组长度)
计算位置的源码:
复杂的计算是为了某一个位置挂载的数据过多
③ 判断当前位置是否为null,如果是null直接存入
④ 如果位置不为null,表示有元素,则调用equals方法比较
⑤ 如果一样,则不存,如果不一样,则存入数组
JDK 7新元素占老元素位置,指向老元素 (头插法)
JDK 8中新元素挂在老元素下面(尾插法)
2.JDK 8 : 数组 + 链表 + 红黑树
⑥ 扩容数组:
A: 当数组中的元素个数到达了 16 * 0.75 (加载因子) = 12扩容原数组 2 倍的大小
B:链表挂载的元素超过了8 (阈值) 个 , 并且数组长度没有超过64
⑦ 链表转红黑树
链表挂载的元素超过了8 (阈值) 个, 并且数组长度到达了64
二、LinkedHashSet 集合类
特点:有序、不重复、无索引。
原理:底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储
的顺序。
总结:
1.如果想要集合中的元素可重复
用ArrayList集合,基于数组的。(用的最多)
2.如果想要集合中的元素可重复,而且当前的增删操作明显多于查询
用LinkedList集合,基于链表的。
3.如果想对集合中的元素去重
用HashSet集合,基于哈希表的。 (用的最多)
4.如果想对集合中的元素去重,而且保证存取顺序
用LinkedHashSet集合,基于哈希表和双链表,效率低于HashSet。
5.如果想对集合中的元素进行排序
用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。
三、Collections 集合工具类
1.可变参数
可变参数用在形参中可以接收多个数据。
可变参数的格式:数据类型...参数名称
传输参数非常灵活,方便,可以不传输参数,可以传输1个或者多个,也可以传输一个数
组
可变参数在方法内部本质上就是一个数组
注:
① 一个形参列表中可变参数只能有一个 ② 可变参数必须放在形参列表的最后面
2.Collections 集合工具类
java.utils.Collections:是集合工具类
作用:Collections并不属于集合,是用来操作集合的工具类
1.public static <T> boolean addAll(Collection<? super T> c, T... elements)
给集合对象批量添加元素
例:ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "a", "b", "c", "d");
2.public static void shuffle(List<?> list) 打乱List集合元素的顺序
例:Collections.shuffle(list);
3.public static <T> int binarySearch (List<T> list, T key) 以二分查找法查找元素
例:Collections.binarySearch(list, "b")
注: 必须是排好序的数据
4.public static <T> void max/min(Collection<T> coll)根据默认的自然排序获取最大/小值
例:ArrayList<Student> nums = new ArrayList<>();
Collections.addAll(nums,
new Student("张三", 23), new Student("王五", 25), new Student("李四", 24)
);
Collections.max(nums)
Collections.min(nums)
注:自定义类要实现Comparable,重写compareTo方法,否则编译报错
5.public static <T> void swap(List<?> list, int i, int j)交换集合中指定位置的元素
例:Collections.swap(nums, 0, 2);
6.排序相关API
使用范围:只能对于List集合的排序
① public static <T> void sort(List<T> list)将集合中元素按照默认规则排序
注意:本方式不可以直接对自定义类型的List集合排序,除非自定义类型实现了比较
规则Comparable接口
② public static <T> void sort(List<T> list,Comparator<? super T> c)
将集合中元素按照指定规则排序
四、Map 接口
1.Map 集合是一种双列集合,每个元素包含两个数据
2.Map 集合的每个元素的格式:key = value(键值对元素)
key (键) : 不允许重复
value (值) : 允许重复
键和值是一一对应的,每个键只能找到自己对应的值
3.key + value 这个整体我们称之为“键值对”或者“键值对对象” 在Java中使用Entry对象表示
4.Map的常见API
①V put(K key,V value) 添加元素
②V remove(Object key) 根据键删除键值对元素
③void clear() 移除所有的键值对元素
④boolean containsKey(Object key) 判断集合是否包含指定的键
⑤boolean containsValue(Object value) 判断集合是否包含指定的值
⑥boolean isEmpty() 判断集合是否为空
⑦int size() 集合的长度,也就是集合中键值对的个数
例:
Map<String, String> map = new HashMap<>();
map.put("张三", "北京");
map.put("李四", "北京");
map.put("王五", "上海");
System.out.println(map);//{李四=北京, 张三=北京, 王五=上海}
map.remove("王五");
System.out.println(map);//{李四=北京, 张三=北京}
System.out.println(map.isEmpty()); // false
System.out.println(map.size()); // 2
System.out.println(map.containsKey("张三")); // true
System.out.println(map.containsValue("上海")); // false
map.clear();
5.Map 接口:双列集合的数据结构,都只针对于键有效,和值没有关系
TreeMap : 键(红黑树)键排序
HashMap : 键(哈希表)键唯一
LinkedHashMap : 键(哈希表 + 双向链表)键唯一,并保证存储顺序
① HashMap底层是哈希表结构的
② 依赖hashCode方法和equals方法保证键的唯一
③ 如果键存储的是自定义对象,需要重写hashCode和equals方法
五、Map 集合的遍历方式
1.Map 集合的三种遍历方式
1) 通过键找值:V get(Object key) Set<K> keySet()
例:
private static void method1(HashMap<String, String> hm) {
// 1. 获取到所有的键
Set<String> keySet = hm.keySet();
// 2. 遍历set集合, 获取每一个键
for (String key : keySet) {
// 3. 调用map集合的get方法, 根据键查找对应的值
String value = hm.get(key);
System.out.println(key + "---" + value);
}
}
2)通过键值对对象获取键和值
例:
private static void method2(HashMap<String, String> hm) {
// 1. 获取到所有的键值对对象
Set<Map.Entry<String, String>> entrySet = hm.entrySet();
// 2. 遍历set集合获取每一个键值对对象
for (Map.Entry<String, String> entry : entrySet) {
// 3. 通过键值对对象, 获取键和值
System.out.println(entry.getKey() + "---" + entry.getValue());
}
}
3)通过foreach方法遍历
例:
public static void main(String[] args) {
HashMap<String, String> hm = new HashMap<>();
hm.put("张三", "北京");
hm.put("李四", "上海");
hm.put("王五", "成都");
hm.forEach((key, value) -> System.out.println(key + "---" + value));
hm.forEach((k,v) -> System.out.println(k + "---" + v));
}
练习:
1.需求:字符串 aababcabcdabcde
请统计字符串中每一个字符出现的次数,并按照以下格式输出
输出结果:a(5)b(4)c(3)d(2)e(1)
代码:
public static void main(String[] args) {
String info = "ababc";
// 1. 准备map集合, 用于统计字符的次数
TreeMap<Character, Integer> tm = new TreeMap<>();
// 2. 拆分字符串
char[] charArray = info.toCharArray();
// 3. 遍历字符数组, 获取每一个字符
for (char c : charArray) {
// 4. 判断当前字符在集合中是否存在
if (!tm.containsKey(c)) {
// 不存在: 说明是第一次出现
tm.put(c, 1);
} else {
// 存在: 说明不是第一次出现, 获取旧值 + 1存回去
tm.put(c, tm.get(c) + 1);
}
}
// 5. 准备StringBuilder
StringBuilder sb = new StringBuilder();
tm.forEach((key, value) -> sb.append(key).append("(").append(value).append(")"));
System.out.println(sb);
}
2.需求:定义一个Map集合,键用表示省份名称,值表示市,但是市会有多个。
添加完毕后,遍历结果:
格式如下:
江苏省 = 南京市,扬州市,苏州市,无锡市,常州市
湖北省 = 武汉市,孝感市,十堰市,宜昌市,鄂州市
四川省 = 成都市,绵阳市,自贡市,攀枝花市,泸州市
代码:
public static void main(String[] args) {
HashMap<String, List<String>> hm = new HashMap<>();
ArrayList<String> list1 = new ArrayList<>();
Collections.addAll(list1, "南京市", "扬州市", "苏州市", "无锡市", "常州市");
ArrayList<String> list2 = new ArrayList<>();
Collections.addAll(list2, "武汉市", "孝感市", "十堰市", "宜昌市", "鄂州市");
ArrayList<String> list3 = new ArrayList<>();
Collections.addAll(list3, "成都市", "绵阳市", "自贡市", "攀枝花市", "泸州市");
hm.put("江苏省", list1);
hm.put("湖北省", list2);
hm.put("四川省", list3);
Set<Map.Entry<String, List<String>>> entrySet = hm.entrySet();
for (Map.Entry<String, List<String>> entry : entrySet) {
// 键: 省份名称
String key = entry.getKey();
System.out.print(key + "=");
// 值: 多个市名
List<String> list = entry.getValue();
for (int i = 0; i < list.size() - 1; i++) {
System.out.print(list.get(i) + ", ");
}
System.out.println(list.get(list.size() - 1));
}
}