Java基础篇-集合(Map)

249 阅读4分钟

前言

Java有多种方式保存对象引用,如数组,但是数组具有固定尺寸,故提供了一些集合类(也称容器类)来解决这个问题。本文将详细的介绍Map集合。

Java Map简介

Map是一个接口类,一种依照键存储元素的容器,它有很多的实现类,它有以下特性:

  • 以键值对(key-value)存储形式存储,每个键(key)都有一个对应的值(value)
  • key和value的数据类型可以自定义(可以相同,也可以不同)
  • 无序的(插入的顺序和读取的顺序是不一样的),但也可以实现有序(实现类:LinkedHashMap)
  • 键(key)不能重复,值(value)可以重复,如果键值重复则覆盖对应value
  • key可以为null,value也可以为null

Map常用的实现类

  • HashMap

    最常用的一个Map实现类,底层基于数组+链表+红黑树实现(jdk1.8)

    特点:
    1、线程不安全,速度快
    2、容量不固定,默认容量(1 << 4) 16,如果传了容量参数则计算得到大于等于参数的最小的2的整数次幂 比如 :17 ---》32
    3、随元素增多而扩容,扩容因子0.75
    4、当链表长度达到8时,转红黑树

    HashMap相关面试题:

    1、谈谈hashMap
    底层基于数组+链表+红黑树实现(jdk1.8),默认容量16.
    当执行put方法时,通过hash值定位到数组对应位置。 如果对应位置无值则直接赋值等。
    2、hash值是个什么样的值? int类型;
    数组默认长度只有16,那如何用int类型的值定位到数组具体位置? 通过hash值和数组长度(n)-1进行按位&,关键代码tab[i = (n - 1) & hash]),这也是为什么每次扩容按照2倍(oldCap << 1)扩容的原因
    3、如果设置初始容量为17 (new HashMap<>(17)),那最后生成的数组大小是多少? 有一个tableSizeFor(int cap)方法,计算出大于等于参数的最小的2的整数次幂
    put方法相关流程图: 流程图.jpg

    tableSizeFor方法源码:

    static final int tableSizeFor(int cap) {  
        // cap - 1是为了 防止cap已经是2的幂
        // 假设cap = 17, n = 16 即二进制的 10000 
        int n = cap - 1;  
        // >>> 向右无符号位位移,10000 >>> 1 = 1000;  
        // 然后 |=,用n和右边位移之后的值 进行按位或之后赋值给n,10000 | 1000 即 n = 11000 对应十进制的24  
        n |= n >>> 1;  
        // 11000 >>> 2 = 110; 11000 | 110 即 11110 对应十进制的30  
        n |= n >>> 2;  
        // 11110 >>> 4 = 1; 11110 | 1 即 11111 对应十进制的31  
        n |= n >>> 4;  
        // 11111 >>> 8 = 0; 11111 | 0 即 11111 对应十进制的31  
        n |= n >>> 8;  
        n |= n >>> 16;  
        // 通过无符号位位移+按位或,将(cap - 1)对应的二进制所占的位数全部置1 
        // 和最大限制大小MAXIMUM_CAPACITY(1 << 30)比较 确定返回值
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;  
    }
    
  • ConcurrentHashMap

    一种线程安全的map, key和value不能为null

    jdk1.8之前采用分段锁,锁的力度较大;
    jdk1.8: 如果通过hash值定位到数组具体位置为空时,采用cas(compareAndSwapObject)乐观锁思想,判断数组对应位置为null再交换值。 如果对应位置不为空时:采用synchronized(悲观锁)锁该位置上的Node

  • LinkedHashMap

    一种有序的Map集合,继承HashMap

    底层通过一个双向链表记录实现有序性(插入顺序和读取顺序一致),当工作中有希望Map读取顺序和插入顺序一致时可以使用ConcurrentHashMap了。

    示例:

    public static void main(String[] args) {  
        testLinkedHashMap();  
        testHashMap();  
    }  
    
    private static void testLinkedHashMap() {  
        LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>(8);  
        linkedHashMap.put("2023-1", "2023-1");  
        linkedHashMap.put("2023-2", "2023-2");  
        linkedHashMap.put("2023-3", "2023-3");  
        linkedHashMap.forEach((key, value) ->  
            System.out.println("linkedHashMap key:" + key + ",value:" + value)  
        );  
    }  
    
    private static void testHashMap() {  
        HashMap<String, String> hashMap = new HashMap<>(8);  
        hashMap.put("2023-1", "2023-1");  
        hashMap.put("2023-2", "2023-2");  
        hashMap.put("2023-3", "2023-3");  
        hashMap.forEach((key, value) ->  
            System.out.println("hashMap key:" + key + ",value:" + value)  
        );  
    }
    

    运行结果

    linkedHashMap key:2023-1,value:2023-1
    linkedHashMap key:2023-2,value:2023-2
    linkedHashMap key:2023-3,value:2023-3
    hashMap key:2023-2,value:2023-2
    hashMap key:2023-3,value:2023-3
    hashMap key:2023-1,value:2023-1

  • TreeMap

    key值有序的一种散列表。

    底层基于红黑树实现,添加或删除元素时较HashMap慢,因为需要维护内部的红黑树来保证key值的有序性。
    key值不能为null,底层会对传入的key进行类型强转,如果传null会出现空指针异常。 相关源码 :comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2) : comparator.compare((K)k1, (K)k2);