Map旗下映射类总结

本文详细介绍了Java中Map接口的几个实现类:HashMap、LinkedHashMap、IdentityHashMap、WeakHashMap、TreeMap和EnumMap。HashMap基于哈希表,允许key的哈希值冲突;LinkedHashMap保持插入顺序或访问顺序;IdentityHashMap使用`==`比较key,允许key相同;WeakHashMap的key是弱引用,可能被GC回收;TreeMap通过红黑树保持key的有序性;EnumMap针对枚举类型,速度快且有序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java.util包中,Map旗下的映射类有这六个:HashMap、LinkedHashMap、IdentityHashMap、WeakHashMap、TreeMap、EnumMap,下面一一分析

1.HashMap
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable

声明,继承了AbstractMap类,实现了Map接口
下面看一组相关联的成员变量和数据类型:

transient Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
        ...
}

从这里我们就可以得出HashMap的数据结构:
首先HashMap是基于数组的,即这个table数组,该数组中存储的每一个元素实质上是一个链表,也称为一个桶,桶里是一些前后链接的Node
table数组的元素是怎样组织的?什么样的Node会被放到一个桶里?我们接着看:

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //重点看i = (n - 1) & hash,此即为寻桶算法,hash为key的哈希值,n为table的长度即桶的个数,(n - 1) & hash即为该元素要存入的桶的编号,可以看出具有相同hash值的元素会被存入同一个桶里,但一个桶里不止有一种hash
        //若桶里没有元素,则直接放入该元素
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    //若桶中不存在hash和key都与该元素相等的元素,则把该元素挂在最后
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //若在桶中存在hash和key都与该元素相等的元素,则认为key已存在
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            //若key已存在,用新value代替旧value,返回旧value
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
}
public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
}

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    //找到该元素所在的桶
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            //在桶中寻找hash和key与该元素都相等的元素
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

从元素的存取方法中可以看出,table元素(桶)的组织方式是,先算出key的hash值,然后利用(n - 1) & hash这个公式计算出桶的序号,即为该元素要放入的桶。可以看出相同hash值的元素一定会放入同一个桶中,但一个桶中也会包含不同hash值的元素
元素在放入桶之前会被包装成Node类型,在桶中以链表的形式存在。并且会先遍历链表,如果已经有与该元素hash值和key值都相等的结点,则直接修改该结点的值,并返回原值;若没有与该元素hash值和key值都相等的结点,则把该Node挂在链表最后
接下来看其他方法:

//构造方法
public HashMap(int initialCapacity, float loadFactor);
public HashMap(int initialCapacity);
public HashMap();
public HashMap(Map<? extends K, ? extends V> m);

initialCapacity(初始容量)、loadFactor(装填因子)都有默认值,也可以人为指定
初始容量指table数组的容量,即桶的个数
填充比达到装填因子后会进行扩容,即容量变为原来的两倍,每个桶中的链表会按奇偶次序分到两个桶中,减少了链表长度,提高了查找效率

static final int TREEIFY_THRESHOLD = 8;
static final int UNTREEIFY_THRESHOLD = 6;

这两个阈值的含义分别是:如果链表长度达到8,就把该链表转换为红黑树;如果红黑树结点降低为6个,就把红黑树转换为链表
HashMap的其他方法:

public int size();

public boolean isEmpty();

public V get(Object key);

public boolean containsKey(Object key);

public V put(K key, V value);

public void putAll(Map<? extends K, ? extends V> m);

public V remove(Object key);

public void clear();

public boolean containsValue(Object value);

//三种视图,无序
public Set<K> keySet();
public Collection<V> values();
public Set<Map.Entry<K,V>> entrySet();

public V getOrDefault(Object key, V defaultValue);

public V putIfAbsent(K key, V value);

//key和value都相等时才删除
public boolean remove(Object key, Object value);

//key和value都相等时才替换
public boolean replace(K key, V oldValue, V newValue);

public V replace(K key, V value);

//函数式接口编程
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction);

public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction);

public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction);

public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction);

//遍历无序
public void forEach(BiConsumer<? super K, ? super V> action);

public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function);

最后需要注意一点,虽然HashMap的遍历是无序的,但在jdk8中由于hash算法的改变,若key是由Integer或数字型的String组成,则遍历次序呈现出一种按key升序排列的形式,但仍然要认为HashMap的遍历是无序的

2.LinkedHashMap
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>

声明,继承了HashMap类,实现了Map接口

static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
}

可以看到其数据结构Entry继承了HashMap的Node,并且新增了before、after两个指针,正是这两个指针保存了元素的插入次序
构造方法:

public LinkedHashMap(int initialCapacity, float loadFactor) {
    super(initialCapacity, loadFactor);
    accessOrder = false;
}

public LinkedHashMap(int initialCapacity) {
    super(initialCapacity);
    accessOrder = false;
}

public LinkedHashMap() {
    super();
    accessOrder = false;
}

public LinkedHashMap(Map<? extends K, ? extends V> m) {
    super();
    accessOrder = false;
    putMapEntries(m, false);
}

public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

可以看到,如不明确指定,accessOrder参数默认为false
accessOrder参数为false时,视图和遍历方法按元素插入顺序排列
accessOrder参数为true时,视图和遍历方法按元素访问次序排列(在插入顺序的基础上,每访问过一个元素就将它排到最后)
所以LinkedHashMap的三个视图

public Set<K> keySet();
public Collection<V> values();
public Set<Map.Entry<K,V>> entrySet();

以及其forEach方法:

public void forEach(BiConsumer<? super K, ? super V> action);

其顺序都与accessOrder参数有关
其他方法与HashMap类似,不再赘述

3.IdentityHashMap
public class IdentityHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, java.io.Serializable, Cloneable

