2022-08-07 学习笔记 day31-JavaSE-集合-Map集合

集合

Map集合

Map中的集合都是以键值对的方式存储元素

Map集合继承关系

«interface»
Map
«interface»
SortedMap
HashMap
Hashtable
Properties
TreeMap

Map接口

  1. 包路径:java.util.Map<K,V>
  2. 方法
    在这里插入图片描述
Map接口中的重点方法
方法功能
boolean containsKey(Object)
boolean containsValue(Object)
V remove(Object key)
使用这三个方法时,要求集合中存放的类型要重写equals()
Set<K> keySet()返回此映射中包含的键的 Set 视图。该 set 受映射支持1,所以对映射的更改可在此 set 中反映出来,反之亦然。如果对该 set 进行迭代的同时修改了映射(通过迭代器自己的 remove 操作除外),则迭代结果是不确定的。set 支持元素移除,通过 Iterator.remove、Set.remove、removeAll、retainAll 和 clear 操作可从映射中移除相应的映射关系。它不支持 add 或 addAll 操作
上面时JDK帮助文档的原话,简单来说就是,Map集合调用keySet()时会返回一个Set集合,对这个Set集合修改会导致Map集合中的内容一同修改,反之对Map集合修改也会导致Set集合内容一同修改;并且对Set集合进行遍历时,支持删除元素(remove),不支持添加元素(add或addAll),并且使用迭代器对Set集合遍历时必须使迭代器的remove()删除元素,否则迭代结果时不确定的
Collection values()返回此映射中包含的值的 Collection 视图。该 collection 受映射支持,所以对映射的更改可在此 collection 中反映出来,反之亦然。如果对该 collection 进行迭代的同时修改了映射(通过迭代器自己的 remove 操作除外),则迭代结果是不确定的。collection 支持元素移除,通过 Iterator.remove、Collection.remove、removeAll、retainAll 和 clear 操作可从映射中移除相应的映射关系。它不支持 add 或 addAll 操作。
Set<Map.Entry<K,V>> entrySet()返回此映射中包含的映射关系的 Set 视图。该 set 受映射支持,所以对映射的更改可在此 set 中反映出来,反之亦然。如果对该 set 进行迭代的同时修改了映射(通过迭代器自己的 remove 操作,或者通过对迭代器返回的映射项执行 setValue 操作除外),则迭代结果是不确定的。set 支持元素移除,通过 Iterator.remove、Set.remove、removeAll、retainAll 和 clear 操作可从映射中移除相应的映射关系。它不支持 add 或 addAll 操作。
Map集合的遍历/迭代

Map集合不能直接遍历,可以通过遍历受映射支持的Set集合/Collection集合来间接遍历Map集合

  1. 遍历keySet()返回的Set集合
package com.jsoft.collection;

import java.util.*;

public class TestMap {
    public static void main(String[] args) {

        TreeMap<String, String> stringTreeMap = new TreeMap<>();
        stringTreeMap.put("1","20");
        stringTreeMap.put("5","120");
        stringTreeMap.put("20","250");
        stringTreeMap.put("1","22");
        stringTreeMap.put("12","20");
        stringTreeMap.put("A","260");
        stringTreeMap.put("z","720");
        
        // 遍历keySet()返回的Set集合
        Set<String> keySet = stringTreeMap.keySet();
        for (String key : keySet) {
            // 通过key拿到value
            String value = stringTreeMap.get(key);
            // 向控制台中打印Map集合的key和value
            System.out.println(key + "=" + value);
        }

    }
}

  1. 遍历entrySet()返回的Set集合
package com.jsoft.collection;

import java.util.*;

public class TestMap {
    public static void main(String[] args) {

        TreeMap<String, String> stringTreeMap = new TreeMap<>();
        stringTreeMap.put("1","20");
        stringTreeMap.put("5","120");
        stringTreeMap.put("20","250");
        stringTreeMap.put("1","22");
        stringTreeMap.put("12","20");
        stringTreeMap.put("A","260");
        stringTreeMap.put("z","720");

        // 遍历entrySet()返回的Set集合
        Set<Map.Entry<String, String>> entrySet = stringTreeMap.entrySet();

        for(Map.Entry<String,String> entry : entrySet) {
            // 通过Map.Entry拿到key
            String key = entry.getKey();
            // 通过Map.Entry拿到value
            String value = entry.getValue();
            // 向控制台打印Map集合key和value
            System.out.println(key + "=" + value);
        }

    }
}
  1. 遍历values()返回的Collection集合
package com.jsoft.collection;

import java.util.*;

