文章目录

  • Map接口概述
  • Map接口和Collection接口的不同
  • Map接口的方法列表
  • 部分方法测试
  • Map接口的实现类
  • HashMap
  • LinkedHashMap
  • TreeMap
  • Hashtable与ConcurrentHashMap
  • Map集合关系粗略图


Map接口概述

  • 将键映射到值的对象
  • 不能包含重复的键
  • 每个键最多只能映射到一个值

Map接口和Collection接口的不同

  • Map是双列的,Collection是单列的
  • Map的键唯一,Collection的子体系Set是唯一的(实际上Set就是Map的键)
  • Map集合的数据结构值针对键有效,跟值无关,Collection集合的数据结构是针对元素有效

Map接口的方法列表

注:部分方法的作用解释可能存在偏差

方法

作用

int size();

返回map中 键值对 的个数

boolean isEmpty();

判断map是否为空(长度为0)

boolean containsKey(Object key);

判断map中是否包含给定键

boolean containsValue(Object value);

判断map中是否包含给定值

V get(Object key);

根据给定键,获取对应值

V put(K key, V value);

,若key不存在,则添加一对键值对,返回null;若key已存在则替换其value,返回旧的value

V remove(Object key);

根据键移除对应键值对

void putAll(Map<? extends K, ? extends V> m);

添加或设置给定Map所包含的所有键值对

void clear();

移除所有键值对

Set keySet();

获取以所有键为元素的Set集合

Collection values()

获取以所有值为元素的Collection集合

Set<Map.Entry<K, V>> entrySet();

获取map中所有键值对对象的set集合

default V getOrDefault(Object key, V defaultValue)

返回给定key对应的值,若给定key不存在或值为null则返回给定值defaultValue(1.8新增)

default void forEach(BiConsumer<? super K, ? super V> action)

遍历map,将每个键值对按照给定消费型函数规则改变(1.8新增)

default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)

遍历map中每个键值对对象,根据给定函数型函数规则替换键值对对象的 值 (1.8新增)

default V putIfAbsent(K key, V value)

如果map中不存在给定的key,或给定的key对应的值为null,则添加,否则返回原本的value(1.8新增)

default boolean remove(Object key, Object value)

若map中已存在给定的key-value键值对则移除,返回true;否则返回false(1.8新增)

default boolean replace(K key, V oldValue, V newValue)

若map中已存在给定的key-oldValue键值对,则替换为给定的key-newValue。返回true;否则返回false(1.8新增)

default V replace(K key, V value)

若map中已存在给定key且其映射的值不为null,则将其值替换为给定value,返回原本值,否则返回null

default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)

若给定key不存在或其所对应的值为null,则按照给定函数式函数计算得出新value作为该key的对应值,返回该新value;若给定key对应的值不为null或计算得出的新value为null,则返回原本的value(1.8新增)

default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)

若给定key存在且其对应值不为null,则按照给定函数式函数计算得出newValue,若newValue不为空,则将给定key的旧值替换为newValue,返回newValue,若newValue为空则移除给定key,返回null;否则返回null。(1.8新增)

default V compute(K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction)

根据给定函数式函数得出newValue。若newValue不为null,则添加或替换key-newValue键值对,返回newValue;若newValue为空,则移除key,返回null

default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)

如果Map中key对应的映射不存在或者为null,则将value关联到key上;否则执行remappingFunction,如果执行结果为null则删除key的映射,否则用该结果跟key关联。

部分方法测试

注:打印结果以注释方式呈现,部分例子可不能不是很恰当,见笑

