集合框架3

六、Map接口
1.Map接口简介
Map接口是Java集合框架的另一大分支体系。

Map与Collection并列存在。用于保存具有映射关系的数据:key-value。Map 中的 key 和 value 都可以是任何引用类型的数据

Map 中的 key 用Set来存放,不允许重复,即同一个 Map 对象所对应的类,须重写hashCode()和equals()方法;
常用String类作为Map的key
key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到唯一的、确定的 value
Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Properties。其中,HashMap是 Map 接口使用频率最高的实现类。
2. Map的实现类比较
Map接口的常用实现类:HashMap、TreeMap、LinkedHashMap和Hashtable、Properties。其中,HashMap是 Map 接口使用频率最高的实现类。

Hashtable类是最早用于存储key-value对的类,它在Map接口出现之前就已将存在了。而HashMap、TreeMap、LinkedHashMap都是Map接口出现后作为Map接口的实现类存在的。Map接口出现后,三者都作为其实现类存在。

HashMap是Map接口的主要实现类,它是线程不安全的,但效率高。它可以存储null的key-value对,而Hashtable是不能存储null的key-value对的。该类具有很好的存取、查找、删除性能。JDK8之后,其底层是数组+链表+红黑树来存储数据的。

LinkedHashMap:是HashMap的子类,主要区别是在原有的HashMap底层结构的的基础上对每个元素都添加了一对引用,分别指向该元素前一个元素和后一个元素。这样在遍历其内部数据时,可以按照插入的方式进行,因此多用于需要频繁遍历的场合。

TreeMap:可以保证按照添加的key-value对进行排序,实现排序遍历。此时主要是按照key值进行自然排序或者定制排序的。其底层存储结构是红黑树。

Hashtable作为Map的古老实现类,它是线程安全的。Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用。它与HashMap的区别在于Hashtable是同步类,属于强同步类,因此效率较低。

尽管它线程安全,但是在多线程问题时,还是会使用HashMap而不是Hashtable,至于HashMap的线程不安全问题,一般会将其转为Collection来解决。还有一个子类Properties。

Properties是Hashtable一个子类。常用来处理配置文件,其key和value都是String类型。

  1. Map结构的理解
    Map中的key:无序的,不可重复的,可以理解是用Set集合存储的Key;

Map中的value:无序的,可重复的,没有这样的一种数据结构,简单地可以理解是用Collection集合存储的value;

一个键值对key-value构成了一个Entry对象;
Map中的Entry:无序的,不可重复的,可以理解是用Set集合存储的。
因此对于HashMap,key所在的类要重写equals方法和hashcode方法。values所在的类应该重写equals方法。
Map中的元素看似是key和value两个东西,实际上是一个东西即它们的整体Entry,只是Entry有两个属性,一个是key,一个是value
4. Map接口中的常用方法
添加、删除、修改操作

元素查询操作

元视图的操作方法

