jdk 集合大家族之Map

jdk 集合大家族之Map

前言:

之前章节复习了Collection接口相关,此次我们来一起回顾一下Map相关
。本文基于jdk1.8。

1. HashMap

1.1 概述

  • HashMap相对于List的数据结构而言,它是键值对的集合。主要通过提供key值来取相对应的value的值。而不是通过遍历来查找所需要的值。
  • key值允许一个为null value不限制
    • key通常使用String Integer这种不可变类作为key
  • 通过数组链表红黑树来实现,如下图所示

1.2 源码分析

  • 成员变量
/**
     * The default initial capacity - MUST be a power of two.
     */
     // 数组长度默认的容量为16  必须是2的幂
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    // 最大容量 为2^30
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     */
    // 默认的负载因子,当构造时没有指定负载因子时使用此值
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * The bin count threshold for using a tree rather than list for a
     * bin.  Bins are converted to trees when adding an element to a
     * bin with at least this many nodes. The value must be greater
     * than 2 and should be at least 8 to mesh with assumptions in
     * tree removal about conversion back to plain bins upon
     * shrinkage.
     */
    // 链表的长度大于8时转换为红黑树
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * The bin count threshold for untreeifying a (split) bin during a
     * resize operation. Should be less than TREEIFY_THRESHOLD, and at
     * most 6 to mesh with shrinkage detection under removal.
     */
    // 链表中的长度小于6时从红黑树转换会链表
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * The smallest table capacity for which bins may be treeified.
     * (Otherwise the table is resized if too many nodes in a bin.)
     * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
     * between resizing and treeification thresholds.
     */
    // 链表转换为红黑树也不是随便转换的,需要满足Map中最少有MIN_TREEIFY_CAPACITY个Node才能允许树形化(将链表转化为红黑树)。
    // 否则表中的过多节点会选择扩容
    // 为了避免扩容和树形化的冲突的选择冲突,此值最少为 4 * TREEIFY_THRESHOLD
    // 数组长度小于64也不会将链表转为红黑树(链表长度大于8但是小于64选择扩容数组,重新hash)
    static final int MIN_TREEIFY_CAPACITY = 64;
     /**
     * The table, initialized on first use, and resized as
     * necessary. When allocated, length is always a power of two.
     * (We also tolerate length zero in some operations to allow
     * bootstrapping mechanics that are currently not needed.)
     */
    //
    transient Node<K,V>[] table;

    /**
     * Holds cached entrySet(). Note that AbstractMap fields are used
     * for keySet() and values().
     */
    transient Set<Map.Entry<K,V>> entrySet;

    /**
     * The number of key-value mappings contained in this map.
     */
    // Map中键值对的数量
    transient int size;

    /**
     * The number of times this HashMap has been structurally modified
     * Structural modifications are those that change the number of mappings in
     * the HashMap or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the HashMap fail-fast.  (See ConcurrentModificationException).
     */
    // 修改次数
    transient int modCount;

    /**
     * The next size value at which to resize (capacity * load factor).
     *
     * @serial
     */
    // (The javadoc description is true upon serialization.
    // Additionally, if the table array has not been allocated, this
    // field holds the initial array capacity, or zero signifying
    // DEFAULT_INITIAL_CAPACITY.)
    // 下一次扩容的阈值 Map数组长度 * 负载因子
    int threshold;

    /**
     * The load factor for the hash table.
     *
     * @serial
     */
    // 负载因子
    final float loadFactor;
  • 构造函数
/**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and load factor.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    // 构造指定容量 指定负载因子的HashMap
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity); // tableSizeFor 在大于等于initialCapacity 范围内找最接近initialCapacity的2的n次幂
    }

    static final int tableSizeFor(int cap) {
        int n = cap - 1; // 防止cap为2的n次幂 造成翻倍现象
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and the default load factor (0.75).
     *
     * @param  initialCapacity the initial capacity.
     * @throws IllegalArgumentException if the initial capacity is negative.
     */
    // 构造指定容量 负载因子为默认0.75的HashMap
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    // 构造默认容量为16 负载因子为默认0.75的HashMap
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

    /**
     * Constructs a new <tt>HashMap</tt> with the same mappings as the
     * specified <tt>Map</tt>.  The <tt>HashMap</tt> is created with
     * default load factor (0.75) and an initial capacity sufficient to
     * hold the mappings in the specified <tt>Map</tt>.
     *
     * @param   m the map whose mappings are to be placed in this map
     * @throws  NullPointerException if the specified map is null
     */
    public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }
  • 内部键值对的存储 Node 和 TreeNode
     /**
     * Basic hash bin node, used for most entries.  (See below for
     * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
     */
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

     /**
     * Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn
     * extends Node) so can be used as extension of either regular or
     * linked node.
     */
     // 继承了Node(LinkedHashMap.Entry继承了HashMap.Node)
    static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;
        TreeNode(int hash, K key, V val, Node<K,V> next) {
            super(hash, key, val, next);
        }

    }  
  • 添加元素 put方法
