Map接口几个常见的实现类

HashMap

HashMap是 Map接口使用频率最高的实现类

  • 允许使用null键和null值,与HashSet一样,不保证映射的顺序。
  • 所有的key构成的集合是Set:无序的、不可重复的。所以,key所在的类要重写:equals()和hashCode()
  • 所有的value构成的集合是Collection:无序的、可以重复的。所以,value所在的类要重写:equals()
  • 一个key-value构成一个entry
  • 所有的entry构成的集合是Set:无序的、不可重复的
  • HashMap判断两个key相等的标准是:两个key通过equals()方法返回 true,hashCode 值也相等。
  • HashMap判断两个value相等的标准是:两个value通过equals()方法返回 true。

存储结构

  • JDK 7及以前版本:HashMap是数组+链表结构(即为链地址法)

  • JDK 8版本发布以后:HashMap是数组+链表+红黑树实现。

QQ截图20210310203911 - Map接口几个常见的实现类

QQ截图20210310203932 - Map接口几个常见的实现类

下面是JDK1.8HashMap存储结构的介绍:

HashMap的内部存储结构其实是 数组+ 链表+ 树 的结合。当实例化一个 HashMap时,会初始化initialCapacity和loadFactor,在put第一对映射关系 时,系统会创建一个长度为initialCapacity的Node数组,这个长度在哈希表 中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为“桶”(bucket),每个bucket都有自己的索引,系统可以根据索引快速的查 找bucket中的元素。

每个bucket中存储一个元素,即一个Node对象,但每一个Node对象可以带 一个引用变量next,用于指向下一个元素,因此,在一个桶中,就有可能 生成一个Node链。也可能是一个一个TreeNode对象,每一个TreeNode对象可以有两个叶子结点left和right,因此,在一个桶中,就有可能生成一个 TreeNode树。而新添加的元素作为链表的last,或树的叶子结点。

那么HashMap 什么时候进行扩容和树形化呢 ?

当HashMap中的元素个数超过数组大小*loadFactor(数组总大小length,不是数组中个数) 时 , 就 会 进 行 数 组 扩 容 , loadFactor 的 默 认 值(DEFAULT_LOAD_FACTOR)为0.75,这是一个折中的取值。也就是说,默认 情况下,数组大小(DEFAULT_INITIAL_CAPACITY)为16,那么当HashMap中 元素个数超过16*0.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元 素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知 HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

当HashMap中的其中一个链的对象个数如果达到了8个,此时如果capacity没有达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链会变成 树,结点类型由Node变成TreeNode类型。当然,如果当映射关系被移除后,下次resize方法时判断树的结点个数低于6个,也会把树再转为链表。

添加元素过程

向HashMap中添加entry1(key,value),需要首先计算entry1中key的哈希值(根据key所在类的hashCode()计算得到),此哈希值经过处理以后,得到在底层Entry[]数组中要存储的位置i。如果位置i上没有元素,则entry1直接添加成功。如果位置i上已经存在entry2(或还有链表(或树)存在的entry3,entry4),则需要通过循环的方法,依次比较entry1中key和其他的entry。如果彼此hash值不同,则直接添加成功。如果hash值不同,继续比较二者是否equals。如果返回值为true,则使用entry1的value
去替换equals为true的entry的value。如果遍历一遍以后,发现所有的equals返回都为false,则entry1仍可添加成功。entry1指向原有的entry元素。

HashMap源码中的重要常量

  • DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16

  • MAXIMUM_CAPACITY : HashMap的最大支持容量,2^30

  • DEFAULT_LOAD_FACTOR :HashMap的默认加载因子

  • TREEIFY_THRESHOLD :Bucket中链表长度大于该默认值,转化为红黑树

  • UNTREEIFY_THRESHOLD :Bucket中红黑树存储的Node小于该默认值,转化为链表

  • MIN_TREEIFY_CAPACITY :桶中的Node被树化时最小的hash表容量。(当桶中Node的数量大到需要变红黑树时,若hash表容量小于MIN_TREEIFY_CAPACITY时,此时应执行 resize扩容操作。这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。)

  • table :存储元素的数组,总是2的n次幂

  • entrySet: 存储具体元素的集

  • size :HashMap中存储的键值对的数量

  • modCount :HashMap扩容和结构改变的次数。

  • threshold :扩容的临界值,=容量*填充因子

  • loadFactor: 填充因子

LinkedHashMap

保证在遍历map元素时,可以按照添加的顺序实现遍历。原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。对于频繁的遍历操作,此类执行效率高于HashMap。

LinkedHashMap hashMap = new LinkedHashMap();
hashMap.put("name", "james");
hashMap.put("age", 36);
hashMap.put("salary", 4000.0);
hashMap.put("team", "lakers");

// 遍历key
Set set = hashMap.keySet();
for (Object s:set) {
    System.out.println(s);
}

// 结果如下
name
age
salary
team

TreeMap

  • TreeMap存储 Key-Value 对时,需要根据 key-value 对进行排序。TreeMap 可以保证所有的 Key-Value 对处于有序状态。

  • TreeSet底层使用红黑树结构存储数据

  • TreeMap的Key的排序:
    自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有 的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException

    定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口

  • TreeMap判断两个key相等的标准:两个key通过compareTo()方法或者compare()方法返回0。

Properties

该类常见应用就是用来获取配置文件信息。如下一个配置文件:

user=root
pass=
port=3306
dbname=hotel

使用Properties读取配置文件信息

FileInputStream fis = null;
try {
    Properties pros = new Properties();

    fis = new FileInputStream("jdbc.properties");
    pros.load(fis);//加载流对应的文件

    String name = pros.getProperty("dbname");
    String port = pros.getProperty("port");

    System.out.println("dbname = " + name + ", port = " + port);
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if(fis != null){
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}