public class TestMap {
    public static void main(String[] args) {

        TreeMap<String, String> stringTreeMap = new TreeMap<>();
        stringTreeMap.put("1","20");
        stringTreeMap.put("5","120");
        stringTreeMap.put("20","250");
        stringTreeMap.put("1","22");
        stringTreeMap.put("12","20");
        stringTreeMap.put("A","260");
        stringTreeMap.put("z","720");

        // 遍历values()返回的Collection集合,只能获取Map集合的value部分
        Collection<String> values = stringTreeMap.values();
        for(String value : values) {
            System.out.println("value: " + value);
        }


    }
}

对于方式1和方式2来遍历Map集合的key和value,建议使用方式2,因为方式2可以直接从entry对象中获取属性值,效率高;方式1需要通过key去获取value(挨个遍历),效率低

HashMap类
  1. HashMap集合底层采用了哈希表/散列表(数组+链表) 这种数据结构
  2. HashMap集合中可以存储null(同理,HashSet集合也可以)
  3. HashMap默认初始化容量为16
  4. HashMap在初始化时,指定初始化的容量必须是2的幂(官方要求),目的时为了达到散列均匀,提高HashMap存储效率
  5. HashMap加载因子0.75,表示当底层数组容量达到75%时,将数组进行扩容
  6. HashMap中数组一次扩容自身的2倍
  7. Java8,HashMap采用了数组+链表+红黑树这种数据结构,HashMap中当链表的节点超过8个时,会将单向链表转换成红黑树,当红黑树中的节点低于6个时候会将红黑树重新转换成单向链表,这种方式是为了提高检索效率,使用红黑数可以再次缩小扫描范围,
  8. 哈希表这种数据结构的特点
    • 随机增删效率高,因为元素的增删都是在链表上完成的,不涉及到元素的位移操作
    • 检索元素效率高,因为通过(数组+链表)这种结构,我们在检索某个元素时,不需要全表扫描,只需要部分扫描(缩小了扫描范围)
put(K,V)方法的实现原理
  1. 首先会将key和value封装成一个Node对象
  2. 调用key的hashCode() 获取hash值
  3. 通过哈希算法将hash值转换成数组下标,通过数组下标定位到指定位置,
    • 如果当前位置没有内容(没有链表),会将新Node对象直接放入(此时新Node对象为该链表的头节点)
    • 如果当前位置存在节点(存在链表),则会调用key的equals() 从头节点依此遍历比较节点的key部分,
      • 如果有所有的equals()都为false,则将新Node放到链表的末尾
      • 如果有一个equals()是true,则找到的该节点的value覆盖

使用put()存元素时什么情况下不会调用equals()?


 数组下标位置为null时,equals()不需要执行。

get(K)方法的实现原理
  1. 调用key的hashCode() 获取hash值
  2. 通过哈希算法将hash值转换成数组下标,通过数组下标定位到指定位置
  3. 如果当前位置没有内容,则get()返回null
  4. 如果当前位置存在节点,则会调用key的equals() 从头节点依此遍历比较节点的key部分,
    • 如果所有的equals()都返回false,则get()返回null
    • 如果有一个equals撒返回了true,则get()返回的值就是这个找到的节点的value

使用get()获取元素时什么情况下不会调用equals()?

 数组下标位置为null时,equals()不需要执行

HashMap的是如何保证无序不可重复特点

无序:使用put()方法往HashMap集合中存储元素时,不一定会将元素放在哪一个链表上
不可重复:使用put()方法往HashMap集合中存元素时,如果有相同的key,则会覆盖value

通过上面对HashMap集合中put()和get()的分析,可以看出无论是使用put()往集合中存储元素,还是使用get()获取集合中的value,都需要调用集合key部分equals()和hashCode(),这是为什么?----> 为了保证哈希表这种数据结构的特点

HashMap集合是通过同时重写集合中key部分的equals()和hashCode()来保证无序不可重复特点,通过重写hashCode定位相同的数组下标(同一链表),通过重写equals来找到(同一链表上)相同的key。
那么如果只重写equals(),不重写hashCode()会怎样?我们来看下面的代码

  @Test
    public void testHashMap() {
        
        // User用户,Integer年龄
        Map<User,Integer> users = new HashMap<>();
        User user1 = new User("admin","123456");
        User user2 = new User("admin","123456");
        System.out.println("user1和user2是否是同一个用户:" + user1.equals(user2)); //user1和user2是否是同一个用户:true
        
        // 不重写equals能否保证HashMap中元素不可重复特点?
        users.put(user1,19);
        users.put(user2,20);
        System.out.println("users集合中元素的个数是:" + users.size()); //users集合中元素的个数是:2
    }

