Map接口主要用于存储双列数据,也就是存储键值对(Key-Value)。
HashMap:Map接口的主要实现类,线程不安全的,效率高,可以保存为null的key和value。
让我们看一下HashMap的底层实现。
在jdk7以前,HashMap是用数组加链表的形式存储的。
在jdk8之后,HashMap改为了数组加链表加红黑树的存储方式。
新元素插入时:
在8(jdk8)的时候,实例化HashMap时会创建一个空的数组,在首次put时创建大小为16的一维数组,在插入元素时首先计算出此次插入元素的哈希值,通过算法求得此元素在数组中得位置,若此位置没有元素则插入成功。
若存在元素则比较此元素和已存在元素的哈希值,若不相同则插入到链表,若相同则调用元素所在类的equals:
若对比结果不相同,则插入成功。
若对比结果相同,则认为是已经存在的元素,插入失败。
扩容机制:
在jdk8时,当插入一个新元素且数组中元素超过临界值(当前容量×加载因子)时数组扩容为原来二倍。
在初始化时,即便给了初始容量也会得到2的次幂的大小的容量,因为系统会自动生成capacity=1,让capacity和初始化容量做对比,若小于则二进制右移一位,这里简单谈一下为什么要是2的次幂,个人理解如果是2的次幂,-1后对应的二进制位全为1,方便位运算,减少哈希冲突。
这里顺便谈一谈对加载因子的理解,加载因子越大,数组越不容易扩容,查询或插入时比较次数增多,性能会下降。
加载因子越小,越容易扩容,会浪费内存。
所以默认的0.75是java开发组经过测试得出较为安全的值。
解决哈希冲突的方式:
先解释一下什么是哈希冲突:简单的来说就是当前元素存储的位置已经有了一个元素。常用解决方法有拉链法、再哈希法、开放地址法。
jdk8中使用了链表加红黑树的方式
当前插入一个元素后,某一个索引位置以链表形式存在的数据个数>8且当前数组长度大于64时,此时此索引位置上所有的数据改为使用红黑树存储。
LinkedHashMap:就是在HashMap的基础上每一个元素定义了一个before和next用来存储上一个和下一个元素。
TreeMap:向TreeMap中添加元素时,要求key是同一个类的对象,而且要重写排序规则,在遍历时以此规则实现遍历。
Hashtable:Map接口的古老实现类,线程安全但是效率低,几乎不使用了,这里不做讨论。(如果需要使用线程安全的通常会将Hashmap丢进synchronizedMap方法中,也可以得到一个线程安全的map)
Properties:Hashtable的子类,该对象主要用于处理属性文件和配置文件 所以properties的key和value都是字符串类型。