双列集合
总结
- TreeMap添加元素的时候,键不需要重写hashCode和equals方法,压根没用到hashCode和equals方法。使用红黑树规则添加的元素,是使用Comparable来比较key是否相等,返回0表示相等,自定义对象必须实现这个接口。
- HashMap是哈希表结构,JDK8开始由数组,链表,红黑树组成。虽然有红黑树,但HashMap的底层是使用哈希值和equals方法来创建红黑树的,所以不需要实现Comparable接口。
- HashMap添加元素的时候,需要重写hashCode和equals方法,它是根据key的hash值决定添加元素的位置,需要使用hashCode和equals方法,不然会添加重复的key。
- HashMap和TreeMap使用put添加元素时会覆盖key相同的value值,返回旧值;使用putIfAbsent方法添加元素则不会覆盖旧值。
- TreeMap和HashMap一般而言,HashMap的效率要更高。
- 如何选择
- 默认:HashMap(效率最高)
- 如果要保证存取顺序一样:LinkedHashMap
- 如果要进行排序:TreeMap
1、特点:
- 双列集合一次需要存一对数据,分别为键和值
- 键不能重复,值可以重复
- 键和值是一一对应的,每一个键只能找到自己对应的值
- 键 + 值 这个整体我们称之为”键值对“ 或者”键值对对象“,在Java中叫做”Entry对象“
2、Map的常见API
Map是双列集合的顶层接口,它的功能是全部双列集合都可以继承使用的
方法名称 | 说明 |
---|---|
V put(K key, V value) | 添加元素 |
V remove(Object key) | 根据删除键值对元素 |
void clear() | 移除所有的键值对元素 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合是否包含是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
public class A01_MapDemo1 {
public static void main(String[] args) {
/*
V put(K key, V value) 添加元素
V remove(Object key) 根据删除键值对元素
void clear() 移除所有的键值对元素
boolean containsKey(Object key) 判断集合是否包含指定的键
boolean containsValue(Object value) 判断集合是否包含是否包含指定的值
boolean isEmpty() 判断集合是否为空
int size() 集合的长度,也就是集合中键值对的个数
*/
// 1.创建Map集合的对象
Map<String, String> map = new HashMap<>();
// 2.添加元素
// put方法的细节:
// 添加/覆盖
// 在添加数据的时候,如果键不存在,那么直接把键值对对象添加到map集合当中,方法返回null
// 在添加数据的时候,如果键存在,那么会将原有的键值对进行覆盖,并将被覆盖的值返回
String value1 = map.put("郭靖", "黄蓉");
System.out.println("value1 = " + value1);; // null
map.put("韦小宝", "沐剑屏");
map.put("尹志平", "小龙女");
String value2 = map.put("郭靖", "小鱼儿");
System.out.println("value2 = " + value2);; // 黄蓉
// 判断是否包含key
boolean keyResult = map.containsKey("郭靖");
System.out.println("keyResult = " + keyResult); // true
// 判断是否包含value
boolean valueResult = map.containsValue("小鱼儿");
System.out.println("valueResult = " + valueResult); // true
// 判断集合是否为空
boolean result1 = map.isEmpty(); // true
// 集合的长度
int size = map.size();
System.out.println(size); // 3
// 3.打印集合
System.out.println(map); // {韦小宝=沐剑屏, 尹志平=小龙女, 郭靖=小鱼儿}
// 删除
String result = map.remove("郭靖");
System.out.println("result = " + result); // 小鱼儿
System.out.println(map); // {韦小宝=沐剑屏, 尹志平=小龙女}
// 清空
map.clear();
System.out.println(map); // {}
}
}
3、Map集合遍历
-
第一种(键找值)
public class A02_MapDemo2 { public static void main(String[] args) { // Map集合的第一种遍历方式 // 1.创建Map集合对象 Map<String, String> map = new HashMap<>(); // 2.添加元素 map.put("郭靖", "黄蓉"); map.put("韦小宝", "沐剑屏"); map.put("尹志平", "小龙女"); // 3.通过键找值 // 3.1获取所有的键,把这些键放到一个单列集合当中 Set<String> keys = map.keySet(); for (String key : keys) { // 3.3利用map集合中的键来获取对应的值 String value = map.get(key); System.out.println(key + " = " + value); } } }
-
第二种(键值对)
public class A03_MapDemo3 { public static void main(String[] args) { // Map集合的第二种遍历方式(键值对) // 1.创建Map集合对象 Map<String, String> map = new HashMap<>(); // 2.添加元素 map.put("郭靖", "黄蓉"); map.put("韦小宝", "沐剑屏"); map.put("尹志平", "小龙女"); // 3.通过键值对对象进行遍历 // 3.1通过entrySet方法获取所有的键值对对象,返回一个set集合 Set<Map.Entry<String, String>> entries = map.entrySet(); // 3.2遍历entries集合,得到里面的每一个键值对对象 for (Map.Entry<String, String> entry : entries) { String key = entry.getKey(); String value = entry.getValue(); System.out.println(key + " = " + value); } } }
-
第三种(Lambda表达式)
public class A04_MapDemo4 { public static void main(String[] args) { // Map集合的第二种遍历方式(Lambda表达式) // 1.创建Map集合对象 Map<String, String> map = new HashMap<>(); // 2.添加元素 map.put("郭靖", "黄蓉"); map.put("韦小宝", "沐剑屏"); map.put("尹志平", "小龙女"); // 3.利用lambda表达式进行遍历 // 底层: // forEach其实就是利用entrySet方法进行遍历,依次得到每一个键和值 map.forEach(new BiConsumer<String, String>() { @Override public void accept(String key, String value) { System.out.println(key + " = " + value); } }); System.out.println("-----------------------------"); // 简写 map.forEach((key, value) -> System.out.println(key + " = " + value)); } }
4、HashMap
4.1、特点
- HashMap是Map里面的一个实现类
- 没有额外需要学习的特有方法,直接使用Map里面的方法就可以了
- 特点都是由键决定的:不重复、无索引、无序:不会按照key进行排序
- HashMap跟HashSet底层原理是一模一样的,都是哈希表结构
- 依赖hashcode方法和equals方法保证键的唯一
- 如果键存储的是自定义对象,需要重写hashCode和equals方法,反之不用
4.2、案例
public class A06_HashMapDemo2 {
public static void main(String[] args) {
/*
某个班级80名学生,现在需要组成秋游活动
班长提供了四个景点依次是(A,B,C,D)
每个学生只能选择一个景点,请统计出最终哪个景点想去的人数最多
*/
// 1.需要先让学生投票
// 定义一个数组,存储4个景点
String[] arr = {"A", "B", "C", "D"};
// 利用随机数模拟80个同学,并把投票结果存储起来
ArrayList<String> list = new ArrayList<>();
Random r = new Random();
for (int i = 0; i < 80; i++) {
int index = r.nextInt(arr.length);
list.add(arr[index]);
}
// 2.如果要统计的东西比较多,不方便使用计数器思想
// 我们可以定义map集合,利用集合进行统计
HashMap<String, Integer> hm = new HashMap<>();
for (String name : list) {
// 判断当前景点在map集合当中是否存在
if (hm.containsKey(name)) {
// 存在
// 先获取当前景点已经被投票的次数
int count = hm.get(name);
// 表示当前景点又投了一次
count++;
// 把新的次数再次添加到集合当中
hm.put(name,count);
} else {
// 不存在
hm.put(name,1);
}
}
System.out.println(hm);
// 3.求最大值
int max = 0;
Set<Map.Entry<String, Integer>> entries = hm.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
int count = entry.getValue();
if (count > max) {
max = count;
}
}
System.out.println(max);
// 4.判断哪个景点的次数跟最大值一样,打印出来
for (Map.Entry<String, Integer> entry : entries) {
Integer count = entry.getValue();
if (count == max) {
System.out.println("最大值:" + entry.getKey());
}
}
}
}
5、LinkedHashMap
5.1、特点
- 由键决定:不重复、无索引、有序:保证存储和取出的元素顺序一致
- 原理:底层数据结构依然是哈希表,只是每个键值对元素又额外多了一个双链表的机制记录存储的顺序
5.2、案例
public class A07_LinkedHashMapDemo3 {
public static void main(String[] args) {
// 1.创建集合
LinkedHashMap<String, Integer> lhm = new LinkedHashMap<>();
// 2.添加元素,有序指的时存取的顺序是一样的
lhm.put("a",123);
lhm.put("d",567);
lhm.put("c",456);
lhm.put("b",345);
// 3.打印集合
System.out.println(lhm); // {a=123, d=567, c=456, b=345}
}
}
6、TreeMap
6.1、特点
- TreeMap跟TreeSet底层原理一样,都是红黑树结构的
- 由键决定特性:不重复,无索引,可排序
- 注意:默认按照键从小到大进行排序,就是升序,也可以自己规定键的排序顺序
代码书写两种排序规则:
- 实现Comparable接口,指定比较规则
- 创建集合时传递Comparator比较器对象,指定比较规则
6.2、案例1
public class A01_TreeMapDemo1 {
public static void main(String[] args) {
/*
TreeMap集合:基本应用
需求1:
键:整数表示id
值:字符串表示商品名称
要求:按照id的升序排列、按照id的降序排列
*/
// 1.创建集合对象
// 默认情况下都是按照升序排列的
// String 按照字母在ASCII码表中对应的数字升序进行排列
// abcde...
TreeMap<Integer, String> tm = new TreeMap<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
// 根据key倒叙排序
// o1:表示当前要添加的元素
// o2:表示已经在红黑树中存在的元素
return o2 - o1;
}
});
// 2.添加元素
tm.put(2,"可口可乐");
tm.put(4,"百事可乐");
tm.put(5,"奥利奥");
tm.put(3,"江小白");
tm.put(1,"康师傅");
// 3.打印集合
System.out.println(tm); // {5=奥利奥, 4=百事可乐, 3=江小白, 2=可口可乐, 1=康师傅}
}
}
6.3、案例2:
-
Student.java
public class Student implements Comparable<Student>{ private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } /** * 获取 * @return name */ public String getName() { return name; } /** * 设置 * @param name */ public void setName(String name) { this.name = name; } /** * 获取 * @return age */ public int getAge() { return age; } /** * 设置 * @param age */ public void setAge(int age) { this.age = age; } public String toString() { return "Student{name = " + name + ", age = " + age + "}"; } @Override public int compareTo(Student o) { // 要求:按照学生年龄的升序排序,年龄一样按照姓名的字母排列,同姓名同年龄的视为同一人 // this:表示当前要添加的元素 // o:表示已经在红黑树中存在的元素 // 返回值: // 负数:表示当前要添加的元素是小的,存左边 // 正数:表示当前要添加的元素是大的,存右边 // 0:表示当前要添加的元素已经存在,舍弃 int i = this.getAge() - o.getAge(); i = i == 0 ? this.getName().compareTo(o.getName()) : i; return i; } }
-
A02_TreeMapDemo2.java
public class A02_TreeMapDemo2 { public static void main(String[] args) { /* TreeMap集合:基本应用 需求2:学生对象 值:籍贯 要求:按照学生年龄的升序排序,年龄一样按照姓名的字母排列,同姓名同年龄的视为同一人 */ // 1.创建集合 TreeMap<Student, String> tm = new TreeMap<>(); // 2.创建三个学生对象 Student s1 = new Student("zhangsan", 23); Student s2 = new Student("lisi", 24); Student s3 = new Student("wangwu", 25); // 3.添加元素 tm.put(s1, "江苏"); tm.put(s2, "天津"); tm.put(s3, "北京"); // 4.打印集合 System.out.println(tm); } }
6.4、案例3:
public class A03_TreeMapDemo3 {
public static void main(String[] args) {
/*需求:
字符串:"adbadadgadbabdadada"
请统计字符串中每个字符出现的次数,并按照以下格式输出
输出结果:
a(5)b(4)c(3)d(2)e(1)
新的统计思想:利用map集合进行统计
如果题目中没有要求对结果进行排序,默认使用HashMap,效率高
如果题目中要求对结果进行排序,使用TreeMap
键:表示要统计的内容
值:表示次数
*/
// 1.定义字符串
String s = "adbadadgadbabdadada";
// 2.创建集合
TreeMap<Character, Integer> tm = new TreeMap<>();
// 3.遍历字符串得到里面的每一个字符
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
// 拿着c到集合中判断是否存在
// 存在,表示当前字符又出现了一次
// 不存在,表示当前字符是第一次出现
if (tm.containsKey(c)) {
// 存在
// 先把已经出现的次数拿出来
int count = tm.get(c);
// 当前字符又出现了一次
count++;
// 把自增之后的结果再添加到集合当中
tm.put(c, count);
} else {
// 不存在
tm.put(c, 1);
}
}
// 4.遍历集合,并按照指定的格式进行拼接
// a(5)b(4)c(3)d(2)e(1)
// StringBuilder sb = new StringBuilder();
// tm.forEach((key, value) -> sb.append(key).append("(").append(value).append(")"));
StringJoiner sj = new StringJoiner("","","");
tm.forEach((key, value) -> sj.add(key + "").add("(").add(value + "").add(")"));
System.out.println(sj);
}
}