KeySet,Values,Entry三种方式遍历<br>Map 的每个实现类都应该实现 2 个构造方法:<br>无参构造方法,用于创建一个空的 map<br>参数是 Map 的构造方法,用于创建一个包含参数内容的新 map<br>
可以使用 Map 作为 Map 的值,但禁止使用 Map 作为 Map 的键。因为在这么复杂的 Map 中,equals() 方法和 hashCode() 比较难定义。<br>另一方面,你应该尽量避免使用“可变”的类作为 Map 的键。如果你将一个对象作为键值并保存在 Map 中,之后又改变了其状态,那么 Map 就会产生混乱,你所保存的值可能丢失。
AbstractMap
HashMap
元素是无序的,而且顺序会不定时改变
不是同步的
采用 拉链法 解决哈希冲突
key 用 Set 存放,所以想做到 key 不允许重复,key 对应的类需要重写 hashCode 和 equals 方法
允许空键,但放在第一个且只能有一个
插入、获取的时间复杂度基本是 O(1)(前提是有适当的哈希函数,让元素分布在均匀的位置)
遍历整个 Map 需要的时间与 桶(数组) 的长度成正比(因此初始化时 HashMap 的容量不宜太大)
桶内链表个数大于等于 8,就要调用 treeifyBin() 方法进行树形化,红黑树结构O(logn)
新初始化哈希表时,容量为默认容量,阈值为 容量*加载因子<br>已有哈希表扩容时,容量、阈值均翻倍<br>如果之前这个桶的节点类型是树,需要把新哈希表里当前桶也变成树形结构<br>复制给新哈希表中需要重新索引(rehash),这里采用的计算方法是<br>e.hash & (newCap - 1),等价于 e.hash % newCap<br>结合扩容源码可以发现扩容的确开销很大,需要迭代所有的元素,rehash、赋值,还得保留原来的数据结构。<br>所以在使用的时候,最好在初始化的时候就指定好 HashMap 的长度,尽量避免频繁 resize()。
LinkedHashMap
HashMap和双向链表合二为一,是一个将所有Entry节点链入一个双向链表双向链表的HashMap
它的迭代顺序默认是插入顺序,也可以是访问顺序
IdentityHashMap
使用==来比较key是否重复,及比较对象在内存中的地址
实现不同于HashMap,虽然也是数组,不过IdentityHashMap中没有用到链表,解决冲突的方式是计算下一个有效索引,并且将数据key和value紧挨着存在map中,即table[i]=key,那么table[i+1]=value。<br>
没有使用Object的hashCode方法,而是使用的System.identityHashCode方法,这是Object.hashCode方法根据对象的内存地址来计算散列码时所使用的方式
TreeMap
有序的key-value集合,它是通过红黑树实现
根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序
适用于按自然顺序或自定义顺序遍历键,否则一般使用hashmap
TreeSet是基于它实现的
提供了 Map 的基本实现,若要实现一个不可变的Map只需要实现唯一的抽象方法<br>public abstract Set<Entry<K,V>> entrySet();<br>
想要实现一个 可变的 Map,我们需要在上述操作外,重写 put() 方法,因为 默认不支持 put 操作,会抛出异常
WeakHashMap
WeakHashMap的键是“弱键”。在WeakHashMap中,当某个键不再正常使用时,会从WeakHashMap中被自动移除(会被垃圾回收)
如果在系统中需要一张很大的Map表,Map中的表项作为缓存使用(即使没能从该Map中取得相应的数据,系统也可以通过候选方案获取这些数据)虽然这样会消耗更多的时间,但是不影响系统的正常运行。<br>在这种场景下,使用WeakHashMap是最合适的。因为WeakHashMap会在系统内存范围内,保存所有表项,而一旦内存不够,在GC时,没有被引用的表项又会很快被清除掉,从而避免系统内存溢出。<br>
如果存放在WeakHashMap中的key都存在强引用,那么WeakHashMap就会退化成HashMap
key为null时,放入的是NULL_KEY,即:private static final Object NULL_KEY = new Object(),是一个静态常量。static不会被gc
只有通过remove方法才能删除null键所关联的value,建议在使用WeakHashMap的时候尽量避免使用null作为键。