import java.util.;
public class Demo03 {
public static void main(String[] args) {
//创建一个Map集合,以HashMap为例实现
Map map = new HashMap();
//1. put(Object key,Object value):将指定key-value添加到当前map对象中
map.put(“aa”,20);
map.put(“dd”,38);
map.put(“cc”,20);
map.put(“dd”,18);
map.put(“hh”,20);
System.out.println(map);
System.out.println("
“);
//2. put(Object key,Object value):map中已有相同的key,则将新value替换原有的value
map.put(“aa”,39);
System.out.println(map);
System.out.println(”
“);
//3. putAll(Map m):将m中的所有key-value对存放到当前map中
Map map1 = new HashMap();
map1.put(“ee”,30);
map1.put(“ff”,38);
map1.put(“dd”,88);
map.putAll(map1);
System.out.println(map);
System.out.println(”
“);
//4. remove(Object key):移除指定key的key-value对,并返回value
Object value = map.remove(“cc”);
System.out.println(“清除cc,返回其value为:” + value);
value = map.remove(“aaa”);
System.out.println(“清除aaa,返回其value为:” + value);
System.out.println(map);
System.out.println(”
“);
//5. get(Object key):获取指定key对应的value,不改变原HashMap集合
value = map.get(“aa”);
System.out.println(“获取aa对应的value为:” + value);
System.out.println(map);
System.out.println(”
“);
//6. containsKey(Object key):是否包含指定的key
//7. containsValue(Object value):是否包含指定的value
System.out.println(“判断map中是否含有ff的key:” + map.containsKey(“ff”));
System.out.println(“判断map中是否含有cc的key:” + map.containsKey(“cc”));
System.out.println(“判断map中是否含有88的value:” + map.containsValue(88));
System.out.println(“判断map中是否含有98的value:” + map.containsValue(98));
System.out.println(”
“);
//8. int size():返回map中key-value对的个数
System.out.println(“当前map中key-value对的个数:” + map.size());
System.out.println(”
“);
//9. isEmpty():判断当前map是否为空
System.out.println(“当前Map是否为空:” + map.isEmpty());
System.out.println(”
“);
//10. equals(Object obj):判断当前map和参数对象obj是否相等
//要想返回结果为true,obj必须也是一个HashMap,且其中的元素和map中的元素一致。
System.out.println(“要想map.equals(Object obj)返回结果为true,obj必须也是一个HashMap,且其中的元素和map中的元素一致”);
System.out.println();
//11. keySet():返回所有key构成的Set集合,使用迭代器遍历keySet
Set keyset = map.keySet();
Iterator iterator = keyset.iterator();
System.out.println(“返回所有key构成的Set集合keyset,遍历keyset:”);
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
System.out.println(”
“);
//12. values():返回所有value构成的Collection集合
Collection values = map.values();
System.out.println(“返回所有value构成的Collection集合values,遍历values:”);
for (Object j:values) {
System.out.println(j);
}
System.out.println(”
“);
//13. entrySet():返回所有key-value对构成的Set集合
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
System.out.println(“输出key-value对的两种方式:”);
while(iterator1.hasNext()) {
//Set集合entrySet中的每一个元素都是entry对象,即都是一个key-value对,这意味着可以将其转换为Map.Entr类对象
//Map.Entry类提供了getkey和getvalue方法
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry)obj;
System.out.println(entry.getKey() + “\t------>\t” + entry.getValue());
}
//还可以根据key及get方法做到像上述那样的遍历key-value对
Set keysset = map.keySet();
Iterator iterator2 = keysset.iterator();
while (iterator2.hasNext()) {
//获取key值
Object key = iterator2.next();
//根据get方法获取对应的value值
System.out.println(key + “\t==========>>\t” + map.get(key));
}
System.out.println(”
************************************");
//14. clear():清空当前map中的所有数据
map.clear();
System.out.println(“清空后的map中key-value对的个数:” + map.size());
System.out.println(“clear后的Map是否为空:” + map.isEmpty());
System.out.println(map);
}
}

运行结果:
{aa=20, dd=18, cc=20, hh=20}
*************************************
{aa=39, dd=18, cc=20, hh=20}
*************************************
{aa=39, dd=88, cc=20, hh=20, ee=30, ff=38}
*************************************
清除cc,返回其value为:20
清除aaa,返回其value为:null
{aa=39, dd=88, hh=20, ee=30, ff=38}
*************************************
获取aa对应的value为:39
{aa=39, dd=88, hh=20, ee=30, ff=38}
*************************************
判断map中是否含有ff的key:true
判断map中是否含有cc的key:false
判断map中是否含有88的value:true
判断map中是否含有98的value:false
*************************************
当前map中key-value对的个数:5
*************************************
当前Map是否为空:false
*************************************
要想map.equals(Object obj)返回结果为true,obj必须也是一个HashMap,且其中的元素和map中的元素一致
返回所有key构成的Set集合keyset,遍历keyset:
aa
dd
hh
ee
ff
*************************************
返回所有value构成的Collection集合values,遍历values:
39
88
20
30
38
*************************************
输出key-value对的两种方式:
aa ------> 39
dd ------> 88
hh ------> 20
ee ------> 30
ff ------> 38
aa ==========>> 39
dd ==========>> 88
hh ==========>> 20
ee ==========>> 30
ff ==========>> 38
*************************************
清空后的map中key-value对的个数:0
clear后的Map是否为空:true
{}
5. HashMap的底层存储原理
Jdk8之前,其底层是用数组+链表结构来存储数据的。与HashSet类似。

