【Java基础】——集合类(下)

时间:2023-02-15 10:15:37

一、映射表(Map)数据结构

集是一个集合,它可以快速的查找现有的元素。但是,要查看一个元素,需要有查找元素的精确副本。这不是一种非常通用的查找方式。通常,我们知道某些键的信息,并想要查找与之对应的元素。映射表(map)数据结构就是为此设计的。映射表用来存放键/值对。如果提供了键,就能找到值。Java类库为映射表提供了两个通用的实现:HashMap和TreeMap。这两个类都实现了Map接口。

二、Map<K,V>接口

1、Map<K,V>简介

a)、Map接口是用于保存具有映射关系的数据,Map存储的是键值对的形式的元素,它的每一个元素,都是由键和值两个元素组成,它的键要求唯一,值可以重复,将键映射到值的对象,一个映射不能包含重复的键,每个键最多只能映射到一个值。

b)、迭代Map接口中的元素不存在直截了当的方法,如果要查询某个Map以了解其哪些元素满足特定的查询,或要迭代其它所有元素。则我们首先需要获取该Map的视图。Map接口提供了三种Collection视图:

  • 所有键值对(键值对)entrySet()
  • 所有键(键集)keySet()
  • 值(值集),values()

三种视图如下表所示:

entrySet() 返回 Map 中所包含映射的 Set 视图。Set 中的每个元素都是一个 Map.Entry 对象,可以使用 getKey() 和 getValue() 方法(还有一个 setValue() 方法)访问后者的键元素和值元素
keySet() 返回 Map 中所包含键的 Set 视图。删除 Set 中的元素还将删除 Map 中相应的映射(键和值)
values() 返回 map 中所包含值的 Collection 视图。删除 Collection 中的元素还将删除 Map 中相应的映射(键和值)
通过这三种返回视图的方法返回的对象,我们可以遍历Map的元素,也可以进行删除操作。

c)、Map接口中常用的实现类

  • HashMap:底层是哈希表数据结构,允许使用null值和null键,实现不同步,效率较高。不保证映射顺序。
  • Hashtable:底层是哈希表数据结构,不允许存入null值和null键,实现同步,效率低,已被HashMap替代。
  • TreeMap:基于红黑树实现,底层是树结构,线程不同步,可以用于给Map集合中的键进行排序,排序原理与TreeSet相似。Set底层就是用Map实现的。

2、Map接口的共性方法

a)、添加方法:

  • put(K key,V value):将指定的值与此映射中的指定键关联
  • putAll(Map<? extendsK,? extendsV> m):从指定映射中将所有映射关系复制到此映射中

b)、删除方法:

  • remove(Object key):如果存在一个键的映射关系,则将其从此映射中移除
  • clear():从此映射中移除所有映射关系

c)、判断方法:

  • containsKey(Object key):如果此映射包含指定键的映射关系,则返回 true
  • containsValue(Object value):如果此映射将一个或多个键映射到指定值,则返回 true
  • equals(Object o):比较指定的对象与此映射是否相等
  • isEmpty():如果此映射未包含键-值映射关系,则返回 true

d)、获取方法:

  • get(Object key):返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null
  • hashCode():返回此映射的哈希码值
  • size():返回此映射中的键-值映射关系数
  • Collection<V> values():返回此映射中包含的值的Collection 视图
  • Set<Map.Entry<K,V>> entrySet():返回此映射中包含的映射关系的Set 视图。
  • Set<K> keySet():返回此映射中包含的键的 Set 视图。

Map集合常用方法示例:

import java.util.*;
/*
Map集合:该集合存储键值对,一对一对往里存,而且要保证元素的唯一性。
常用方法演示。
*/
public class MapTest1{
public static void main(String[] args){
//创建一个Map集合
Map<String,String> map = new HashMap<String,String>();
//添加元素
map.put("01","zhangsan1");
map.put("02","zhangsan2");
map.put("03","zhangsan3");

/*
添加元素,如果出现添加相同的键。那么后添加的值会覆盖原有键对
应值。put方法会返回其原来的值。
*/
System.out.println(map.put("01","huangxiang"));//返回zhangsan1

//判断是否存在改键。结果:containsKey:false
System.out.println("containsKey:"+map.containsKey("022"));

//删除元素,remove(key)方法,返回删除的键对应的值。
//System.out.println("remove:"+map.remove("02"));//remove:zhangsan2

//获取指定键的值。
System.out.println("get:"+map.get("01"));//get:huangxiang
//如果此集合不包含该键的映射关系,则返回null。
map.put("04",null);
System.out.println("get:"+map.get("04"));//get:null

//获取map集合中所有的值。
Collection<String> coll = map.values();
System.out.println(coll);//[null, huangxiang, zhangsan2, zhangsan3]
System.out.println(map);//{04=null, 01=huangxiang, 02=zhangsan2, 03=zhangsan3}
}
}

