01-集合(Map概述)
我们已经把集合的半壁江山讲完了,就是中间用蓝色框出来那部分:
接下来,我们要讲Map了。
Map和Colletion一样,它们都属于集合框架中的顶层接口,它们两者外部之间没有必然的联系(当然内部是有的)。
我们先看一下Map接口的特点。
Map接口在java.util这个包中。
Map集合:该集合存储键值对,一对一对往里存,而且要保证键的唯一性。
那么像这种一对一对往里面存的东西应用多吗?
很多呢。
我们知道,在存元素的时候,ArrayList的特点是可以给每个元素加上索引,这样取值比较方便一些,而Map集合就不单单是给元素加索引的问题了,它是直接给元素起名字都行。
其实和生活中一样,有结婚的,也有单身的,对于他们我们都要考虑到。而在Java中,Collection中装的都是单身汉,Map集合装的是夫妻。
用专业术语来说,Collection叫单列集合,而Map叫双列集合。
注意:值是可以重复的,键是不能重复的。
Map接口中的方法:
1,添加。
put(K key,V value)
putAll(Map<? extends K,? extends V>m)
2,删除。
clear()
remove(Object key)
3,判断。
containsValue(Object value)
containsKey(Object key)
isEmpty()
4,获取。
get(Object key)
size()
values()
entrySet()
keySet()
Map集合有三个子类:Hashtable、HashMap、TreeMap。
02-集合(Map子类对象特点)
在Map集合中有三个子类:Hashtable、HashMap、TreeMap。
我们先来看一下Hashtable:
HashMap:
Map
|——Hashtable:底层是哈希表数据结构,不可以存入null键null值,该集合是线程同步的。JDK1.0出现的。效率低。
|——HashMap:底层是哈希表数据结构,可以存入null键null值,该集合是不同步的。JDK1.2出现的。效率高。
|——TreeMap:底层是二叉树数据结构,线程不同步,可以用于给map集合中的键进行排序。
我们发现,Map和Set很像。其实,Set底层就是使用了Map集合。(Set集合的方法底层调用的都是Map集合的方法,Map可以存一对,Set去掉值,只剩一个)
03-集合(Map共性方法)
接下来说一下Map所涉及到的基本常见的方法。
右边new的是子类对象,在这里new谁都可以,主要是演示一下共性方法:
小tips:
我们也可以通过get方法的返回值(是否为null)来判断某一个键是否存在。
我们可以这样存吗?
我们试着获取一下:
发现是可以存进去的:
这就说明了在HashMap集合中,空是可以作为键存在的。
但这种情况很少见,没有人没事拿null作为键值。
接下来用一下这个方法:
用法:
打印结果:
但是我们的存放顺序是zhangsan1,zhangsan2,zhangsan3,null,而打印顺序和存放顺序不一样。所以注意HashMap不是按照存放顺序来排序的,我们说是无序的(但它是按照哈希值来排序的,并不是乱七八糟顺序喔)。
另外,我们发现,add方法返回的是boolean类型,而put方法是返回V的:
对于HashSet集合,添加了"adc"再添加"adc"的时候,返回值就是false,因为“adc”已经存在了。
那返回V是什么意思呢?
演示一下:
打印结果:
再存一个同键不同值的元素:
打印结果:
当存了相同键的时候,新的值会替换老的值,而put方法会将这个键所对应的原来的值返回来。第一次存的时候,由于还没有“01”键,所以返回为null。
04-集合(Map-keySet)
上节课,我们用get方法可以取到某一个键的值,但是这个方法比较单一,现在我们想把所有键的值都取出。
但是我们发现Map并没有迭代器,所以我们现在的思路是:先拿到所有的键,然后让每个键都执行get方法,这样所有值就都可以拿到了。
这个可以拿到所有键的方法,叫做keySet:
也就是说,它将Map集合中所有的键都存到Set当中去了,而Set具备迭代器,可以用迭代方式取出所有键。
演示一下:
现在我们拿到了所有键的值,有了键就可以通过Map集合的get方法获取其对应的值:
再用画图的方式描述一下过程(其实每个格子里存的都是引用地址值,这里为了方便理解直接将它们的值画进去了):
05-集合(Map-entrySet)
通过keySet方法,我们发先Map集合不需要迭代器,它的取出原理是将Map集合转成set集合,再通过迭代器取出。
接下来我们发现还有一个方法:
点击Map.Entry,发现它是一个接口,有自己的方法:
我们可以通过它的getKey和getValue方法获取键和值。
不多说,先写代码演示一下,将Map集合中的映射关系取出,存入到Set集合中:
虽然说这个方法写起来有一点点小麻烦,但是取出方式很爽~
画图表示一下它的取出过程:
其实存到Set中的是一个个关系:Map.Entry,所以Map.Entry又为我们提供了getKey和getValue方法来获取这个关系中的键和值。
打个比方,之前的keyset方法,取出来的是丈夫的身份证,然后通过丈夫的身份证号来找到他对应的妻子;而entrySet方法,取出来的是他们的结婚证,其中又提供了方法获取结婚证中丈夫的信息和妻子的信息。
这个方法用完之后,我们来解释一下什么叫Map.Entry<K,V>。
其实Entry也是一个接口,它是Map接口中的一个内部接口:
我们可以看到Map接口中有一个嵌套类(内部接口)摘要:
为什么把Entry定义成Map的一个子接口呢?为什么不把它定义到外部来呢?
我们想一想,是不是现有Map集合再有这个映射关系,所以这个映射关系是Map集合中的内部事物,只有有了Map集合才能有关系。而且,这个关系是在直接访问Map集合中的元素。综上,把它定义成了内部规则。
注意,Map.Entry<K,V>是静态的:
而能加static的接口都是内部接口,因为只有接口在成员位置上才能加静态修饰符。
总结一下:
map集合的两种取出方式:
1,Set<K> keySet:将Map中所有的键存入到Set集合,因为Set具备迭代器。所以可以用迭代方式取出所有的键,再根据get方法,获取每一个键对应的值。
2,Set<Map.Entry<k,v>> entrySet:将Map集合中的映射关系存入到了Set集合中,而这个关系的数据类型就是:Map.Entry。
06-集合(Map练习)
接下来做一个练习,需求:
我们分成三步完成:1,描述学生。2,定义Map容器,将学生作为键,地址作为值,存入。3,获取Map集合中的元素。
接下来用代码来实现:
Student描述好了,但是像这样的事物产生的会非常的多,一般是要存的,存的时候有可能就会存到HashSet中去,如果存到HashSet中去,就要判断一下元素的重复的条件。但是这个类不写hashCode和equals方法,它比较的就是元素的地址值,而我们应该比较的是按照自定义的元素自身的条件特点来判断唯一性。
所以,我们需要复写hashCode方法和equals方法:
因为Student可能会产生很多对象,所以这些对象也需要一些自然顺序,因此让它实现Comparable接口,从而具备可比性:
重写compareTo方法:
记住,凡事这种能同时创建多个对象需要被用的时候,一定要做这些动作:重写hashCode方法,重写equals方法,实现Comparable接口、重写compareTo方法。
对象描述完了,存就对了。
别忘了导入包哦:
存入&取出的代码(先用keySet来实现):
接下来用第二种取出方式entrySet来实现:
我们将hashCode方法和equals方法注释掉,存同一键值的元素试试:
运行后发现同一键值的两个元素都存进来了,没有保证键的唯一性,所以重写hashCode方法和equals方法是十分必要的。
07-集合(TreeMap练习)
上节的练习只是对学生对象和对应的地址进行了存取,这节课我们进行排序。
需求:对学生对象的年龄进行升序排序。
因为数据是以键值对形式存在的,所以要使用可以排序的Map集合:TreeMap。
Student类(写在MapTest.java文件中):
主函数(写在MapTest2.java中):
先编译MapTest.java(Student类所在的java文件):
再编译MapTest2.java(主函数所在的java文件):
运行,可以看到取出的是按照年龄来排序的:
下面想按照学生姓名来排序。
我们可以指定一个比较器:
接下来我们自定义一个姓名比较器:
主函数:
编译运行:
08-集合(TreeMap练习-字母出现的次数)
练习: "sdfgzxcvasdfxcvdf"获取该字符中的字母出现的次数。
希望打印结果:a(1)c(2)......
通过结果发现,每一个字母都有对应的次数。
说明字母和次数之间都有映射关系。
注意了,当发现有映射关系时,可以选择Map集合,因为Map集合中存放的就是映射关系。
什么时候使用Map集合呢?
当数据之间存在着映射关系时,就要先想Map集合。
接下来将思路画个图:
把思路整理一下:
1,将字符串转换成字符数组,因为要对每一个字母进行操作。
2,定义一个Map集合,因为打印结果的字母有顺序,所以使用TreeMap集合。
3,遍历字符数组,将每一个字母作为键去查Map集合。 如果返回null,将该字母和1存入到Map集合中。 如果返回不是null,说明该字母在Map集合中已经存在并有对应次数。 那么就获取该次数并进行自增,然后将该字母和自增后的次数存入到Map集合中,覆 盖掉键原先所对应的值。
4,将Map集合中的数据变成指定的字符串形式返回。
字母是char型,数字是int型,所以我们在建立TreeMap对象时要存<char,int>吗?
不是的。
因为泛型里面接收的都是引用数据类型,所以必须找到其基本的数据类型包装类,不能直接往里面存char和int。
应该这样存:
完整实现代码:
主函数中调用:
运行:
运行结果是没有问题的,现在我们需要将结果打印成a(4)b(2)......这样的形式。
想换成这种形式该怎么做呢?
我们遍历完之后将结果存起来。
怎么存?往哪里存?用什么容器?
缓冲区。
缓冲区里什么类型都可以放,而且最终变成字符串。
代码实现(紧接着之前的代码写):
主函数中接收到一个返回的字符串,并打印字符串:
运行:
接下来发现一个小问题,在这里,这两个put的代码比较重复:
我们给它优化一下:
注意:count变量定义在循环外比较好哦,这样只需要开辟一次空间,如果定义在循环内部,每次循环都会开辟、释放一次count空间。
还有个小问题,字符串中可能存在"+"、"-"、"1"这样非字母的字符,我们运行发现它们都会被存进去:
但是我们并不需要它们,该怎么做呢?
每次取出字符的时候都判断一下就OK了:
09-集合(Map扩展)
接下来说一下Map集合的扩展知识。
Map集合被使用时因为具备映射关系。
现在有一个例子,一个学校有多个教室,每个教室中又有多个学生,每个学生又有学号和姓名:
我们画图来表示它们的关系:
这是一个集合嵌套集合的关系,比如yureban和它右边那个方框存在映射关系,而这个方框中,学号和姓名又存在着映射关系。
代码实现:
现在想取出czbk里的所有学生。
拿到了所有教室:
接着拿每个教室里学生的信息:
OK的。
我们都知道,学号和姓名通常会封装成学生对象。那我们这个例子可以改成这样:
这一个班里对应好多学生对象,而学生对象就是一个对象,这时候就不用Map集合来存了。
画图表示一下:
代码实现:
运行:
成功。