通过结果可以看出,如果只重写equals()补充些hashCode()是无法保证元素不可重复的特点,因为集合的key未重写hashCode(),则会调用父类Object的hashCode(),此处User实例调用Object类的hashCode(),返回的是对象的内存地址,而不同对象的内存地址是一定不同的,所以在使用put()添加元素时,通过hash算法转化数组下标时,对于所有的不同对象都会定位到不同的数组下标,从而无法保证HashMap中存储元素不可重复的这种特点。如果重写了hashCode()可以将“内容相同的”对象定位到同一数组下标上(同一链表上),这样可以保证HashMap集合中元素不可重复的特点

结论:
我们需要同时重写存储在HashMap集合key部分的equals()和hashCode(),以保证HashMap集合中存储元素的特点,当然对于HashSet集合也是如此,因为HashSet底层是一个HashMap

那么可能有的童鞋会有疑问,HashMap集合中同一链表上所有的节点的key部分的hash值一定是相同的么,(也就是说不同节点的key部分通过调用hashCode()转换的hash值一定时相同的么)?
答案是不一定的,因为在调用hashCode()方法时,不等值(equals()结果为false)的对象,也可能会生成相同的hash值,这种情况我们称之为哈希碰撞/散列冲突这与底层的模运算结果有关,有想了解的童鞋可以去问下度娘,这里就不赘述了(其实是我也不太懂😓),但是就算发生了哈希碰撞也不影响HashMap存储元素的特点,因为key部分hash值相同的的节点一定会定位到同一数组下标(同一链表中)

对于两个同一类型的对象
hashCode()相同,equals()不一定相等(哈希碰撞)
equals()相等,hashCode()一定相同

哈希表HashMap使用不当时无法发挥性能
  1. 假设所有的hashCode()都返回一个相同的值会怎样?
     会导致哈希表变成单项链表,这种情况是散列不均匀的
  2. 假设所有的hashCode()都返回不同的值会怎样?
     会导致哈希表变成一维数组,这种情况是散列不均匀的
  3. 什么是散列均匀
     假设有100个元素,10个单项链表,那么每个单项链表上都有10个节点,这种情况便是散列均匀的
  4. 为什么HashMap要尽量达到散列均匀?
     越是散列均匀越是可以将哈希表HashMap的性能发挥到极致,使HashMap存储元素效率提到最高
Hashtable类
  1. Hashtable集合底层采用了哈希表/散列表(数组+链表) 这种数据结构
  2. Hashtable集合中不可以存储null
  3. Hashtable集合默认初始化容量为11
  4. Hashtable集合的加载因子为0.75,表示底层数组在容量达到75%时,对数组进行扩容
  5. Hashtable集合底层数组一次扩容原来的2倍加1
  6. Hashtable集合和HashMap集合区别 ----> Hashtable是线程安全的(Hashtable中的方法都有synchronized关键字修饰),现在保证线程安全有更好的方式了,Hashtable已过时
Properties类
  1. Properties也是线程安全
  2. Properties集合中的key和value部分只能是String类型
  3. Properties又被称为属性类

Properties的使用

  @Test
    public void testProperties() {
        Properties pros = new Properties();
        // 存
        pros.setProperty("driver","com.mysql.jdbc.Driver");
        pros.setProperty("url","jdbc:mysql://localhost:8080/jsoft?charecterEncoding=UTF-8");
        pros.setProperty("user","root");
        pros.setProperty("password","123");

        // 取
        String driver = pros.getProperty("driver");
        String url = pros.getProperty("url");
        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
    }

Properties与IO流联合使用
在这里插入图片描述

 @Test
    public void testPropertiesAndIO() throws IOException {
        Properties pros = new Properties();
        // 让pros加载info.properties
        pros.load(new FileInputStream("info.properties"));

        // 从info.properties中通过key取value
        String driver = pros.getProperty("driver");
        String url = pros.getProperty("url");
        String user = pros.getProperty("user");
        String password = pros.getProperty("password");

    }
SortedMap接口
  1. SortedMap接口继承了Map接口的特点(无序不可重复)
  2. SortedMap集合中元素都是可排序的(根据key部分自动排序)
TreeMap类
  1. TreeMap接口继承了SortedMap接口的特点,集合中的元素根据key部分自动排序

TreeSet集合和TreeMap集合中的存放的类型必须实现Comparable接口否则在程序运行期间会出现ClassCaseException

UML类图

在这里插入图片描述


  1. 对映射(Map集合)的更改可以在Set中反映出来,反之亦然(即对Set集合的更改也可以在映射中反映出来) ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值