三、HashMap、Hashtable和TreeMap类

1、HashMap和Hashtable概述

HashMap和Hashtable之间的关系完全类似与ArrayList与Vector的关系。两个底层都是哈希表数据结构,两者的区别在于:

  • Hashtable不可以存入null键和null值,HashMap可以存入。
  • Hashtable是线程安全实现,HashMap是线程不安全的实现类,HashMap的性能可能较高点。

所以,这里主要对HashMap进行分析。

2、TreeMap类概述

TreeMap类底层的实现是二叉树结构。线程是不同步的,其特点是,TreeMap会对集合中的所有Key进行排序,排序的有两种方式:

  • 自然排序:TreeMap的所有key必须实现Compareable接口,而且所有key必须是同一类对象。
  • 自定义比较器排序:通过自定义比较器的方式,在创建集合时,将比较器作为对象参数传入。

3、HashMap构造方法摘要

  • HashMap():构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。
  • HashMap(int initialCapacity):构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。
  • HashMap(int initialCapacity, float loadFactor):构造一个带指定初始容量和加载因子的空 HashMap。
  • HashMap(Map<? extendsK,? extendsV> m):构造一个映射关系与指定 Map 相同的新 HashMap。

4、TreeMap构造方法摘要

  • TreeMap():使用键的自然顺序构造一个新的、空的树映射。
  • TreeMap(Comparator<? superK> comparator):构造一个新的、空的树映射,该映射根据给定比较器进行排序。
  • TreeMap(Map<? extendsK,? extendsV> m):构造一个与给定映射具有相同映射关系的新的树映射,该映射根据其键的自然顺序 进行排序。
  • TreeMap(SortedMap<K,? extends V> m):构造一个与指定有序映射具有相同映射关系和相同排序顺序的新的树映射。

5、Map集合的两种取出方式

由于在Map集合中没有Iterator迭代器,要想遍历取出Map集合中的元素可以通过以下两个方法:

  • Set<K> keySet():将map中所有的键存到set集合。因为set具有迭代器。通过迭代方式取出所有的键,再根据get方法,取出每一个键对应的值。
  • Set<Map.Entry<K,V>>   entrySet():将map集合中的映射关系存入到了set集合中,而这个关系的数据类型就是:Map.Entry。

Map集合两种取出方法的代码示例:

import java.util.*;
/*
map集合的两种取出方式:
1、keyset:将map中所有的键存到set集合。因为set具有迭代器。通过迭代方式取出所有
的键,再根据get方法,取出每一个键对应的值。
Map集合取出原理:将map集合转成set集合。再通过迭代器取出。
2、entryset:将map集合中的映射关系存入到了set集合中,
而这个关系的数据类型就是:Map.Entry。
*/
public class MapTest2{
public static void main(String[] args){
//创建map集合
Map<String,String> map = new HashMap<String,String>();
//添加元素
map.put("01","huangxiang");
map.put("02","huangxiang2");
map.put("03","huangxiang3");
map.put("04","huangxiang4");

//将map集合中的映射关系取出,存入到Set集合中。
Set<Map.Entry<String,String>> entrySet = map.entrySet();
//返回set迭代器,通过Map.entrySet 方法返回映射的 collection 视图。
Iterator<Map.Entry<String,String>> it = entrySet.iterator();
//迭代取出
while(it.hasNext()){
Map.Entry<String,String> me = it.next();
//通过Map.Entty接口提供的getKey()和getValue()方法取出键和值。
String key = me.getKey();
String value = me.getValue();
//打印输出。
System.out.println(key+"::"+value);
}
/*输出结果
04::huangxiang4
01::huangxiang1
02::huangxiang2
03::huangxiang3
*/
//先获取map集合的所有键的set集合
Set<String> keySet = map.keySet();
//有了set集合,就可以获取其迭代器
Iterator<String> it2 = keySet.iterator();
//通过迭代器获取其键值
while(it2.hasNext()){
String key = it2.next();
String value = map.get(key);
System.out.println("key:"+key+"::value:"+value);
}
/*输出结果:
key:04::value:huangxiang4
key:01::value:huangxiang1
key:02::value:huangxiang2
key:03::value:huangxiang3
*/
}
}

