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是数组+链表+红黑树实现。
下面是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();
}
}
}