Map接口实现类的特点(很实用)
注意:这里讲的是JDK8的Map接口特点
1、Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
2、Map中的key和value可以任何引用类型的数据,会封装到HaspMap$Node对象中
3、Map中的key不允许重复,原因和HashSet一样,前面分析过源码
4、Map中的value可以重复
5、Map的key可以为null,value也可以为null,注意key为null,只能有一个,value为null,可以多个
6、常用的String类作为Map的key
7、key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName Map_
* @Description
* @Author 小黄debug
* @Date 2022/3/13 17:24
* @Version 1.0
**/
public class Map_ {
public static void main(String[] args) {
//解读Map接口实现类的特点,使用实现类HashMap
//1.Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value(双列元素)
//2.Map中的key和value可以是任何引用类型的数据,会封装到HashMap$Node对象中
//3.Map中的key不允许重复,原因和HashSet一样,前面分析过源码
//4.Map中的value可以重复
//5.Map的key可以为null,value也可以为null,注意key为null,
//只能有一个,value为null,可以多个
//6.常用String类作为Map的key
//7.key和value之间存在单向一对一关系,即通过指定的key总能找到对应的value
Map map = new HashMap();
map.put("no1","小黄debug");
map.put("no2","张无忌");
map.put("no1","张三丰"); //当有相同的k,就等价于替换
map.put("no3","张三丰");
map.put(null,null);
map.put(null,null);
map.put(null,"abc");//等价替换
map.put("no4",null);//k-v
map.put("no5",null);//k-v
map.put(1,"赵敏");//k-v
map.put(new Object(),"金毛狮王");//k-v
//通过get方法,传入key,会返回对应的value
System.out.println(map.get("no2"));
System.out.println("map = "+map);
}
}
源码解读
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* @ClassName MapSource_
* @Description
* @Author 小黄debug
* @Date 2022/3/13 18:55
* @Version 1.0
**/
public class MapSource_ {
public static void main(String[] args) {
Map map = new HashMap();
map.put("no1","韩顺平"); //k-v
map.put("no2","张无忌");
map.put(new Car(),new Person()); //k-v
//解读:
//1.k-v 最后是HashMap$Node node = new Node(hash,key,value,null)
//2.k-v 为了方法程序员的遍历 ,还会 创建 EntrySet集合,该集合存放的元素的类型 Entry
// 对象就有k,v EntrySet<Entry<K,V>> 即: transient Set<Map.Entry<K,V>> entrySet;
//3. entrySet中,定义的类型是Map.Entry,但是实际上存放的还是HashMap$Node
// 为什么Entry存放的类型HashMap$Node 这是因为HashMap$Node implements Map.Entry
//4.当把HashMap$Node对象存放到entrySet就方便我们遍历,因为Map.Entry提供了了重要的方法
// K getKey(); V getValue();
Set set = map.entrySet();
System.out.println(set.getClass());
for(Object obj : set){
//System.out.println(obj.getClass());
//为了从HashMap$Node 取出k-v
//1.先做一个向下转型
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "- " + entry.getValue());
}
Set set1 = map.keySet();
System.out.println(set1.getClass());
Collection values = map.values();
System.out.println(values.getClass());
}
}
class Car{
}
class Person{
}
Map体系的继承图
Map
Hashtable
Properties
HashMap
LinkedHashMap
SortedMap //接口
TreeMap
Map接口的常用方法
1、put:添加
2、remove:根据键删除映射关系
3、get:根据键获取值
4、size:获取元素个数
5、isEmpty:判断个数是否为0
6、clear:清除
7、containsKey:查找键是否存在
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName MapMethod
* @Description
* @Author 小黄debug
* @Date 2022/3/13 19:36
* @Version 1.0
**/
public class MapMethod {
public static void main(String[] args) {
Map map = new HashMap();
map.put("邓超",new Book("",100));
map.put("邓超","孙俪");
map.put("王宝强","马蓉");
map.put("宋喆","马蓉");
map.put("刘令博",null);
map.put("null","刘亦菲");
map.put("鹿晗","关晓彤");
System.out.println("map = "+map);
//remove:根据键删除映射关系
map.remove(null);
//get:根据键获取值
Object val = map.get("鹿晗");
System.out.println("val = "+ val);
//size:获取元素个数
System.out.println(map.size());
//isEmpty:判断个数是否为0;
System.out.println(map.isEmpty());
//clear:清除
map.clear();
System.out.println("map="+map);
//containsKey:查找键是否存在
System.out.println(map.containsKey("hsy"));
}
}
class Book{
private String name;
private int num;
public Book(String name, int num) {
this.name = name;
this.num = num;
}
}
Map接口遍历方法
1、containsKey:查找键
2、keySet:获取所有的键
3、entrySet:获取所有关系
4、values:获取所有的值
import java.util.*;
/**
* @ClassName MapFor
* @Description
* @Author 小黄debug
* @Date 2022/3/13 19:50
* @Version 1.0
**/
public class MapFor {
public static void main(String[] args) {
Map map = new HashMap();
map.put("邓超",new Book("",100));
map.put("邓超","孙俪");
map.put("王宝强","马蓉");
map.put("宋喆","马蓉");
map.put("刘令博",null);
map.put("null","刘亦菲");
map.put("鹿晗","关晓彤");
//第一组:先取出所有的Key,通过Key取出对应的Value
Set keySet = map.keySet();
//(1)增强for
for(Object key:keySet){
System.out.println(key + "-" + map.get(key));
}
//(2)迭代器
Iterator iterator = map.keySet().iterator();
while(iterator.hasNext()){
Object key = iterator.next();
System.out.println(key + "-" +map.get(key));
}
//第二组,把所有的value取出
Collection values = map.values();
//这里可以使用Collections使用的遍历 方法
//(1)增加for
for(Object value: values){
System.out.println(value);
}
//(2)迭代器
Iterator iterator1 = values.iterator();
while (iterator1.hasNext()) {
Object next = iterator1.next();
System.out.println(next);
}
//第三种,通过EntrySet来获取 k-v
Set entrySet = map.entrySet();
//(1)增强for
for(Object entry: entrySet){
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-"+m.getValue());
}
//(2)迭代器
Iterator iterator2 = entrySet.iterator();
while (iterator2.hasNext()) {
Map.Entry m = (Map.Entry)iterator2.next();
System.out.println(m.getKey() +"-"+m.getValue() );
System.out.println(m.getClass());
}
}
}
练习
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* @ClassName MapExercise
* @Description
* @Author 小黄debug
* @Date 2022/3/13 20:03
* @Version 1.0
**/
public class MapExercise {
/*
使用HashMap添加3个员工对象,要求
键:员工Id
值:员工对象
并遍历 显示工资>18000的员工(遍历方式最少两种)
员工类:姓名、工资、员工id
*/
public static void main(String[] args) {
Map map = new HashMap();
map.put(1,new Emp("张三",18900,1));
map.put(2,new Emp("李四",17000,2));
map.put(3,new Emp("王二麻子",19900,3));
Set set = map.keySet();
for(Object key : set){
if(((Emp)map.get(key)).getSal() > 18000){
System.out.println(map.get(key));
}
}
Iterator iterator = map.keySet().iterator();
while (iterator.hasNext()) {
Emp next = (Emp)map.get(iterator.next());
if(next.getSal() > 18000){
System.out.println(next);
}
}
}
}
class Emp{
private String name;
private double sal;
private int id;
@Override
public String toString() {
return "Emp{" +
"name='" + name + '\'' +
", sal=" + sal +
", id=" + id +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Emp(String name, double sal, int id) {
this.name = name;
this.sal = sal;
this.id = id;
}
}
HashMap小结
1、Map接口的常用实现类:HashMap、Hashtable和Properties
2、HashMap是Map接口使用频率最高的实现类
3、HashMap是以key-val对的方式来存储数据
4、key不能重复,但是value可以重复,允许使用null键和null值
5、如果添加相同的key,则会覆盖原来的key-val,等同于修改。(key不会替换,val会替换)
6、与HashSet一样,不保证映射的顺序,因为底层是以hash表的方式来存储的(JDK8的hashMap底层 数组+链表+红黑树)
7、HashMap没有实现同步,因此线程是不安全的。方法没有做同步互斥的操作,没synchronized
HashMap底层机制及源码剖析
1、(k,v)是一个Node实现了Map.Entry<K,V>,查看HaspMap的源码 可以看到。
2、jdk7.0的hashmap底层实现[数组+链表】,jdk8.0底层[数组+链表+红黑树]
先说结论再debug
扩容机制[和HashSet相同]
1) HashMap底层维护了Node类型的数组table,默认为null
2) 当创建对象时,将加载因子(loadfactor)初始化为0.75
3)当添加key-val时,通过key的哈希值 得到在table的索引。然后判断该索引处是否有元素,
如果没有元素直接添加。如果该索引处有元素,继续判断该元素的key是否准备加入的key是否相等,
如果相等,则直接替换val;如果不相等需要判断 是树结构还是链表结构,做出相应处理,如果添加时发现容器不够,则需要扩容
4)第一次添加,则需要扩容table容器为16,临界值(threshold)为12。
5)以后再扩容,则需要扩容table容器为原来的2倍,临界值为原来的2倍,即24,依次类推。
6) 在Java8中,如果一条链表的元素个数超过了TREEIFY——THRESHOLD(默认是8),并且table的大小 >= MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)
import java.util.HashMap;
/**
* @ClassName HashMapSource1
* @Description
* @Author 小黄debug
* @Date 2022/3/13 20:46
* @Version 1.0
**/
public class HashMapSource1 {
public static void main(String[] args) {
HashMap map = new HashMap();
map.put("java",10);
map.put("php",10);
map.put("java",20); //替换
System.out.println("map="+map);
/* HashMap源码解读
1.执行构造器 new HashMap()
初始化加载因子 loadfactor = 0.75
HashMap$Node[] table = null
2. 执行put调用hash方法,计算key的hash值 (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
3.执行putVal
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i; //辅助变量
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//取出hash值对应的table索引位置的Node,如果为null,就直接把加入的k-v
//创建一个Node,加入该位置即可
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
HashMap.Node<K,V> e; K k; //辅助变量
//如果table的索引位置的key的hash和新的key的hash值相同
//并满足(table现在结点的key和准备添加的Key是同一个对象 || equals返回真)
//就认为不能加入新的key
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof HashMap.TreeNode) //如果当前的table的已有的Node是红黑树,就按红黑树的方式处理
e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//如果找到的结点,后面是链表,就循环比较
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) { //如果整个链表没有和他相同的就回到该链表的最后
p.next = newNode(hash, key, value, null);
//加入后,判断当前链表的个数,是否已经到8个,到8个后,
//就调用treeifyBin(tab, hash);方法进行红黑树的转换
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//如果有循环比较过程中,发现有相同的就break,就只替换value
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value; //更新value
afterNodeAccess(e); //这里什么都没干
return oldValue;
}
}
++modCount; //每增加一个Node,就size++;
if (++size > threshold) //如果size >= 临界值,就进行扩容
resize();
afterNodeInsertion(evict);
return null;
}
5.关于树化(转成红黑树)
//如果table表为null,或者大小还没到64,暂时不树化,而是进行扩容。
//否则才会真正的树化 -> 剪枝
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
*/
}
}
模拟
import java.util.HashMap;
/**
* @ClassName HashMapSource2
* @Description
* @Author 小黄debug
* @Date 2022/3/13 21:27
* @Version 1.0
**/
public class HashMapSource2 {
public static void main(String[] args) {
HashMap hashMap = new HashMap();
for (int i = 0; i <= 12; i++) {
hashMap.put(new A(i),"hello");
}
System.out.println("hashMap="+hashMap);
/*
布置一个任务,自己设计代码去验证,table的扩容
0 -> 16(12) -> 32(24) -> 64(48) -> 128 (96) ->
自己设计程序,验证 -》 增强自己阅读源码的能力
*/
HashMap hm = new HashMap();
for (int i = 0; i <= 100; i++) {
hm.put(i,"hello");
}
System.out.println("hm="+hm);
}
}
class A{
private int num;
public A(int num){
this.num = num;
}
//所有A对象的hashCode都是100
public int hashCode(){
return 100;
}
@Override
public String toString() {
return "\nA{" +
"num=" + num +
'}';
}
}