第三章:集合03
一:Map接口
1. 框架结构
2. Map接口特点及常用方法
- 所存储值的特点:Map用于保存具有映射关系的数据,Map里保存着两组数据:key和value,它们都可以使任何引用类型的数据,但key不能重复。所以通过指定的key就可以取出对应的value。
- 存储特点:无序,且不可重复。
- 必须掌握的底层实现类:HashMap,TreeMap.
- 注意事项: Map 没有继承 Collection 接口, Map 提供 key 到 value 的映射,你可以通过“键”查找“值”。一个 Map 中不能包含相同的 key ,每个 key 只能映射一个 value 。 Map 接口提供 3 种集合的视图, Map 的内容可以被当作一组 key 集合,一组 value 集合,或者一组 key-value 映射。
- 常用方法
- Map集合如何遍历??方法如何使用??
/*
java.util.Map接口中常用方法
1.Map集合和Collection没有继承关系
2.Map集合以key和value的方式存储数据:键值对
kay和value都是引用数据类型。
kay和value都是存储对象的内存地址。
kay起主导地位,value是key的一个附属品。
3.常用方法
V put(K key, V value) //向Map集合中添加键值对
V get(Object key) //通过key获取value
void clear() //清空Map集合
boolean containsKey(Object key) //判断Map集合中是否包含某个key
boolean containsValue(Object value)//判断Map中是否包含某个value
boolean isEmpty()//判断Map集合中元素个数是否为0
V remove(Object key) //通过key删除键值对
int size() //获取Map中键值对的个数
Collection<V> values()//获取Map集合中所有的value,返回一个Colection
Set<K> keySet() //获取Map集合所有的key
Set<Map.Entry<K,V>> entrySet() //将Map集合转换为一个Set集合
【注意:Map集合通过entrySet()方法转换成的这个Set集合,Set集合中元素类型是Map.Entry<K,V>(内部类)】
Map集合对象
key value
-----------------------
1 zhangsan
2 lisi
3 wangwu
4 zhaoliu
Set set = map.entrySet();
Set集合对象
1=zhangsan
2=lisi
3=wangwu
4=zhaoliu
*/
public class MapTest01 {
public static void main(String[] args) {
//创建Map对象
Map<Integer,String> map = new HashMap<>();
// V put(K key, V value) //向Map集合中添加键值对
map.put(1,"zhangsan");
map.put(2,"lisi");
map.put(3,"wangwu");
map.put(4,"zhaoliu");
// V get(Object key) //通过key获取value
System.out.println(map.get(2));
//int size() //获取Map中键值对的个数
System.out.println("键值对数量为:" + map.size());
// V remove(Object key) //通过key删除键值对
map.remove(2);
System.out.println("键值对数量为:" + map.size());
//boolean containsKey(Object key) //判断Map集合中是否包含某个key
//boolean containsValue(Object value)//判断Map中是否包含某个value
System.out.println(map.containsKey(1));
System.out.println(map.containsValue("zhangsan"));
// Collection<V> values()//获取Map集合中所有的value,返回一个Colection
Collection<String> values = map.values();
for (String s:values) {
System.out.println(s);
}
// void clear() //清空Map集合
map.clear();
System.out.println("键值对数量为:" + map.size());
System.out.println(map.isEmpty());
}
}
/*
Map集合的遍历
大数据量使用第二种方式(效率高)
*/
public class MapTest02 {
public static void main(String[] args) {
/*
第一种方式:由map.keySet()获取所有的key,通过Set集合中的key,由迭代器遍历
*/
Map<Integer,String> map = new HashMap<>();
map.put(1,"zhangsan");
map.put(2,"lisi");
map.put(3,"wangwu");
map.put(4,"zhaoliu");
//获取所有的key
Set<Integer> keys= map.keySet();// Set<K> keySet() //获取Map集合所有的key
//迭代器
Iterator<Integer> it= keys.iterator();
while (it.hasNext()){
Integer key = it.next();
System.out.println(key +"=" +map.get(key));//V get(Object key) //通过key获取value
}
//由keymap.keySet()获取所有的,通过Set集合中的key,增强for循环遍历
System.out.println("----------------------");
for (Integer key:keys) {
System.out.println(key +"=" + map.get(key));
}
System.out.println("----------------------");
/*
第二种方式:由Set<Map.Entry<K,V>> entrySet() //将Map集合转换为一个Set集合
*/
Set<Map.Entry<Integer,String>> set = map.entrySet();
//遍历set集合
Iterator<Map.Entry<Integer,String>> it2= set.iterator();
//直接输出这个node
// while (it2.hasNext()){
// System.out.println(it2.next());
// }
//或者通过node内部类中的方法 node.getKey();和node.getValue();获取key和value方法
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("----------------------");
for (Map.Entry<Integer,String> node:set) {
System.out.println(node.getKey()+ "---->" + node.getValue());
}
}
}
3. Map接口实现类之HashMap集合
-
底层数据结构:HashMap集合底层是哈希表/散列表的数据结构
-
存储特点:无序,且不可重复(k值一定不能相同)。
-
如何保证:无序和不可重复。
无序:由于底层的数据结构是数组加单向链表,因此我们在存储时底层并不知道挂在哪一个单向链表中,而取值是是先根据数组下标,在第一个数组的单向链表中取数据,所以会无序。
不可重复:底层的k值一定不可重复。 -
存取值的原理是什么。
存:put(k,v)实现原理:
第一步:首先将k,v封装到Node对象当中(节点)。
第二步:调用K的hashCode()方法得出hash值。
第三步:通过哈希表函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,
就把Node添加到这个位置上。如果下标位置有元素(即存在hash冲突),此时,就会拿着k和
链表上每个节点的k进行equal(因为底层存储的并不是基本数据类型,而是数据类型的地址值
(可以理解引用数据类型)如果所有的equals方法返回都是false(即k值不相同),那么这个新的
节点将被添加到链表的末尾,如其中有一个equals返回了true(即k值相同),那么这个节点的
value将会被覆盖。。
取:map.get(k)实现原理:
第一步:先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。
第二步:通过上一步哈希算法转换成数组的下标之后,在通过数组下标快速定位到某个位置上。
第三步:如果该位置上没有链表(红黑树)则返回null。如果这个位置上有单向链表,那么它就会拿着
参数K和单向链表上的每一个节点的K进行equals,如果所有equals方法都返回false,则get方法返回null。
如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,
get方法最终返回这个要找的value。
-
为什么k值类型的类(如k部分为String)必须同时重写hashCode和equals方法。
假设第一步:k部分存储的值为Student对象:代码如下所示:class Student{ private int no; private String name; }
第二步:创建Student对象s1和s2.
Student1 s1 = new Student(111,"zhangsan"); Student s2 = new Student(111,"zhangsan");
第三步:此时我们往HashMap集合中存储元素。
Map<Student,Integer> hashMapTest = new HashMap<>(); map1.put(s1,222); map1.put(s2,333);
第四步:前面说到,存储Map中的元素一定是无序且不可重复的(如何做到不可重复:因为Map中的元素都是一键一值的,如果k相同,那么一定是对原值得覆盖)。
因此,从上面的例子我们可以明显的看到s1和s2是相同的,所以存储进去是s2对s1的覆盖,但是s1和s2是两个对象,由于Student没有重写hashCode方法,所以它们是默认继承Object类的hashCode方法,由于它们是两个对象,所以JVM默认它们是不相等的。
但是我们从内容中发现,它们其实是相同的,此时如果我们重写hashCode 方法则在hash值方面它们是相等的。
在put(k,v)方法中当K中的hash值相等的时候,它们一定是在同一个链表结构中,此时我们利用equals方法进行对比,但是我们在Student类中没有对equals方法重写,默认继承Object类,
但是 Object类对比的是两个对象的内存地址,但是这两个对象的内存地址不相等,如果我们不重写equals方法,发现s2能够存储进去,但没有对si覆盖,所以这是不对的。
综上所述,当使用 HashMap集合时,我们必须要保证所存储的内容的对象必须重写了hashCode和equals方法。
4. Map接口实现类之TreeMap集合
-
底层数据结构:TreeMap集合底层红黑树(个人认为是平衡二叉树)的数据结构
-
存储特点:无序,且不可重复(k值一定不能相同),但确实可排序的。
-
如何保证:无序和不可重复。
无序:由于底层的数据结构是数组加单向链表,因此我们在存储时底层并不知道挂在哪一个单向链表中,而取值是是先根据数组下标,在第一个数组的单向链表中取数据,所以会无序。
不可重复:底层的k值一定不可重复。 -
存取值的原理是什么。
如HashMap集合相差不大,其实我们一般很少用Map集合,但我们大多数用的是Set集 合,但是 无论是HashSet集合还是TreeSet集合,其底层都是HashMap和TreeMap集合,即我们在存储时 是将值存储在Map集合中的k中。
-
TreeMap集合是如何可排序的。
由于TreeMap集合时一个自平衡二叉数,所以它在内部是可以进行比较(是有大小的)的,如何 进行比较?? 首先:TreeMap集合是对存储进去元素的k值进行比较,如果我们的k值是String,或者是包装类型, 当然可以,因为它们是可以进行比较的。 同理:如HashMap集合一样,如果我们的k值是我们自己制作的引用数据类型呢??如何对应进行 比较??是按照什么进行比较??比较的规则是什么??如果我们没有自己 制定规则的话JVM会不知道如何进行比较,自然就构不成书,此时会出现异常: java.lang.ClassCastException:class 进阶.集合05.Map集合.HashMap.Person cannot be cast to class java.lang.Comparable
import java.util.TreeSet; /* 对于自定义的类型来说,Tree可以排序吗?? 以下程序对于Person类型来说,无法排序。因为没有指定Person对象之间的比较规则。 谁大谁小并没有说明。 java.lang.ClassCastException: class 进阶.集合05.Map集合.HashMap.Person cannot be cast to class java.lang.Comparable 出现这个异常的原因是: Person类没有实现java.lang.Comparable接口。 */ public class TreeSetTest02 { public static void main(String[] args) { Person p1 = new Person(32); Person p2 = new Person(20); Person p3 = new Person(30); Person p4 = new Person(25); TreeSet<Person> people = new TreeSet<>(); people.add(p1); people.add(p2); people.add(p3); people.add(p4); for (Person p :people) { System.out.println(p); } } } class Person{ int age; public Person(int age) { this.age = age; } @Override public String toString() { return "Person{" + "age=" + age + '}'; } }
综上所述,我们在自己制定k值得数据类型的时候,并将其存放在TreeMap集合中必须制定相应的比较规则,否在在存储时会报错。那么如何制定比较规则呢??有两种,如下所示。
TreeMap集合比较规则法一
在创建类时,继承java.lang.Comparable接口,并在类中重写了 compareTo()方法,如下所示:
import java.util.TreeSet;
public class TreeSetTest03 {
public static void main(String[] args) {
CusTomer c1 = new CusTomer(32);
CusTomer c2 = new CusTomer(20);
CusTomer c3 = new CusTomer(30);
CusTomer c4 = new CusTomer(25);
TreeSet<CusTomer> cusTomers = new TreeSet<>();
cusTomers.add(c1);
cusTomers.add(c2);
cusTomers.add(c3);
cusTomers.add(c4);
for (CusTomer c :cusTomers) {
System.out.println(c);
}
}
}
//放在TreeSet集合中的元素,需要实现java.lang.Comparable接口
//并且实现compareTo方法。equals可以不写
class CusTomer implements Comparable<CusTomer>{
int age;
public CusTomer(int age) {
this.age = age;
}
@Override
public String toString() {
return "CusTomer{" +
"age=" + age +
'}';
}
//需要在这个方法中编写比较的逻辑,按照说明进行比较
//k.compareTo(t.key)
//拿着参数k和集合中的每一个k进行比较,返回值可能是>0 ,<0 =0
@Override
public int compareTo(CusTomer o) {
//比较规则最终还是有程序员指定的:例如按照年龄升虚。或者按照年龄降序
// int age1 = this.age;
// int age2 = o.age;
// if (age1 == age2){
// return 0;
// }else if (age1 > age2){
// return 1;
// }else {
// return -1;
// }
return this.age - o.age;
}
}
TreeMap集合比较规则法二
自己创建比较器
import java.util.Comparator;
import java.util.TreeSet;
/*
最终的结论:
放到TreeSet集合或者TreeMap集合key部分的元素要想做到排序,包括两种方式:
第一种:放到集合中元素的对象类型,自己实现了java.lang.Comparable接口。
第二种:放到集合中的自定义对象,在构造TreeSet或者TreeMap集合的时候给他传入一个比较器对象,
或者在自定义对象实现了java.lang.Comparable接口
Comparable和Comparator怎么选择
当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议使用Comparable接口
如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口
Comparator接口接口设计符合OCP原则
*/
public class TreeSetTest04 {
public static void main(String[] args) {
//创建TreeSet集合的时候,需要使用比较器
//TreeSet<WuGui> wuGuis = new TreeSet<>();
//TreeSet<WuGui> wuGuis = new TreeSet<>(new WuGuiComparator());
//匿名内部内的方式
TreeSet<WuGui> wuGuis = new TreeSet<>(new Comparator<WuGui>() {
@Override
public int compare(WuGui o1, WuGui o2) {
return o1.age-o2.age;
}
});
wuGuis.add(new WuGui(1000));
wuGuis.add(new WuGui(800));
wuGuis.add(new WuGui(810));
for (WuGui wuGui:wuGuis) {
System.out.println(wuGui);
}
}
}
class WuGui{
int age;
public WuGui(int age) {
this.age = age;
}
@Override
public String toString() {
return "WuGui{" +
"age=" + age +
'}';
}
}
class WuGuiComparator implements Comparator<WuGui> {
@Override
public int compare(WuGui o1, WuGui o2) {
//指定比较规则
//按照年龄排序
return o1.age - o2.age;
}
}