声明,继承了AbstractMap类,实现了Map接口,其内部结构与HashMap类似
看构造方法:

public IdentityHashMap();
public IdentityHashMap(int expectedMaxSize);
public IdentityHashMap(Map<? extends K, ? extends V> m);

其与HashMap的不同点为,HashMap不允许出现key相同的情况,但IdentityHashMap却有可能出现相同的key,我们可以从put方法中看出原因:

public V put(K key, V value) {
        final Object k = maskNull(key);

        retryAfterResize: for (;;) {
            final Object[] tab = table;
            final int len = tab.length;
            int i = hash(k, len);

            for (Object item; (item = tab[i]) != null;
                 i = nextKeyIndex(i, len)) {
                //==运算符比较的是地址,而不是key的实际值
                if (item == k) {
                    @SuppressWarnings("unchecked")
                        V oldValue = (V) tab[i + 1];
                    tab[i + 1] = value;
                    return oldValue;
                }
            }

            final int s = size + 1;
            // Use optimized form of 3 * s.
            // Next capacity is len, 2 * current capacity.
            if (s + (s << 1) > len && resize(len))
                continue retryAfterResize;

            modCount++;
            tab[i] = k;
            tab[i + 1] = value;
            size = s;
            return null;
        }
}

对比一下HashMap和IdentityHashMap的put方法中判断key相等的语句:

//HashMap
(k = p.key) == key || (key != null && key.equals(k))
//IdentityHashMap
item == k

可见HashMap当key的equals方法或==运算有一个为真时,就判定key相等;而IdentityHashMap只有==运算为真时,才判定key相等,所以会出现key的值相等的情况
其他方法与HashMap类似,不再赘述

4.WeakHashMap
public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>

声明,继承了AbstractMap类,实现了Map接口
看构造方法:

public WeakHashMap(int initialCapacity, float loadFactor);
public WeakHashMap(int initialCapacity);
public WeakHashMap();
public WeakHashMap(Map<? extends K, ? extends V> m);

注意这个数据结构:

private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>

可以看出WeakHashMap中的键值对Entry继承了WeakReference,即弱引用,所以可能随时被GC回收,这就是它与HashMap的不同点。这种特性可用于需要缓存的场景
看这个成员变量:

private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

被回收的引用保存在这个队列中,以备更新映射
其他方法与HashMap类似,不再赘述

5.TreeMap
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable

声明,继承了AbstractMap类,实现了NavigableMap接口
TreeMap是有序的映射,用红黑树进行排序,且有一系列的导航方法

private final Comparator<? super K> comparator;

根据比较器来进行key的排序
看构造方法:

public TreeMap();//无构造器,按key的自然序升序排列
public TreeMap(Comparator<? super K> comparator);
public TreeMap(Map<? extends K, ? extends V> m);
public TreeMap(SortedMap<K, ? extends V> m);

看其他方法:

public int size();

public boolean containsKey(Object key);

public boolean containsValue(Object value);

public V get(Object key);

//返回当前的比较器
public Comparator<? super K> comparator();

public K firstKey();

public K lastKey();

public void putAll(Map<? extends K, ? extends V> map);

public V put(K key, V value);

public V remove(Object key);

public void clear();

public Map.Entry<K,V> firstEntry();

public Map.Entry<K,V> lastEntry();

public Map.Entry<K,V> pollFirstEntry();

public Map.Entry<K,V> pollLastEntry();

public Map.Entry<K,V> lowerEntry(K key);

public K lowerKey(K key);

public Map.Entry<K,V> floorEntry(K key);

public K floorKey(K key);

public Map.Entry<K,V> ceilingEntry(K key);

public K ceilingKey(K key);

public Map.Entry<K,V> higherEntry(K key);

public K higherKey(K key);

public Set<K> keySet() {return navigableKeySet();}
public NavigableSet<K> navigableKeySet();

public NavigableSet<K> descendingKeySet();

public Collection<V> values();

public Set<Map.Entry<K,V>> entrySet();

public NavigableMap<K, V> descendingMap();

public NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive);

public NavigableMap<K,V> headMap(K toKey, boolean inclusive);

public NavigableMap<K,V> tailMap(K fromKey, boolean inclusive);

public SortedMap<K,V> subMap(K fromKey, K toKey);

public SortedMap<K,V> headMap(K toKey);

public SortedMap<K,V> tailMap(K fromKey);

public boolean replace(K key, V oldValue, V newValue);

public V replace(K key, V value);

public void forEach(BiConsumer<? super K, ? super V> action);

public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function);
6.EnumMap
public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V> implements java.io.Serializable, Cloneable

声明,继承了AbstractMap类

private final Class<K> keyType;

keyType指key所属的枚举类型
看put方法:

public V put(K key, V value) {
        typeCheck(key);

        int index = key.ordinal();
        Object oldValue = vals[index];
        vals[index] = maskNull(value);
        if (oldValue == null)
            size++;
        return unmaskNull(oldValue);
}

可以看出value存在vals数组中,其顺序与枚举key的顺序是一致的,所以用key.ordinal()得到key的索引,也就得到了对应的value的索引。故其查找速度要比HashMap快
看构造方法:

public EnumMap(Class<K> keyType);
public EnumMap(EnumMap<K, ? extends V> m);
public EnumMap(Map<K, ? extends V> m);

再看其他方法:

public int size();

public boolean containsValue(Object value);

public boolean containsKey(Object key);

public V get(Object key);

public V put(K key, V value);

public V remove(Object key);

public void putAll(Map<? extends K, ? extends V> m);

public void clear();

public Set<K> keySet();

public Collection<V> values();

public Set<Map.Entry<K,V>> entrySet();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值