6、HashMap应用示例

import java.util.*;
/*
练习:每一个学生都有对应的归属地。
学生Student,地址String
学生属性:姓名和年龄
注意:姓名和年龄相同的视为同一个学生,保证学生的唯一性。

1、描述学生
2、定义map容器。将学生作为键,地址作为值存入。
3、获取map集合中的元素
*/
public class MapTest3{
public static void main(String[] args){
HashMap<Student,String> hm = new HashMap<Student,String>();

hm.put(new Student("zhangsan",25),"shanghai");
hm.put(new Student("lisi",28),"beijing");
hm.put(new Student("wangwu",26),"tianjin");
hm.put(new Student("zhaoliu",23),"chengdu");
hm.put(new Student("zhouqi",35),"dalian");

//第一种取出方式 keySet

//1、通过keyset()方法获取键值Student的Set集合。
Set<Student> keySet = hm.keySet();
//2、返回键值Student的Set集合的迭代器
Iterator<Student> it = keySet.iterator();
//迭代获取键值
while(it.hasNext()){
Student stu = it.next();
//有了键值,通过map集合的get方法获取与键值对应的value值
String addr = hm.get(stu);

System.out.println(stu+"::::"+addr);
}

//第二种取出方式:entrSet
Set<Map.Entry<Student,String>> entry = hm.entrySet();
Iterator<Map.Entry<Student,String>> iter = entry.iterator();
//3、迭代获取Map.Entry
while(iter.hasNext()){
Map.Entry<Student,String> me = iter.next();
Student stu = me.getKey();
String addr = me.getValue();

System.out.println(stu+"......."+addr);
}
}
}
//自定义学生类实现Comparable接口。
class Student implements Comparable<Student>{
private String name;
private int age;
//构造函数初始化
Student(String name,int age){
this.name = name;
this.age = age;
}
//提供获取方法
public String getName(){
return name;
}

public int getAge(){
return age;
}
//自定义输出内容
public String toString(){
return name+"::::"+age;
}
//复写hashCode方法
public int hashCode(){
return name.hashCode()+age*52;
}
//复写equals方法
public boolean equals(Object obj){
//健壮性判断
if(!(obj instanceof Student))
throw new ClassCastException("类型不匹配");
Student s = (Student)obj;
return this.name.equals(s.name) && this.age == s.age;
}
//复写compareTo方法
public int compareTo(Student s){
int num = new Integer(this.age).compareTo(new Integer(s.age));
if(num == 0)
return this.name.compareTo(s.name);
return num;
}

}

7、 TreeMap应用举例:对学生的年龄按照升序排列