@Test
    public void test1() {
        Map<Integer, String> map = new HashMap<>();
        //put方法
        map.put(1, "1");
        map.put(2, "2");
        String put = map.put(3, "3");
        String put1 = map.put(3, "33");
        System.out.println(put);  // null
        System.out.println(put1); // 3
        System.out.println(map); // {1=1, 2=2, 3=33}

        Map<Integer, String> map2 = new HashMap<>();
        map2.put(1, "11");
        map2.put(2, "22");
        map2.put(4, "44");
        //putAll方法
        map.putAll(map2);
        System.out.println(map); // {1=11, 2=22, 3=33, 4=44}

        //keySet()
        Set<Integer> keySet = map.keySet();
        System.out.println(keySet);// [1, 2, 3, 4]
        //values()
        Collection<String> values = map.values();
        System.out.println(values); //[11, 22, 33, 44]
        //entrySet()
        Set<Map.Entry<Integer, String>> entries = map.entrySet();
        //Map遍历的一种方式(注意此处调用的是Iterable的forEach方法)
        entries.forEach(e -> {
            Integer key = e.getKey();
            String value = e.getValue();
            System.out.println(key + "--" + value);
        });
        /* 结果:
            1--11
            2--22
            3--33
            4--44
         */

        //getOrDefault方法
        String aDefault = map.getOrDefault(5, "不存在该key");
        System.out.println(aDefault);// 不存在该key

        //forEach方法
        map.forEach((key, value) -> {
            if ((key & 1) == 1) {
                value += "(key是奇数)";
            } else {
                value += "(key是偶数)";
            }
            map.put(key, value);
        });
        System.out.println(map); //{1=11(key是奇数), 2=22(key是偶数), 3=33(key是奇数), 4=44(key是偶数)}

        //replaceAll方法
        map.replaceAll((key, val) -> {
            if (val.endsWith(")")) {
                return val.substring(0, 2);
            }
            return key.toString() + key + toString();
        });
        System.out.println(map); //{1=11, 2=22, 3=33, 4=44}

        //putIfAbsent方法
        String putIfAbsent1 = map.putIfAbsent(4, "王富贵");// 已存在该key -> 4==44
        System.out.println(putIfAbsent1); // 44
        System.out.println(map); // {1=11, 2=22, 3=33, 4=44}
        String putIfAbsent2 = map.putIfAbsent(5, "55");// 不存在该key
        System.out.println(putIfAbsent2); // null
        System.out.println(map); //{1=11, 2=22, 3=33, 4=44, 5=55}

        //remove(key,val)方法
        System.out.println("移除键值对 5==55 :" + map.remove(5, "55")); //移除键值对 5==55 :true
        System.out.println(map); //{1=11, 2=22, 3=33, 4=44}
        System.out.println("移除键值对 4==520 : " + map.remove(4, "520")); //移除键值对 4==520 : false
        System.out.println(map); //{1=11, 2=22, 3=33, 4=44}

        //replace(key,oldVal,newOld)方法
        boolean replace = map.replace(4, "44", "newOld 44");
        System.out.println(replace); //true
        System.out.println(map); // {1=11, 2=22, 3=33, 4=newOld 44}

        //replace(key,val)方法
        String oldVal = map.replace(4, "44");
        System.out.println(oldVal); //newOld 44
        System.out.println(map); //{1=11, 2=22, 3=33, 4=44}

        //computeIfAbsent方法
        String s = map.computeIfAbsent(4, key -> key.toString() + key.toString());//给定key存在且对应值不为空
        System.out.println(s); //44
        System.out.println(map); //{1=11, 2=22, 3=33, 4=44}
        String s2 = map.computeIfAbsent(5, key -> key.toString() + key.toString());//给定key不存在
        System.out.println(s2); //55
        System.out.println(map); //{1=11, 2=22, 3=33, 4=44, 5=55}

        //computeIfPresent方法
        String present1 = map.computeIfPresent(5, (key, oldValue) -> {
            String val = key.toString() + key.toString(); // 55
            if (oldValue.equals(val)) {
                return val + ":符合条件";
            }
            return null;
        });
        System.out.println(present1); //55:符合条件
        System.out.println(map); // {1=11, 2=22, 3=33, 4=44, 5=55:符合条件}
        //再次执行 5=55:符合条件
        String present2 = map.computeIfPresent(5, (key, oldValue) -> {
            String val = key.toString() + key.toString(); // 55
            if (oldValue.equals(val)) {
                return val + ":符合条件";
            }
            return null; //newValue为null时将删除该键值对
        });
        System.out.println(present2); //null
        System.out.println(map); //{1=11, 2=22, 3=33, 4=44}

        //compute方法
        String compute = map.compute(5, (k, v) -> k.toString() + k.toString());
        System.out.println(compute); //55
        System.out.println(map); // {1=11, 2=22, 3=33, 4=44, 5=55}

        //merge方法
        String merge = map.merge(6, "新val", (oldValue, newValue) -> oldValue + newValue);//给定key不存在
        System.out.println(merge); //新val
        System.out.println(map); //{1=11, 2=22, 3=33, 4=44, 5=55, 6=新val}

        String merge2 = map.merge(6, "66", (oldValue, newValue) -> newValue);//给定key已存在
        System.out.println(merge2); //66
        System.out.println(map); // {1=11, 2=22, 3=33, 4=44, 5=55, 6=66}

    }