//
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
}

// 计算hashcode 无符号右移16位然后做异或运算,比取余效率高
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 为空 或者数组长度为0 进行扩容
        if ((tab = table) == null || (n = tab.length) == 0)
           n = (tab = resize()).length;
        // (n-1)&hash 对数组长度减一与运算计算map数组中的下表 位运算比取余效率高
        // 如果数组的桶为空则直接放到桶内
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else { // 桶内已经有其他数据了,进行比较 扩容判断等操作
            // e用于原来的值的返回
            Node<K,V> e; K k;
            // p点为桶中第一个节点 如果与桶内第一个节点hash相同 ,判断是否key是否equals 相同则把原来的值赋值给它
            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) { // 遍历链表
                    if ((e = p.next) == null) { // 如果遍历到结尾了 则加到结尾
                        p.next = newNode(hash, key, value, null);
                        // 遍历到大于等于8时 进行转换红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k)))) // 如果遍历到key hash相同 并且equals时跳出循环
                        break;
                    // 继续向后遍历
                    p = e;
                }
            }
            // 存在相同值的key 替换并返回旧的value
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                // 默认为空实现,允许我们修改完成后做一些操作
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        // 达到 负载因子*capacity 进行扩容
        if (++size > threshold)
            resize();
        // 默认也是空实现,允许我们插入完成后做一些操作
        afterNodeInsertion(evict);
        return null;
    }
  • 转换成树
 final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        // 此处如果数组的长度小于64 只是进行扩容 rehash 并未进行红黑树操作
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do { // 将节点的数据结构转成TreeNode 形成一个新的链表
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            // 将新的树结点链表赋给第index个桶
            if ((tab[index] = hd) != null)
                // 真正的转成红黑树
                hd.treeify(tab);
        }
    }

     final void treeify(Node<K,V>[] tab) {
            TreeNode<K,V> root = null;
            // 遍历链表中的每一个TreeNode 当前的节点为x
            for (TreeNode<K,V> x = this, next; x != null; x = next) {
                next = (TreeNode<K,V>)x.next;
                x.left = x.right = null;
                // 当root为空 当前节点设置成根节点 根节点设置成黑色(x.red=false)
                if (root == null) {
                    x.parent = null;
                    x.red = false;
                    root = x;
                }
                else {
                    K k = x.key;
                    int h = x.hash;
                    Class<?> kc = null;
                    // 从根节点开始遍历 找到x插入的位置
                    for (TreeNode<K,V> p = root;;) {
                        int dir, ph;
                        K pk = p.key;
                        // 如果当前结点的hash值小于根结点的hash值,方向dir = -1;
                        if ((ph = p.hash) > h)
                            dir = -1;
                        // 如果当前结点的hash值大于根结点的hash值,方向dir = 1;
                        else if (ph < h)
                            dir = 1;
                        // 如果x结点的key没有实现comparable接口,或者其key和根结点的key相等(k.compareTo(x) == 0)仲裁插入规则
                        // 只有k的类型K直接实现了Comparable<K>接口,才返回K的class,否则返回null,间接实现也不行。
                        else if ((kc == null &&
                                  (kc = comparableClassFor(k)) == null) ||
                                 (dir = compareComparables(kc, k, pk)) == 0)
                            // 仲裁插入规则
                            dir = tieBreakOrder(k, pk);

                        TreeNode<K,V> xp = p;
                        // 如果p的左右结点都不为null,继续for循环,否则执行插入
                        if ((p = (dir <= 0) ? p.left : p.right) == null) {
                            x.parent = xp;
                            if (dir <= 0)
                                xp.left = x;
                            else
                                xp.right = x;
                            // 插入后进行树的调整
                            root = balanceInsertion(root, x);
                            break;
                        }
                    }
                }
            }
            moveRootToFront(tab, root);
        }

其他map相关集合的如下图所示,concurrentHashMap 更高性能的支持并发,可以单独讲一篇。

LinkdedHashMap

LinkedHashMap保存了记录的插入顺序,在用Iteraor遍历LinkedHashMap时,先得到的记录肯定是先插入的,在遍历的时候会比HashMap慢,有HashMap的全部特性。

TreeMap

TreeMap将存储的键值对进行默认排序,并且还能够指定排序的比较器,是线程不安全的。TreeMap不允许键值为null

Hashtable

是线程安全的HashMap 但是他的线程安全控制是通过任意一个线程只能有一个线程写Hashtable,所以并发性不是很好。相比于ConcurrentHashMap使用的是分段锁,并发性更好。所以推荐使用ConcurrentHashMap。

参考资料

  • https://www.cnblogs.com/rjzheng/p/11302835.html
  • https://www.jianshu.com/p/309ea054cbc9

原文地址:https://www.cnblogs.com/wei57960/p/12231545.html

时间: 2024-10-22 17:25:16