import java.util.*;
/*
需求:对学生对象的年龄进行升序排序。

因为数据是以键值对形式存在的。
所以要使用可以排序的Map集合。TreeMap。
*/
public class MapTest4{
public static void main(String[] args){
/*
创建一个TreeMap集合。可以用复写comparable接口中的compareTo方法进行
比较排序。也可以自定义一个比较器实现comparator接口进行比较。
*/
//自定义比较器可以作为对象参数传入,也可以使用匿名内部类。
TreeMap<Student,String> tm = new TreeMap<Student,String>(/*new StuNameComparator()*/);
//添加元素
tm.put(new Student("zhangsan",25),"shanghai");
tm.put(new Student("lisi",28),"beijing");
tm.put(new Student("wangwu",26),"tianjin");
tm.put(new Student("zhaoliu",23),"chengdu");
tm.put(new Student("zhouqi",35),"dalian");
//entrySet取出方式
Set<Map.Entry<Student,String>> entrySet = tm.entrySet();

Iterator<Map.Entry<Student,String>> it = entrySet.iterator();

while(it.hasNext()){
Map.Entry<Student,String> me = it.next();
Student stu = me.getKey();
String addr = me.getValue();
System.out.println(stu+"....."+addr);
}
}
}
//自定义比较器:以字母的自然排序为主。。因为下面的学生类中实现了按年龄排序。
//所以这个比较器实现了以字母的自然顺序排序。。
class StuNameComparator implements Comparator<Student>{
public int compare(Student s1,Student s2){
int num = s1.getName().compareTo(s2.getName());
if(num == 0)
return new Integer(s1.getAge()).compareTo(new Integer(s2.getAge()));
return num;
}
}
//学生类
class Student implements Comparable<Student>{
private String name;
private int age;
//构造函数初始化
Student(String name,int age){
this.name = name;
this.age = age;
}

public String getName(){
return name;
}

public int getAge(){
return age;
}

public String toString(){
return name+"::::"+age;
}
//复写hashCode()方法
public int hashCode(){
return name.hashCode()+age*52;
}
//复写equals()方法
public boolean equals(Object obj){
if(!(obj instanceof Student))
throw new ClassCastException("类型不匹配");
Student s = (Student)obj;
return this.name.equals(s.name) && this.age == s.age;
}
//复写compareTo方法。
public int compareTo(Student s){
int num = new Integer(this.age).compareTo(new Integer(s.age));
if(num == 0)
return this.name.compareTo(s.name);
return num;
}
}

8、Map集合综合运用举例

何时使用Map集合:当发现存在映射关系时,我们就可以选择Map集合。应为Map集合其本身存储的就是映射关系。其中HashMap是散列映射表,TreeMap是树映射表。

练习举例:"sdfgzxcvasdfxcvdf"获取该字符串中的字母出现的次数。希望打印结果:a(1)c(2).....

代码实现如下

import java.util.*;
/*
练习:
"sdfgzxcvasdfxcvdf"获取该字符串中的字母出现的次数。

希望打印结果:a(1)c(2).....

通过结果发现,每一个字母都有对应的次数。
说明字母和次数之间都有映射关系。

注意了,当发现有映射关系时,可以选择map集合。
因为map集合中存放就是映射关系。


什么使用map集合呢?
当数据之间存在这映射关系时,就要先想map集合。

思路:
1,将字符串转换成字符数组。因为要对每一个字母进行操作。

2,定义一个map集合,因为打印结果的字母有顺序,所以使用treemap集合。

3,遍历字符数组。
将每一个字母作为键去查map集合。
如果返回null,将该字母和1存入到map集合中。
如果返回不是null,说明该字母在map集合已经存在并有对应次数。
那么就获取该次数并进行自增。,然后将该字母和自增后的次数存入到map集合中。覆盖调用原理键所对应的值。

4,将map集合中的数据变成指定的字符串形式返回。
*/
public class MapTest5{
public static void main(String [] args){
String s = charCount("sdfgzxcvasdfxcvdf");
System.out.println(s);
}

public static String charCount(String str){
//将字符串转化为字符串数组,因为要操作每一个字符。
char[] chs = str.toCharArray();
/*
创建一个TreeMap集合。因为TreeMap其按键自动排序。将字符作为键,出现次数
作为值存入。
*/
TreeMap<Character,Integer> tm = new TreeMap<Character,Integer>();

int count = 0;
for(int i = 0;i < chs.length;i++){

if(chs[i] < 'a' && chs[i] > 'z' || chs[i] < 'A' && chs[i] > 'Z')
continue;//非字母,则不计
Integer value = tm.get(chs[i]);//获取第i个角标对应的值。
//当value不为空,用count记住其值
if(value != null)
count = value;
count++;

tm.put(chs[i],count);//直接存入,自动装箱

count = 0;//计数器复位。不复位会出现统计错误。
}

//因为要按照a(1)c(2).....格式输出,所以建立一个容器
StringBuilder sb = new StringBuilder();
Set<Map.Entry<Character,Integer>> entrySet = tm.entrySet();

Iterator<Map.Entry<Character,Integer>> it = entrySet.iterator();
//遍历
while(it.hasNext()){
Map.Entry<Character,Integer> me = it.next();
//用Map.Entry中的方法取出字符及其出现次数
Character ch = me.getKey();
Integer value = me.getValue();
sb.append(ch+"("+value+")");//存入容器中
}
return sb.toString();//返回字符串
}
}