java8 HashMap特点/实现概述

一、接口继承图

二、接口功能概述

Map接口包含的方法如下:

其中部分方法时java8新增的,下列方法时是不太常用的,且都是Map接口中的default方法。

  1. getOrDefault(Object key, V defaultValue) 如果存在key返回对应的value,否则返回defaultValue
  2. replaceAll(BiFunction<? super K, ? super V, ? extends V> function) 以key为维度替换所有的value,替换逻辑在function中实现
  3. putIfAbsent(K key, V value)     如果key存在则返回对应的value,否则将key和value添加到map中
  4. computeIfAbsent(K key,Function<? super K, ? extends V> mappingFunction)   如果key存在返回对应的value,如果不存在则通过function计算出value并放入map中
  5. computeIfPresent(K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction)  如果key存在则根据function计算一个新的value并新的value放入map,如果新value为null则从map中移除该key。
  6. compute(K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction)   通过function计算key对应的newValue,newValue不为空则放入map,否则在key存在的情况下移除该key。
  7. merge(K key, V value,BiFunction<? super V, ? super V, ? extends V> remappingFunction)   基于key对应的oldValue和value通过function计算新的newValue,newValue不为空则放入map,否则从map中移除key。

Cloneable Serializable接口功能描述参考:

       https://blog.csdn.net/xiaomingdetianxia/article/details/74453033

三、AbstractMap源码实现

      AbstractMap是Map接口的抽象实现类,将Set<Entry<K,V>> entrySet()方法留给子类实现,put(K key, V value)方法抛出不支持异常,keySet()方法和values()通过两个分别保存key和value的集合实现,其他方法都是基于entrySet()的返回值通过循环遍历实现,如get(K key)方法实现:

 

          这种循环遍历方式的实现性能较差,子类需要重写实现。AbstractMap中对子类比较有意义的实现是覆写了默认的toString(),equals(Object o),clone(), hashCode()方法,toString()可以将map中的key和value打印出来。

三、HashMap特点

1、允许key/value为null,但是key只能有一个null

2、非线程安全,多个线程同时操作同一个HashMap实例所做的修改在线程间不同步

3、遍历时不保证任何顺序,跟元素插入顺序或者访问顺序无关

4、进行遍历时如果执行HashMap的remove(Object key)或者put(Object value)方法时会快速失败,抛出异常ConcurrentModificationException。遍历时删除元素只能通过Iterator本身的remove()方法实现。参考如下代码:

    @Test
    public void test6() throws Exception {
        Map<String,Integer> map=new HashMap<>();
        map.put("1", 1);
        map.put("2", 2);
        map.put("3", 3);
        map.put("4", 4);
        for(String key:map.keySet()){
            if(key.equals("1")){
                //两种操作都会抛异常ConcurrentModificationException
//                map.put("1a", 1);
                map.remove("1");
            }
        }
        System.out.println(map);
    }

    @Test
    public void test7() throws Exception {
        Map<String,Integer> map=new HashMap<>();
        map.put("1", 1);
        map.put("2", 2);
        map.put("3", 3);
        map.put("4", 4);
        Iterator<String> iterator=map.keySet().iterator();
        while (iterator.hasNext()){
            String key=iterator.next();
            if(key.equals("1")){
                iterator.remove();
            }
        }
        System.out.println(map);
    }

四、HashMap实现概述

     HashMap中有两个重要的概念,容量(capacity)和负载因子(load factor),容量即该HashMap能够存储的最大的key个数,为便于扩容,容量都是2^n次方。负载因子用于计算执行扩容的阈值,默认的负载因子是0.75,该值是综合考虑HashMap的get/put等多种操作时的时间空间上的平衡,推荐使用默认值即可。假如容量时256,负载因子是0.75时,当key的总量超过192个时会进行扩容,即容量变成原来的两倍512,原有的存储的key会重新通过hash运算重新分配。

     HashMap实现的基础数据结构是数组(Node[]),又称哈希桶,数组的大小等于HashMap的容量。插入key时,对key的hashCode值对容量取余,找到该key在数组中的位置。无论hash分布是否均匀,数组中每个位置只能存储一个元素,假如哈希碰撞时多个key算出来的数组索引一样,怎么存储呢?答案很简单,假如数组某个位置已经有元素了(称该元素为头元素),往该位置继续插入新元素时,会通过链表的方式保存该元素,查找元素时通过头元素沿着链表逐一遍历。因为链表遍历的时间复杂度是O(n),哈希碰撞极端情况下,有N多key算出的数组索引一样即链表的长度为N时怎么办?答案是,当N超过阈值时会转化成红黑树结构,遍历查找的效率是O(lgn)。查找某个key时,先计算该key的hashCode值在数组中位置,对该位置的链表或者红黑树遍历,找到value相当的元素即可。数据结构如下图所示:

    

猜你喜欢

转载自blog.csdn.net/qq_31865983/article/details/83472760