双例集合
Map接口
定义了双例集合的存储特征,它不是Collection接口的子接口。双例集合的存储特征是以【key】与【value】结构为单位进行存储。体
现的是数学中的函数 y=f(x)的概念
Map 与collecton的区别
- Collection 中的容器,元素是孤立存在的,向集合中存储元素采用一个个元素的方式存储
- Map 中的容器,元素是成对存在的。每个元素由【键】与【值】两部分组成,通过键查找对对应的值
- Collection 中的容器称为单列集合,Map中的容器称为双列集合
- Map 中的集合不包含重复的键,值可以重复;每个键只能对应一个值
- Map 中常用的容器为HashMap、TreeMap等
Map接口的常用方法
方法 | 说明 |
---|---|
V put(K key,V value) | 把key与value添加Map集合中 |
void putAll(Map m) | 从指定Map中将所有映射关系复制到此Map中 |
V remove(Object key) | 删除key对应的value |
V get(Object key) | 根据指定的key,获取对应的value |
boolean containsKey(Object key) | 判断容器中是否包含指定的key |
boolean containsValue(Object value) | 判断容器中是否包含指定的value |
Set keySet() | 获取Map集合中所有的hey,存储到Set集合中 |
Set<Map.Entry<K,V>> entrySet() | 返回一个Set基于Map.Entry类型包含Map中所有映射 |
void clear() | 删除Map中所有的映射 |
key相同,替换、覆盖
一个【key-value对】称为一个【映射】
HashMap容器类
- HashMap是Map 接口的接口实现类,它采用哈希算法实现,是 Map接口最常用实现类
- 由于底层采用了哈希表存储数据,所以要求键不能重复,如果发生重复,新的值会替换旧的值
- HashMap在查找、删除、修改方面都有非常高的效率
添加元素
//实例化HashMap容器
Map<String,String> map = new HashMap<>();
/*
添加元素,若容器中没有与要添加映射相同的key,返回值为null;
若有,则【新value】替换【旧value】,并返回【旧value】
*/
map.put("a", "A");
String v1 = map.put("a", "B");
System.out.println(v1);
获取元素的三种方式
- get()
- keySet() get()
- 通过entryset方法获取Map.Entry类型获取元素(内部接口.外部接口???),返回的键值对
/*
通过key获取value
难点:
1、如果要获取很多个value,需要每次都使用get方法
2、如果我们事先并不知道key,就无法获取对应value
*/
String s1 = map.get("a");
System.out.println(s1);
map.put("b","B");
map.put("c","C");
map.put("d","D");
map.put("e","E");
/*
使用keySet与get方法
获取HashMap容器中所有元素
成对输出
*/
Set<String> keys = map.keySet();
for (String key:keys){
String val = map.get(key);
System.out.println(key+" --- "+val);
}
/*
一个Map.Entry相当于一个键值对【K-V】
*/
Set<Map.Entry<String,String>> entrySet = map.entrySet();
for (Map.Entry<String,String> entry:entrySet){
String key = entry.getKey();
String val = entry.getValue();
System.out.println(key+" ---- "+val);
}
Map容器的并集操作
/*
并集操作
如果两容器中有相同的key,则原容器中的value将被覆盖
*/
Map<String,String> map2 = new HashMap<>();
map2.put("f","F");
map2.put("c","cc");
map2.putAll(map);
Set<Map.Entry<String,String>> entrySet2 = map2.entrySet();
for (Map.Entry<String,String> entry2:entrySet2){
String key2 = entry2.getKey();
String val2 = entry2.getValue();
System.out.println(key2+" ------ "+val2);
}
删除元素
System.out.println("------------通过key删除对应value-------------");
String v = map.remove("e");
System.out.println(v);//返回删除的value
Set<String> keys1 = map.keySet();
for (String key:keys1){
String k1 = map.get(key);
System.out.println(key+" --- "+k1);
}
如何判断key或value是否存在
boolean flag1 = map.containsKey("d");
System.out.println(flag1);
boolean flag2 = map.containsValue("B");
System.out.println(flag2);
HashMap底层存储特征
-
数组:占用空间连续,寻址易,查询快,增删效率低
-
链表:占用空间不连续,寻址难,查询慢,增删效率高
-
数组+链表—哈希表
JDK8,哈希表优化,红黑树
HashMap源码分析
-
static final float DEFAULT_LOAD_FACTOR = 0.75f
容器空间使用75%时就扩容,不是达到100%才扩容
-
static final int TREEIFY_THRESHOLD = 8
节点数大于8,才可能进行链表树型化(第二条件)
-
static final int MIN_TREEIFY_CAPACITY = 64
数组长度大于64,才会可能进行链表树型化(第一条件)
-
数组初始化
JDK8的HashMap中数组采用延迟初始化。resize方法既实现数组初始化,也实现数组扩容处理
计算哈希值(面试常问)/P231
元素放入节点,节点放入数组或数组中某一链表或红黑树,计算哈希值决定节点在数组的位置???
(1)获得key对象的hashcode
调用key对象的hashcode()方法,获得key 的hashcode值
(2)根据hashcode计算出hash值(要求在**[0,数组长度-1]**区间)
hashcode是一个整数,需要将它转化成**[O,数组长度-1]范围内的整数。且要求转化后的hash值尽量均匀地分布在区间,减少“hash冲突”**
-
—种极端简单和低下的算法
- hash值= hashcode/hashcode;
- 哈希值始终为1,所有元素所在节点都将放在数组中索引为1的位置
-
—种简单和常用的算法—相除取余算法
- hash值=hashcode%数组长度
- 这种算法可以让hash值均匀分布在区间。但由于使用了除法,效率低下。
- 改进的算法:首先约定数组长度必须为2的整数幂,现在采用位运算即可实现取余的效果: hash值=hashcode&(数组长度-1)。
(3)具体计算过程(二进制)
- h = key.hashcode()
- h >>>16(取高16位)
- 两者做异或运算^得到一个过程中的哈希值
- 数组长度-1
- 两者做与运算&得到一个最终的哈希值
- 转为十进制
(4)容器容量0-16-32,阈值为其容量的0.75倍,达到即扩容至2倍
TreeMap容器类
TreeMap和HashMap同样实现了Map接口,所以,对于API的用法来说是没有区别的。HashMap效率高于TreeMap;TreeMap是可以对
【键】进行排序的一种容器,在需要对键排序时可选用TreeMap。TreeMap底层是基于红黑树实现的。
在使用TreeMap时需要给定排序规则:
-
元素自身实现比较规则
/* 实例化TreeMap容器 Users作为Key,String作为value Key实现比较规则 */ Map<Users,String> map = new TreeMap<>(); Users u1 = new Users("cyf",18); Users u2 = new Users("damin",22); Users u3 = new Users("admin",22); map.put(u1,"cyf"); map.put(u2,"damin"); map.put(u3,"admin"); Set<Users> keys = map.keySet(); for (Users user:keys){ Users u = user; System.out.println(u); }
package com.cyf; public class Users implements Comparable<Users> { private String username; private int userage; public Users() { } public Users(String username, int userage) { this.username = username; this.userage = userage; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getUserage() { return userage; } public void setUserage(int userage) { this.userage = userage; } @Override public String toString() { return "Users{" + "username'" + username + '\'' + ",userage=" + userage + '}'; } //定义比较规则:正数,大;0,相等;负数,小 @Override public int compareTo(Users o) { if (this.userage > o.getUserage()){ return 1; }//年龄相等即无所谓先后 if (this.userage == o.getUserage()){ return this.username.compareTo(o.getUsername()); } return -1; } }
-
通过比较器实现比较规则
System.out.println("-----------------------------"); Map<Student,String> treeMap = new TreeMap<>(new StudentComparator()); Student s1 = new Student("cyf",18); Student s2 = new Student("admin",22); Student s3 = new Student("sxt",22); treeMap.put(s1,"cyf"); treeMap.put(s2,"admin"); treeMap.put(s3,"sxt"); Set<Student> keys1 = treeMap.keySet(); for (Student s:keys1){ System.out.println(s+"----"+treeMap.get(s)); }
public class StudentComparator implements Comparator<Student> { //比较器 定义比较规则 @Override public int compare(Student o1, Student o2) { if (o1.getAge() > o2.getAge()){ return 1; } if (o1.getAge() == o2.getAge()){ return o1.getName().compareTo(o2.getName()); } return -1; } }