Java基础 Map

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 +
                '}';
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值