Map集合学习
备注:Map:一次添加一对元素。Collection一次添加一个元素。
一、Map说明
Map也称为双列集合,Collection集合称为单列集合。
其实map集合中存储的就是键值对。
map集合中必须保证键的唯一性。
Map集合不具备迭代器。迭代器只有Collection系列的集合才有
二、Map接口的常用方法:
1,添加。
value put(key,value):返回前一个和key关联的值(上一个key对象的value值),如果没有返回null.
2,删除。
void clear():清空map集合。
value remove(key):根据指定的key翻出这个键值对。
3,判断。
boolean containsKey(key):
boolean containsValue(value):
boolean isEmpty();
4,获取。
value get(key):通过键获取值,如果没有该键返回null。当然可以通过返回null,来判断是否包含指定键。
int size(): 获取键值对的个数。
三、Map接口常用的实现类:
|--Hashtable :内部结构是哈希表,是同步的。不允许null作为键,不允许null作为值。
|--Properties:用来存储键值对型的配置文件的信息,可以和IO技术相结合。
|--HashMap : 内部结构是哈希表,不是同步的。允许null作为键,允许null作为值。
|--LinkHashMap:内部结构是哈希表和链接列表实现,具有可预知的迭代顺序(有序的)
|--TreeMap : 内部结构是二叉树,不是同步的。可以对Map集合中的键进行排序。
四、set集合和map集合关系
备注:
学习Set集合应该注意:Set集合的底层代码是由Map集合实现的。
为什么会有set集合?
因为:Map的集合中存储的是键值对,其中键在Map集合中是唯一的,因此将Map中的键作为set中的元素进行存储,就可以保证单列集合的唯一性了,这样就出现了没有重复元素的set集合了。
因为现实世界中因为有这样的需求,所以就有了set集合的存在
推论:
因为Set集合的底层代码是由Map集合实现的,所以set的子类xxxSet的底层实现是Map的子类xxxMap。
而我查看java的源码发现,这个推论在一定程度上是成立的。因为我发现:1)HashMap是HashSet的底层实现。2)TreeMap是TreeSet的底层实现。3)LinkedHashMap是LinkedHashSet的底层实现
1、HashMap是HashSet的底层实现
public HashSet() {
map = new HashMap<>();
}
2、TreeMap是TreeSet的底层实现
public TreeSet() {
this(new TreeMap<E,Object>());
}
3、LinkedHashMap是LinkedHashSet的底层实现
public LinkedHashSet() {
super(16, .75f, true);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
五、map遍历方法
1、方法一: Set<K> keySet()方法
取出map中的所有元素。
原理,通过keySet方法获取map中所有的键所在的Set集合,在通过Set的迭代器获取到每一个键,
在对每一个键通过map集合的get方法获取其对应的值即可。
思考:为什么是返回Set集合,而不是返回List集合呢?
map中的key是唯一的,而set集合有一个特点:元素是唯一的,所以返回set集合符合了map中key的特点,因此使用set集合。而List集合的元素则不保证集合元素的唯一性,所以没有考虑使用List集合。此处的选择,目的只有一个:确保拿到的key集合是唯一的,不重复的。
2、方法二: Set<Map.Entry<K,V>>entrySet()方法
通过Map转成set就可以迭代。
找到了另一个方法:entrySet。
该方法将键和值的映射关系作为对象存储到了Set集合中,而这个映射关系的类型就是Map.Entry类型
3、Map.Entry<K,V>之思
备注:内部类,内部接口需要进一步学习
(1)Map接口与Map.Entry接口
Map接口:外部接口(外部规则)
Map.Entry接口:内部接口(内部规则)--包含键和值的内容,是键和值的映射关系对象
(2)先后关系
先有Map接口中的Map(键值对)的映射,才有映射关系,而Entry接口做的就是把这个关系封装成了对象,且这个关系是访问Map中的键和值。
(3)外部规则与内部规则
外部规则里面有内部规则,内部规则可以直接访问内部规则的内部
(4)示例代码:
//内部接口:MyEntry 外部接口:MyMap
interface MyMap{
public static interface MyEntry{//内部接口
void get();
}
}
class MyDemo implements MyMap.MyEntry{
public void get(){}
}
//内部类:Innner
class Outer{
static class Inner{
static void show(){}
}
}
4、方法三:values方法
原理:取出Map集合中的所有值的Collection集合
5、三种方法的集合遍历案例
(1)代码实现
package map;
/*
* map集合遍历
* 1.遍历方法一:KeySet方法
* 2.遍历方法二:Map.Entry接口的 entrySet方法
* 3.遍历方法三:values方法
*/
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class MapDemo {
public static void main(String[] args) {
// Map map=new HashMap();
Map<Integer,String> map=new HashMap<Integer,String>();//泛型
map.put(1, "张三");
map.put(2, "李四");
map.put(3, "王五");
map.put(4, "世间");
map.put(5, "时间");
//1.遍历方法一:KeySet方法
Set<Integer> keySet=map.keySet();//拿到键的集合
Iterator<Integer> it=keySet.iterator();//迭代器
System.out.println("遍历方法一:KeySet方法");
while(it.hasNext()){
int key=(int) it.next();
String value=map.get(key);
System.out.println(key+":"+value);
}
//2.遍历方法二:Map.Entry接口的 entrySet方法
Set<Map.Entry<Integer,String>> bothResult=map.entrySet();//返回map集合中包含的键和值的映射关系
Iterator<Entry<Integer,String>>it2=bothResult.iterator();//迭代器
System.out.println("遍历方法二:Map.Entry接口的 entrySet方法");
while(it2.hasNext()){
Entry<Integer,String> entry=(Entry<Integer, String>) it2.next();
int key=entry.getKey();
String value=entry.getValue();
System.out.println(key+"::"+value);
}
//3.遍历方法三:values方法
Collection<String> cols=map.values();
Iterator<String> it3=cols.iterator();
System.out.println("遍历方法三:values方法");
while(it3.hasNext()){
String value=it3.next();
System.out.println(value);
}
}
}
(2)结果
遍历方法一:KeySet方法
1:张三
2:李四
3:王五
4:世间
5:时间
遍历方法二:Map.Entry接口的 entrySet方法
1::张三
2::李四
3::王五
4::世间
5::时间
遍历方法三:values方法
张三
李四
王五
世间
时间
六、自定义对象存map集合,map集合保证键唯一性探讨
联系set集合
set集合中的TreeSet和HashSet中都有保证存入元素唯一的方法,而map集合如何保证存入map集合的键(key)唯一呢?
因为set集合的底层是通过map集合实现。
而且我们知道:
TreeMap是TreeSet的底层实现
HashMap是HashSet的底层实现
所以保证唯一,通过类比的方式得出:
HashMap(保证键唯一)与HashSet(保证元素唯一)方式一样
是通过对象的hashCode和equals方法来完成对象唯一性的。
TreeMap(保证键唯一)与TreeSet(保证元素唯一)方式一样
实现Comparable接口的compareTo方法 或者 实现 Comparator接口,覆盖compare方法
七、HashMap集合
1、HashMap集合说明
HashMap : 内部结构是哈希表,不是同步的。允许null作为键,允许null作为值。
2、HashMap集合保证键(key)唯一
HashMap是HashSet的底层实现
所以保证唯一,通过类比的方式得出:
HashMap(保证键唯一)与HashSet(保证元素唯一)方式一样
是通过对象的hashCode和equals方法来完成对象唯一性的。
3、自定义对象与非自定义对象之HashMap集合使用
(1)非自定义对象:
HashMap集合中存储非自定义对象,即:jdk中已有的对象(比如:String对象,Integer对象),这些对象覆盖了Object对象的hashCode方法和equals方法,可以保证这些对象插入HashMap集合时,键值是唯一的。
(2)自定义对象:
HashMap集合中存储自定义对象,即:自己写的对象。我们要想保证这样的自定义对象作为键时,键值是唯一的,我们需要给这个自定义对象覆盖Object对象的hashCode方法和equals方法。这样,才可以
可以保证这些自定义对象插入HashMap集合时,键值是唯一的。
4、HashMap案例
4.1案例一:将学生对象和学生的归属地通过键与值存储到map集合
(1)自定义对象Student类
class Student{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int hashCode() {
//乘以一些奇数,增大随机性
return this.name.hashCode()+age*37;
}
/*
* 1.比较对象内容是否相同
* 2.健壮性判断(1)对象地址是否相同,相同则忽略比较(2)对象类型是否正确,错误则抛出异常
*/
@Override
public boolean equals(Object obj) {
if(this==obj){
return true;//同一个学生
}
if(!(obj instanceof Student)){
throw new RuntimeException("类型错误");
}
//姓名和年龄都相同的视为同一个学生
Student s=(Student)obj;
//同一个学生
if(this.getName().equals(s.getName())&&this.getAge()==s.getAge()){
return true;
}
return false;//不是同一个学生
}
}
(2)HashMap测试类
package map;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import list_set.People;
/*
* 将学生对象和学生的归属地通过键与值存储到map集合中
*
* 判断相同依据:姓名和年龄都相同的视为同一个学生
*
* HashMap 类比 HashSet
* 保证唯一:是通过对象的hashCode和equals方法来完成对象唯一性的
*/
public class HashMapDemo {
public static void main(String[] args) {
// HashMap hm=new HashMap<Student,String>();
HashMap<Student,String> hm=new HashMap<Student,String>();
hm.put(new Student("张三",20),"杭州");
hm.put(new Student("张三",20),"黑龙江");//和上面的键重复了,测试键是否插入唯一
//结果:只有一个new Student("张三",20)作为键,说明使用成功了
hm.put(new Student("李四",20),"广州");
hm.put(new Student("王五",20),"天津");
hm.put(new Student("李六",20),"北京");
hm.put(new Student("世间",20),"上海");
//遍历
Set<Map.Entry<Student,String>>set=hm.entrySet();
for(Map.Entry<Student, String> map:set){
Student s=map.getKey();
String name=s.getName();//名字
int age=s.getAge();//年龄
String area=map.getValue();//归属地
System.out.println(name+":"+age+":"+area);
}
}
}
(3)结果:(保证了自定义对象的键唯一)
王五:20:天津
张三:20:黑龙江
世间:20:上海
李四:20:广州
李六:20:北京
4.2案例二:map对象存jdk已有对象
(1)测试类
package map;
import java.util.HashMap;
import java.util.Set;
public class HashMapDemo2 {
public static void main(String[] args) {
HashMap<Integer,String> hm=new HashMap<Integer,String>();
hm.put(1, "张三");
hm.put(1, "张三");//重复键,覆盖原来的值
hm.put(2, "张三3");
hm.put(3, "张三5");
Set<Integer> set=hm.keySet();
for(Integer i:set){
int key=i;
String value=hm.get(key);
System.out.println(i+":"+value);
}
}
}
(2)结果
Integer对象已经覆盖了Object对象的equals和hashCode方法,我们直接把Integer对象作为map集合的键插入集合中即可保证键的唯一性,无需自己做额外的操作
1:张三
2:张三3
3:张三5
八、TreeMap集合
1、TreeMap集合说明
TreeMap : 内部结构是二叉树,不是同步的。可以对Map集合中的键进行排序。
2、TreeMap集合保证键(key)唯一
TreeMap是TreeSet的底层实现
所以保证唯一,通过类比的方式得出:
TreeMap(保证键唯一)与TreeSet(保证元素唯一)方式一样
实现Comparable接口的compareTo方法 或者 实现 Comparator接口,覆盖compare方法
3、自定义对象与非自定义对象之TreeMap集合使用
(1)TreeMap集合排序方式一:让元素自身具备比较功能
【1】当元素为自定义对象(eg:Person类)时,让元素自身具备比较功能,实现Comparable接口,覆盖compareTo方法
【2】当元素为字符串对象(eg:“abd”)或者jdk中的对象有实现Comparable接口的方法时,由于这些对象本身已经实现了Comparable接口的compareTo方法,本身就具备比较功能,直接使用TreeMap即可。
(2)TreeMap集合排序方式二:让集合自身具备比较功能
让集合自身具备比较功能,定义一个类实现Comparator接口,覆盖compare方法。将该类对象作为参数传递给TreeMap集合的构造函数。
(3)排序优先
当TreeMap集合有两种比较功能时,TreeMap集合排序方式二(集合自身具备比较功能) 优先于 TreeMap集合排序方式一(元素本身具备比较功能)
4、TreeMap案例—按自定义对象的姓名属性排序
(1)实现方式一:
TreeMap集合排序方式一:让元素自身具备比较功能
【1】自定义对象XueSheng(实现Comparable接口)
package map;
//学生对象
public class XueSheng implements Comparable{
private String name;
private int age;
public XueSheng(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Object o) {
XueSheng s=(XueSheng)o;
//按自定义对象的姓名属性排序
return this.getName().compareTo(s.getName());
}
}
【2】TreeMap测试类
package map;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/*
* 将学生对象和学生的归属地通过键与值存储到map集合中
*
* 按自定义对象的姓名属性排序
* TreeMap集合排序方式一:让元素自身具备比较功能
*/
public class TreeMapDemo {
public static void main(String[] args) {
TreeMap<XueSheng,String> tm=new TreeMap<XueSheng,String>();
tm.put(new XueSheng("张三",20),"杭州");
tm.put(new XueSheng("张三",20),"黑龙江");//和上面的键重复了,测试键是否插入唯一
//结果:只有一个new XueSheng("张三",20)作为键,说明使用成功了
tm.put(new XueSheng("李四",20),"广州");
tm.put(new XueSheng("王五",20),"天津");
tm.put(new XueSheng("李六",20),"北京");
tm.put(new XueSheng("世间",20),"上海");
//遍历
Set<Map.Entry<XueSheng,String>> set=tm.entrySet();
for(Map.Entry<XueSheng, String> map:set){
XueShengs=map.getKey();
String name=s.getName();//名字
int age=s.getAge();//年龄
String area=map.getValue();//归属地
System.out.println(name+":"+age+":"+area);
}
}
}
【3】结果:(保证了自定义对象的键唯一)
世间:20:上海
张三:20:黑龙江
李六:20:北京
李四:20:广州
王五:20:天津
(2)实现方式二:
TreeMap集合排序方式二:让集合自身具备比较功能
【1】自定义对象XueSheng
package map;
//学生对象
public class XueSheng{
private String name;
private int age;
public XueSheng(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
【2】ComparatorByName比较器类
package map;
import java.util.Comparator;
//比较器
//实现 按自定义对象的姓名属性排序
public class ComparatorByName implements Comparator{
@Override
public int compare(Object o1, Object o2) {
XueSheng s1=(XueSheng)o1;
XueSheng s2=(XueSheng)o2;
// 按自定义对象的姓名属性排序
return s1.getName().compareTo(s2.getName());
}
}
【3】TreeMap测试类
package map;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/*
* 将学生对象和学生的归属地通过键与值存储到map集合中
*
* 按自定义对象的姓名属性排序
* TreeMap集合排序方式二:让集合自身具备比较功能
*/
public class TreeMapDemo2 {
public static void main(String[] args) {
TreeMap<XueSheng,String> tm=new TreeMap<XueSheng,String>(new ComparatorByName()); //比较器的使用
tm.put(new XueSheng("张三",20),"杭州");
tm.put(new XueSheng("张三",20),"黑龙江2");//和上面的键重复了,测试键是否插入唯一
//结果:只有一个new XueSheng("张三",20)作为键,说明使用成功了
tm.put(new XueSheng("李四",20),"广州");
tm.put(new XueSheng("王五",20),"天津");
tm.put(new XueSheng("李六",20),"北京");
tm.put(new XueSheng("世间",20),"上海");
//遍历
Set<Map.Entry<XueSheng,String>> set=tm.entrySet();
for(Map.Entry<XueSheng, String> map:set){
XueSheng s=map.getKey();
String name=s.getName();//名字
int age=s.getAge();//年龄
String area=map.getValue();//归属地
System.out.println(name+":"+age+":"+area);
}
}
}
【4】结果:
世间:20:上海
张三:20:黑龙江2
李六:20:北京
李四:20:广州
王五:20:天津
九、LinkedHashMap集合(有序)
LinkHashMap:【父类是HashMap】内部结构是哈希表和链接列表实现,具有可预知的迭代顺序(有序的)
联系set集合
LinkedHashSet的底层是由LinkedHashMap实现的。
说明:
如果要保证LinkedHashMap集合键的唯一性,可以参考父类HashMap的实现方式,它们俩实现是一样的。
案例
代码实现:
package map;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
/*
* 有序 插入顺序和遍历获取顺序一致
*/
public class LinkedHashMapDemo {
public static void main(String[] args) {
LinkedHashMap<String,Integer> lhm=new LinkedHashMap<String,Integer>();
lhm.put("张三", 1023);
lhm.put("lisi", 1021);
lhm.put("王五", 1022);
lhm.put("你好", 1025);
lhm.put("世间", 1026);
Iterator<Map.Entry<String,Integer>> it= lhm.entrySet().iterator();
while(it.hasNext()){
Entry<String,Integer> ele=it.next();
String key = ele.getKey();
Integer value = ele.getValue();
System.out.println(key+":"+value);
}
}
}
结果:(有序)
张三:1023
lisi:1021
王五:1022
你好:1025
世间:1026
十、实现功能--记录字母次数
代码实现:
package map;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.TreeMap;
/*
* 记录字母次数
*
* 字符串String s="kasdgasdvoasdfnasgsdgaegbweeegwef",获取该字符串中,每一个字母出现的次数。
* 要求打印结果是:a(5)b(1)...;
*
* 分析:
* 1、记录结果包括字母和次数,确定容器为map集合,以键值对形式存储
* 2、因为输出结果是按照字母的字典顺序输出的,所以选择使用TreeMap集合
* 3、该实现特点:字母(key)不变,次数(value)不断变化
* 实现思路:
* 1、key 是字母 value 是次数
* 2、将字符串转为字符数组,然后给字符数组进行遍历
* 3、遍历字符数组时,分两种情况考虑:
* (1)当map集合中不存在当前字母(key),即把当前字母存入,并设置其value值为1【表示第一次出现】
* (2)当map集合存在当前字母(key),取出该字母对应的value,进行加1(value=value+1)操作【表示又一次出现】
* 4、进行遍历输出map集合中的key和value即可
*/
public class CountByCharacter {
public static void main(String[] args) {
String s="12kasdgasdv44oasdfnasgsdgaegbweeegwef";
char[] sArray=s.toCharArray();//字符串变字符数组
TreeMap<String,Integer> tm=arrayToMap(sArray);//计算字母出现次数,并将字母和次数保存在map集合中
listCount(tm);//遍历输出字符串中的字母和次数情况
}
//计算字母出现次数,并将字母和次数保存在map集合中
private static TreeMap<String, Integer>arrayToMap(char[] sArray) {
TreeMap<String,Integer> tm=new TreeMap<String,Integer>();
//遍历字符数组
for(int i=0;i<sArray.length;i++){
char letter=sArray[i];
//健壮性判断,key必须是字母,否则不进行存储和计算
boolean result=keyIsCharacter(letter);
if(!result){
continue;//退出该次循环,直接进入下一次循环
}
//键
String key=letter+"";//字符letter转为String类型
//默认情况设置为1(默认情况:key第一次出现)
int value=1;//value=1
//key第二次出现
if(tm.get(key)!=null){
//value=value+1
value=tm.get(key)+1;//tm.get(key)就是上一个value值
}
//key是char类型,需要转为String类型:key+""
tm.put(key, value);//设置key和value
}
return tm;
}
//判断key是否是字母,如果是,返回true;否则返回false
private static boolean keyIsCharacter(char key) {
//key是字母
if((key>='A'&&key<='Z')||(key>='a'&&key<='z'))
return true;
return false;
}
//遍历输出字符串中的字母和次数情况
private static void listCount(TreeMap<String, Integer> tm) {
Iterator<Entry<String,Integer>> it=tm.entrySet().iterator();
while(it.hasNext()){
Entry<String,Integer> entry=it.next();
String key=entry.getKey();
int value=entry.getValue();
System.out.print(key+"("+value+") ");
}
}
}结果:
a(5) b(1) d(4)e(5) f(2) g(5) k(1) n(1) o(1) s(5) v(1) w(2)
结论:
不同容器有不同的应用场景,在不同情况下,有些容器实现起来极其简单和高效,所以我们要努力挖掘出该应用场景下的关键信息,然后做出最佳选择
1884

被折叠的 条评论
为什么被折叠?