jdk 集合大家族之Map的相关文章

jdk 集合大家族之Collection

jdk 集合大家族之Collection 前言: ??此处的集合指的是java集合框架中的实现了Collection接口相关的类.所以主要为List Set 和 Queue 其他章节会专门介绍Map相关. 1. List 1.1 ArrayList 从数组中间删除某个元素需要很大代价,因为被删除之后的元素都要向数组的前端移动 适合查找和修改 ArrayList底层通过数组实现 顺序存储 读取 存入操作方便 插入.删除操作不方便 1.1.1 ArrayList分析 (jdk 8) 从ArrayLi

Java 集合系列 08 Map架构

java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例 Java 集合系列 05 Vector详细介绍(源码解析)和使用示例 Java 集合系列 06 Stack详细介绍(源码解析)和使用示例 Java 集合系列 07 List总结(LinkedList, ArrayList等使用场景和

集合框架之Map接口

Map是将键映射到值的对象.一个映射不能包含重复的键:每个键最多只能映射到一个值. Map 接口提供三种collection视图,允许以键集.值集或键-值映射关系集的形式查看某个映射的内容.映射顺序定义为迭代器在映射的 collection 视图上返回其元素的顺序.某些映射实现可明确保证其顺序,如 TreeMap 类:另一些映射实现则不保证顺序,如 HashMap 类. 所有通用的映射实现类应该提供两个"标准的"构造方法:一个 void(无参数)构造方法,用于创建空映射:一个是带有单个

java 集合大家族

在编写java程序中,我们最常用的除了八种基本数据类型,String对象外还有一个集合类,在我们的的程序中到处充斥着集合类的身影!java中集合大家族的成员实在是太丰富了,有常用的ArrayList.HashMap.HashSet,也有不常用的Stack.Queue,有线程安全的Vector.HashTable,也有线程不安全的LinkedList.TreeMap等等! 上面的图展示了整个集合大家族的成员以及他们之间的关系.下面就上面的各个接口.基类做一些简单的介绍(主要介绍各个集合的特点.区别

java提高篇(二十)-----集合大家族

在编写java程序中,我们最常用的除了八种基本数据类型,String对象外还有一个集合类,在我们的的程序中到处充斥着集合类的身影!java中集合大家族的成员实在是太丰富了,有常用的ArrayList.HashMap.HashSet,也有不常用的Stack.Queue,有线程安全的Vector.HashTable,也有线程不安全的LinkedList.TreeMap等等! 上面的图展示了整个集合大家族的成员以及他们之间的关系.下面就上面的各个接口.基类做一些简单的介绍(主要介绍各个集合的特点.区别

双列集合框架:Map(个人理解)

双列集合框架:Map1.常用实现类结构|----Map:双列数据,存储key-value对的数据 ---类似于高中的函数:y = f(x)* |----HashMap:作为Map的主要实现类:线程不安全的,效率高:存储null的key和value* |----LinkedHashMap:保证在遍历map元素时,可以照添加的顺序实现遍历.* 原因:在原的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素.* 对于频繁的遍历操作,此类执行效率高于HashMap.* |----Tre

Java学习:集合双列Map

数据结构 数据结构: 数据结构_栈:先进后出 入口和出口在同一侧 数据结构_队列:先进先出 入口和出口在集合的两侧 数据结构_数组: 查询快:数组的地址是连续的,我们通过数组的首地址可以找到数组,通过数组的索引可以快速的查找某一个元素. 增删慢:数组的长度是固定的,我们想要增加/删除一个元素,必须创建一个新数组,把原数组的数据复制过来 例: int[] arr = new int[]{1,2,3,4}; 要把数组索引是3的元素删除 必须创建一个新的数组,长度是原数组的长度-1 把原数组的其它元素

3.9 java基础总结集合①LIst②Set③Map④泛型⑤Collections

集合①LIst②Set③Map④泛型⑤Collections 一.List:有顺序,可重复实现类:1.ArrayList 广泛,适用于查询,增删不频繁的(类似数组,其长度可按需增大,增删效率慢)2.LinkedList 中间频繁增删的,查询效率低(双向链表,先进先出,不支持随机查找,必须重头开始查找,效率低)3.Vector 类似ArrayList,线程安全,性能低 二.Set:没有顺序,不可重复最多存一个null实现类:1.HashSet 速度快,不排序2.TreeSet 速度慢,内部排序,看

黑马程序员-集合框架(Map和Collections)

--Java培训.Android培训.iOS培训..Net培训.期待与您交流!--- 一.概述 Map是一种存储键值对的存储容器,而且保证键的唯一性.提供一种以"键"标识"值"的数据存储方式.接口形式为:Map<K,V>,其中K是此映射所维护的键的类型,V是映射值的类型.其有两个常用子类,HashMap和TreeMap,另有HashTable与HashMap功能类似,是早期版本.三者特点与不同如下: HashMap:JDK1.2版本出现,底层使用哈希表数