Map集合

    科技2024-01-24  119

    Map

    java.util.Map接口中常用的方法

    Map和Collection没有继承关系

    Map集合以key和value的方式存储数据时:键值对

    key和value都是引用数据类型。

    key和value都是存储对象的内存地址。

    key起到主导的地位,value是key的附属品

    常用方法

    V put(K key, V value) 向Map集合中添加键值对

    V get(Object key) 通过key 获取 value

    void clear() 清空Map集合

    boolean containsKey(Object key) 判断Map集合是否含有某个key

    boolean containsValue(Object value) 判断Map集合中是否含有某个value

    boolean isEmpty() 判断Map集合是否为空

    V remove(Object key) 通过key删除键值对

    int size() 获取Map集合的键值对的个数

    Collection values() 获取Map集合中所有的value 返回一个Collection集合

    //创建Map集合对象 Map<Integer,String> map = new HashMap<>(); //向Map集合中添加键值对 map.put(1,"zhangsan"); map.put(2,"lisi"); map.put(3,"wangwu"); map.put(4,"zhaoliu"); //通过key获取value String value = map.get(2); System.out.println(value);//lisi //获取键值对的数量 System.out.println(map.size());//4 //判断Map集合是否含有某个key System.out.println(map.containsKey(1));//true //判断Map集合是否含有某个value System.out.println(map.containsValue("lisi"));//true //获取所有的value Collection<String> values = map.values(); for (String s : values) { System.out.println(s); }/* zhangsan lisi wangwu zhaoliu */ //通过key删除value System.out.println(map.remove(2));//lisi //清空map集合 map.clear(); //判断Map集合是否为空 System.out.println(map.isEmpty());//true

    Set keySet() 获取Map集合所有的key(多有的键是一个Set集合)

    Set<Map.Entry<K,V>> entrySet() 将Map集合转换成一个Set集合

    ​ 假设现在有一个Map集合

    map1集合对象

    key value


    1 zhangsan

    2 lisi

    3 wangwu

    4 zhaoliu

    Set set = map1.entrySet();

    Set集合对象


    1=zhangsan

    2=lisi

    3=wangwu

    4=zhaoliu

    注意:Map集合通过entrySet()方法转换成的这个Set集合,Set集合中元素的类型是Map.Entry<K,V>

    Map.Entry和String一样,都是一种类型的名字,只不过Map.Entry是个静态内部类 ,是Map中的静态内部类

    Map集合的遍历

    获取所有的key,通过遍历key,来获取value

    //创建集合 Map<Integer,String> map = new HashMap<>(); //添加元素 map.put(1,"zhangsan"); map.put(2,"lisi"); map.put(3,"wangwu"); map.put(4,"zhaoliu"); //获取set集合 Set<Integer> set = map.keySet(); //通过遍历key来获取value for (Integer a : set) { System.out.println(a + "=" + map.get(a)); } Iterator<Integer> it = set.iterator(); while (it.hasNext()){ Integer key = it.next(); System.out.println(key + "=" + map.get(key)); }

    Set<Map.Entry<K,V>> entrySet() 遍历推荐使用这种方法

    在源码中,它并不是存成了key=value这种形式,而是在一个集合元素中存储了两个属性,可以通过getKey和getValue来获取

    Set<Map.Entry<Integer,String>> sets = map.entrySet(); for(Map.Entry<Integer,String> newSet : sets){ /*System.out.println(newSet);*///两种方法都可以 System.out.println(newSet.getKey() + "=" + newSet.getValue()); } Iterator<Map.Entry<Integer,String>> iterator = sets.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); }

    HashMap

    哈希表/散列表

    HashMap集合底层是哈希表/散列表的数据结构。

    哈希表是一个数据和单向链表的结合体。

    数据:在查询方面效率很高,随机增删方面效率很低。

    单向链表:在随机增删方面效率很高,在查询方面效率很低。

    哈希表将以上两种数据结构融合在一起,充分发挥他们各自的优点。

    集合底层源代码

    public class HashMap{ //Hashmap底层实际上就是一个数组。(一维数组) Node<K,V>[] table; //静态的内部类HashMap.Node static class Node<K,V>{ final int hash;//哈希值(哈希值是key的hashcode()方法的执行结果。哈希值可以通过哈希函数/算法,可以转换成数组的下标。) final K key;//存储到map集合中的key V value;//存储到map集合中的value Node<K,V> next;//下一个节点的内存地址。 }

    哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表。(数组和链表的集合体)

    获取以及存储方法的实现原理

    map.put(k,v)实现原理

    先将k,v封装到Node对象中。

    底层会调用k的hashCode()方法得出hash值

    通过哈希函数/哈希算法,将hash值转换成数组的下标。

    如果Node数组的这个下标对应的位置上没有任何元素,就把Node添加到这个位置上,如果对应位置上有元素/链表,此时会拿这个元素和这个链表上的每一个节点的k进行equals比较

    如果所有的equals方法返回的都是false,那么这个新节点将被添加到链表的末尾,如果其中有一个equals方法返回了true,那么这个节点的value将会被覆盖

    v = map.get(k)实现原理

    调用k的hashCode()方法得出hash值。通过哈希函数/哈希算法,将hash值转换成数组的下标。如果Node数组的这个下标对应的位置上没有任何元素,就返回null。如果这个位置上有单向链表,那么会拿着这个参数k和单项链表上的每个节点的k进行equals比较。如果所有equals方法返回false,那么get方法返回null,但只要其中一个节点返回true,那么这个节点的value就是要找的value。get方法将这个value返回。

    总结:为什么哈希表的随机增删和查询效率都很高

    增删是在链表上完成的。查询也不需要完全扫描,只需要进行部分扫描但哈希表没有纯数组的查询效率高,随机增删也没有链表的效率高,但都不弱在hashMap集合中同一个单项链表上的所有元素的hash值是相同的,因为他们的数组下标是相同的,但同一个链表的k和k的值肯定都不相等重点:hashMap集合的key会先后调用两个方法,第一个是hashCode()方法,第二个是equals()方法,那么这连个方法就都需要重写,因为equals方法默认比较的是两个对象的内存地址

    HashMap集合key部分的特点:无序,不可重复

    无序:通过hashCode()方法以后不一定放到哪一个链表上

    不可重复:equals()方法保证了hashMap集合的key不可重复。如果key重复了,会把旧的value覆盖。

    放到HashMap集合key部分的元素其实就是放到HashSet集合中了。所以hashSet集合中的元素也需要同时重写hashCode()+equals()方法。

    当重写hashCode()方法不当,导致hashCode方法只返回一个固定值时,那这个hashMap底层就会变成一个纯单向链表,那就无法发挥hashMap集合的查询的优势,称为散列分布不均匀。如100个元素,分为10个单向链表,每个链表平均10个元素,就是分布均匀的。

    如果hashCode返回的hash值太多或全部不一样,会导致HashMap底层无限接近于数组,那就无法发挥hashMap的随机增删的优势,也是散列分布不均匀。

    HashMap集合的初始化容量( capacity )为16,默认加载因子为0.75

    ​ 默认加载因子是当hashMap集合底层数组的容量达到75%的时候,数组开始扩容。扩容量为2倍 左移一位

    ​ 官方建议hashMap集合的初始化容量必须是2的幂,因为这是达到散列分布,提高HashMap集合的存取效率所必须的,不过用户也不用在意 因为Z在源码中已经写好了,当用户输入一个初始化容量时(传进来会先减一,如果用户输入4,他的代码会把4变成8),hashMap会选择一个刚刚好大于它的2的幂作为初始化容量

    一些注意点

    思考题:equals方法什么情况下不调用

    put(k,v)

    ​ hashCode所对应的数组下标位置没有元素时

    ​ k.hashCode()方法返回哈希值,

    ​ 哈希值经过哈希算法转换成数组下标。

    ​ 数组下标位置上如果时null,equals不需要执行

    get(k,v)

    ​ hashCode所对应的数组下标位置没有元素时

    ​ k.hashCode()方法返回哈希值,

    ​ 哈希值经过哈希算法转换成数组下标。

    ​ 数组下标位置上如果时null,equals不需要执行

    注意点:

    如果一个类的equals方法重写了,那么hashCode()方法必须重写。

    并且equals方法返回的如果时true,hashCode()方法返回的值必须一样。

    euqals方法返回true表示两个对象相同;在同一个单项链表上比较。那么对于同一个单向链表上的节点来说,他们的哈希值也必须都是相同的。所以hashCode()方法的返回值也应该相同

    放在hashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法。

    对于哈希表数据结构来说:

    ​ 如果o1 和 o2 的hash值相同,一定时放在同一个单向链表上。

    ​ 当然如果o1 和 o2 的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”

    ​ 只要任意一条链表上有两个元素,那这两个元素一定发生了哈希碰撞

    Set<Student> students = new HashSet<>(); students.add(new Student("柳柴")); students.add(new Student("柴柕")); //往map集合中随机添加5个元素 Random random = new Random(); for (int i = 0; i < 10000 ; i++) { students.add(new Student(String.valueOf(random.nextInt(5)))); } System.out.println(students.size()); Object[] objects = students.toArray(); for (int i = 0; i < objects.length; i++) { int x = objects[i].hashCode(); for (int j = 0; j < objects.length; j++) { if (objects[j].hashCode() == x && !(i == j)){ System.out.println(objects[i] + "=" + objects[j]); System.out.println("相同"); } } }/* 这两个元素的hashCode值相同 Student{name='柳柴'}=Student{name='柴柕'} 相同 Student{name='柴柕'}=Student{name='柳柴'} 相同 */

    map集合允许key为空

    hashMap集合的key null值只能有一个

    //创建一个没有泛型的map集合 Map map = new HashMap(); //map集合允许key为null map.put(null,null); //key重复value覆盖 map.put(null,100); System.out.println(map.size()); //通过key获取value System.out.println(map.get(null));//100

    HashTable和HashMap的比较

    不同点:

    HashTable的key和value都不允许为空,会抛出一个空指针异常

    Map map = new Hashtable(); //key不允许为空 map.put(null,"123"); //Exception in thread "main" java.lang.NullPointerException //value也不允许为空 map.put(123,null); //Exception in thread "main" java.lang.NullPointerException

    HashMap的key和value都可以

    HashTable方法都带有synchronized:线程安全的。

    线程安全有其他的方案,这个HashTable对线程的处理导致效率较低,使用较少了

    HashTable初始化容量为11 默认加载因子0.75 扩容是老容量的2倍再加1

    HashMap初始化容量16 默认加载因子0.75 扩容为2倍

    相同点:底层都是哈希表

    Properties

    只需要掌握Properties属性类的相关方法即可

    Properties是一个Map集合,继承HashTable,Properties的key和value都是String类型。

    Properties被称为属性类对象

    Properties是线程安全的

    Properties的两个方法

    String getProperty(String key, String defaultValue)

    String getProperty(String key)

    //创建一个属性类对象 Properties pro = new Properties(); //需要掌握Properties中两个方法 pro.setProperty("url","jdbc:mysql:localhost:3306"); pro.setProperty("driver","com.mysql.jdbc.Driver"); pro.setProperty("username","root"); pro.setProperty("password","123"); //通过key获取value String url = pro.getProperty("url"); String driver = pro.getProperty("driver"); String username = pro.getProperty("username"); String password = pro.getProperty("password"); System.out.println(url); System.out.println(driver); System.out.println(username); System.out.println(password);
    Processed: 0.011, SQL: 8