JDK8之后,其底层是数组+链表+红黑树来存储数据的。

5.1 Jdk8之前的结构
HashMap的内部存储结构其实是数组和链表的结合。

HashMap map = new HashMap();当实例化一个HashMap时,系统会创建一个长度为Capacity的Entry数组,当未存储任何数据时,Capacity的默认值是16。
map.put(keyn,valuen)执行put操作,将Entry对象放入HashMap集合中。执行此操作时,会调用keyn所在类的hashcode方法,计算Entryn的哈希值。计算得到的hash值经过某种算法转换,可以得到该Entry对象在Entry数组中的位置。
若该位置上没有数据,则该Entryn对象就会占据该位置,key-value对添加成功;
若该位置上有数据(该位置存在一个数据或以链表方式存储的多个数据,假定为Entrym(nkeym-valuem))。则将Entryn的哈希值依次与原位置上所有的对象的哈希值比较。
若结果都不同,则将新的Entry对象与原本在该位置处的对象组成链表,且新的Entry对象位于链表的头部。
若Entryn的哈希值与该位置上的某一元素相同,则会调用keyn所在类的equals方法进行比较,若返回false说明二者的key不同,则keyn-valuen对添加成功。
若返回未true,说明二者key值相同,此时会用新的valuen值替换原有元素的value值。相当于修改操作。
关于扩容的问题:

当数组长度不够时,会执行扩容操作。即建立一个新的长度为原有数组长度2倍的数组,并将原数组的内容赋值给新数组。

5.2 Jdk8之后的结构
JDK8之后,其底层是数组+链表+红黑树来存储数据的。

主要区别:

当new HashMap()创建Map集合时并没有创建一个长度为16的数组。
当第一次调用put()方法时,底层才创建一个长度为16的Node[]数组。
当位置上的元素不等,则新node对象将和原有的该位置上的元素以链表结构存在,且新的node对象位于链表的尾部。
当数组使用率超过0.75时,执行扩容操作,扩为原有数组长度的2倍。
当node[]数组上某一个位置上以链表形式存放的数据个数超过8个且当前node[]数组的长度超过64时,当前位置上的所有数据改为红黑树存储。(为了便于查找)

5.3 与HashSet的关系
HashSet的底层存储用的其实也是HashMap,相当于只用HashMap的key就构成了HashSet。不过HashSet里没有红黑树。那么对应的value是什么呢?源码中将HashSet时的Map中的value设为一个固定的Object对象obj,即所有的Key都指向这个空的对象。

5.4 LinkedHashMap的底层存储
与HashMap相比,LinkedHashMap中node数组上的每个位置上的元素在存储时会多两个引用,一个指向它前一个进来的元素,一个指向后一个添加进来的元素,这样就出现了类似于保持了和插入顺序一致的排列的效果。实际上内部还是无序的。

对于频繁遍历Map中的元素的时候,LinkedHashMap拥有较好的性能。

6.TreeMap的使用
TreeMap 是 SortedMap接口的实现类,TreeMap 可以按照key-vaue对的key进行排序,这就要求TreeMap中的key必须是相同类的对象。

TreeSet和TreeMap底层都是使用红黑树结构存储数据。

TreeMap 两种排序方法:自然排序和定制排序,也就是Comparable借口和Comparator接口。默认情况下,TreeSet 采用自然排序。

自然排序:

TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException。