Map接口的实现类

HashMap

键是哈希表结构,可以保证键的唯一性。线程不安全,HashMap最多只允许一条记录的键为Null,允许多条记录的值为 Null;。

jdk1.8前后HashMap底层结构的变化(原文浅谈HashMap的底层原理(JDK1.8之前与JDK1.8之后)):

一、在JDK1.8之前,HashMap的底层是采用数组+链表的方法,即用链表处理哈希冲突。插入元素是先通过HashMap中Key的hashcode()方法计算出插入到数组的位置,如果数组当前位置没有元素,直接插入;如果当前位置已有元素,通过equals方法比较key,如果key也相同,直接覆盖,如果key不相同,插入链表中。(即拉链法:数组每一格都代表可以“下挂”一个链表,当遇到哈希冲突时,插入到链表中即可。)
二、Jdk1.8之后,HashMap的底层结构在处理哈希冲突时有了较大改变,即采用数组+链表+红黑树的结构,当链表长度大于8时,链表自动转化为二叉树。这样做的好处是,提高当链表长度过长时,搜索速度过慢的问题。

有关HashMap的更多理解可参考这篇文章:HashMap和currentHashMap的知识总结

LinkedHashMap

继承自HashMap。使用Map接口的哈希表和链表实现,具有可预知的迭代顺序
此实现与HashMap的不同之处在于:LinkedHashMap维护着一个双向循环链表。此链表定义了迭代顺序,该迭代顺序通常就是存放元素的顺序。遍历速度比HashMap慢。

小测试

@Test
    public void test2() {
        HashMap<String,String> hashMap = new HashMap<>();
        hashMap.put("hello","11");
        hashMap.put("hash","22");
        hashMap.put("map","33");
        hashMap.put("Good","44");
        hashMap.put("Luck","55");
        hashMap.put(null,null);//允许一个键为null
        Set<Map.Entry<String, String>> entries = hashMap.entrySet();
        entries.forEach(System.out::println);
        /*  结果(遍历顺序与添加顺序不同):
            null=null
            Luck=33
            hello=11
            Good=33
            map=33
            hash=22
         */
        System.out.println("-----------------------------");

        LinkedHashMap<String,String> linkedHashMap = new LinkedHashMap<>();
        linkedHashMap.put("hello","11");
        linkedHashMap.put("hash","22");
        linkedHashMap.put("map","33");
        linkedHashMap.put("Good","44");
        linkedHashMap.put("Luck","55");
        linkedHashMap.put(null,null);//允许一个键为null
        Set<Map.Entry<String, String>> entrySet = linkedHashMap.entrySet();
        entrySet.forEach(System.out::println);
        /*  结果(遍历顺序与添加顺序相同) :
            hello=11
            hash=22
            map=33
            Good=44
            Luck=55
            null=null
         */
    }

TreeMap

键是红黑树结构,可以保证键的排序(自然排序或自定义排序)和唯一性线程不安全。不允许有null键,但允许有null值。

小测试

@Test
    public void test3() {
        //自然排序
        TreeMap<Character,String> treeMap = new TreeMap<>();
        treeMap.put('D',"DI");
        treeMap.put('B',"Battle");
        treeMap.put('A',"Abstract");
        treeMap.put('C',"Character");
        Set<Map.Entry<Character, String>> entries = treeMap.entrySet();
        entries.forEach(System.out::println);
        /* 结果:
            A=Abstract
            B=Battle
            C=Character
            D=DI
         */

        //自定义排序
        TreeMap<String,Integer> map = new TreeMap<>(Comparator.comparingInt(String::length)); 	//按照主键字符串的长度
        map.put("333",333);
        map.put("22",22);
        map.put("1",1);
        map.put("55555",333);
        map.put("4444",333);
        map.entrySet().forEach(System.out::println);
        /*  结果
            1=1
            22=22
            333=333
            4444=333
            55555=333
         */
    }

Hashtable与ConcurrentHashMap

  • 1.Hashtable: 线程安全,效率低。使用一把锁处理并发问题,当有多个线程访问时,需要多个线程竞争一把锁,容易导致阻塞。
  • 2.ConcurrentHashMap:线程安全,效率高。ConcurrentHashMap则从JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+红黑树的底层实现使之在多线程下效率高。

Map集合关系粗略图

Java 单个键值对 java 键值对类型_Java 单个键值对