import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class Demo04 {
public static void main(String[] args) {
Users u1 = new Users(“harry”,20);
Users u2 = new Users(“alpha”,28);
Users u3 = new Users(“beer”,34);
Users u4 = new Users(“maxc”,25);
Users u5 = new Users(“sfsdg”,20);
Users u6 = new Users(“sfsdg”,56);
TreeMap map = new TreeMap();
map.put(u1,99);
map.put(u2,85);
map.put(u3,77);
map.put(u4,95);
map.put(u5,87);
map.put(u6,77);
//自然排序
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while(iterator1.hasNext()) {
//Set集合entrySet中的每一个元素都是entry对象,即都是一个key-value对,这意味着可以将其转换为Map.Entr类对象
//Map.Entry类提供了getkey和getvalue方法
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry)obj;
System.out.println(entry.getKey() + “\t------>\t” + entry.getValue());
}
}
}
class Users implements Comparable {
private String name;
private double age;
@Override
//按照名称从高到低排序,名称一致的时候按年龄的从低到高排
public int compareTo(Object o) {
//判断元素是都是Users子类
if (o instanceof Users) {
//是的话,就强制转换为Users类,类型一致方可比较
Users users = (Users) o;
int compare = -this.name.compareTo(users.name);
if (compare != 0) {
return compare;
} else {
return Double.compare(this.age, users.age);
}
}
throw new RuntimeException(“类型不一致”);
}
@Override
public String toString () {
return “Users{” +
“name='” + name + ‘’’ +
“, age=” + age +
‘}’;
}
public Users() {
}
public Users(String name, double age) {
this.name = name;
this.age = age;
}
}

运行结果:
Users{name=‘sfsdg’, age=20.0} ------> 87
Users{name=‘sfsdg’, age=56.0} ------> 77
Users{name=‘maxc’, age=25.0} ------> 95
Users{name=‘harry’, age=20.0} ------> 99
Users{name=‘beer’, age=34.0} ------> 77
Users{name=‘alpha’, age=28.0} ------> 85
定制排序:

创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现Comparable 接口。

import java.util.*;
public class Demo04 {
public static void main(String[] args) {
Users u1 = new Users(“harry”,20);
Users u2 = new Users(“alpha”,28);
Users u3 = new Users(“beer”,34);
Users u4 = new Users(“maxc”,25);
Users u5 = new Users(“sfsdg”,20);
Users u6 = new Users(“sfsdg”,56);
//定制排序设定
//按照年龄从小到大排列
Comparator som = new Comparator(){
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Users && o2 instanceof Users) {
Users u1 = (Users)o1;
Users u2 = (Users)o2;
return Double.compare(u1.getAge(),u2.getAge());
}
throw new RuntimeException(“类型不一致!”);
}
};
TreeMap map = new TreeMap(som);
map.put(u1,99);
map.put(u2,85);
map.put(u3,77);
map.put(u4,95);
map.put(u5,87);
map.put(u6,77);
Set entrySet = map.entrySet();
Iterator iterator1 = entrySet.iterator();
while(iterator1.hasNext()) {
//Set集合entrySet中的每一个元素都是entry对象,即都是一个key-value对,这意味着可以将其转换为Map.Entr类对象
//Map.Entry类提供了getkey和getvalue方法
Object obj = iterator1.next();
Map.Entry entry = (Map.Entry)obj;
System.out.println(entry.getKey() + “\t------>\t” + entry.getValue());
}
}
}
class Users {
private String name;
private double age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getAge() {
return age;
}
public void setAge(double age) {
this.age = age;
}
@Override
public String toString () {
return “Users{” +
“name='” + name + ‘’’ +
“, age=” + age +
‘}’;
}
public Users() {
}
public Users(String name, double age) {
this.name = name;
this.age = age;
}
}

运行结果:
Users{name=‘harry’, age=20.0} ------> 87
Users{name=‘maxc’, age=25.0} ------> 95
Users{name=‘alpha’, age=28.0} ------> 85
Users{name=‘beer’, age=34.0} ------> 77
Users{name=‘sfsdg’, age=56.0} ------> 77
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明,KuangStudy,以学为伴,一生相伴!

本文链接:https://www.kuangstudy.com/bbs/1381579951827873793

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值