JavaCore-彻底搞懂集合-collection接口集合体系详解四-Map接口-HashMap原理-TreeMap-Hashtable

概述

Map与Collection并列存在。用于保存具有映射关系的数据:key-value
Map 中的 key 和 value 都可以是任何引用类型的数据
Map 中的 key 用Set来存放, 不允许重复,即同一个 Map 对象所对应的类,须重写hashCode()和equals()方法,常用String类作为Map的“键”
key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到唯一的、确定的 value
Map接口的常用实现类: HashMap、 TreeMap、 LinkedHashMap和Properties。 其中, HashMap是 Map 接口使用频率最高的实现类
Map:双列数据,存储key-value对的数据 —类似于高中的函数:y = f(x)
参考
Hashmap的结构,1.7和1.8有哪些区别,史上最深入的分析

继承结构

|----Map:双列数据,存储key-value对的数据 —类似于高中的函数:y = f(x)
|----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
|----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。
原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
对于频繁的遍历操作,此类执行效率高于HashMap。
|----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序 底层使用红黑树
|----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
|----Properties:常用来处理配置文件。key和value都是String类型

Map中的key:无序的、不可重复的,使用Set存储所有的key —> key所在的类要重写equals()和hashCode() (以HashMap为例)
Map中的value:无序的、可重复的,使用Collection存储所有的value —>value所在的类要重写equals()
一个键值对:key-value构成了一个Entry对象。
Map中的entry:无序的、不可重复的,使用Set存储所有的entry

理解

在这里插入图片描述

常用方法

添加、删除、修改操作:
Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中
void putAll(Map m):将m中的所有key-value对存放到当前map中
Object remove(Object key):移除指定key的key-value对,并返回value
void clear():清空当前map中的所有数据
元素查询的操作:
Object get(Object key):获取指定key对应的value
boolean containsKey(Object key):是否包含指定的key
boolean containsValue(Object value):是否包含指定的value
int size():返回map中key-value对的个数
boolean isEmpty():判断当前map是否为空
boolean equals(Object obj):判断当前map和参数对象obj是否相等
元视图操作的方法:
Set keySet():返回所有key构成的Set集合
Collection values():返回所有value构成的Collection集合
Set entrySet():返回所有key-value对构成的Set集合

HashMap

在这里插入图片描述

特点

HashMap是 Map 接口使用频率最高的实现类。
允许使用null键和null值,与HashSet一样,不保证映射的顺序。null键也是只出现一次
所有的key构成的集合是Set:无序的、不可重复的。所以, key所在的类要重写:equals()和hashCode(),String类已经重写了这两个方法,所以在使用中经常以String作为key。
所有的value构成的集合是Collection:无序的、可以重复的。所以, value所在的类要重写: equals()
一个key-value构成一个entry
所有的entry构成的集合是Set:无序的、不可重复的
HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 方法返回 true,hashCode 值也相等。
HashMap 判断两个 value相等的标准是:两个 value 通过 equals() 方法返回 true。

HashMap的存储结构

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: 填充因子

JDK7

JDK 7及以前版本: HashMap是数组+链表结构(即为链地址法)
在这里插入图片描述

存储结构

HashMap的内部存储结构其实是数组和链表的结合。 当实例化一个HashMap时,系统会创建一个长度为Capacity的Entry数组, 这个长度在哈希表中被称为容量(Capacity), 在这个数组中可以存放元素的位置我们称之为“桶” (bucket), 每个bucket都有自己的索引, 系统可以根据索引快速的查找bucket中的元素。
每个bucket中存储一个元素, 即一个Entry对象, 但每一个Entry对象可以带一个引用变量, 用于指向下一个元素, 因此, 在一个桶中, 就有可能生成一个Entry链。而且新添加的元素作为链表的head。

添加元素的过程:

向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的扩容

当HashMap中的元素越来越多的时候, hash冲突的几率也就越来越高, 因为数组的长度是固定的。 所以为了提高查询的效率, 就要对HashMap的数组进行扩容, 而在HashMap数组扩容之后, 最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置, 并放进去, 这就是resize。

扩容时机

那么HashMap什么时候进行扩容呢?
当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)loadFactor 时 , 就 会 进 行 数 组 扩 容 , loadFactor 的 默 认 值(DEFAULT_LOAD_FACTOR)为0.75, 这是一个折中的取值。 也就是说, 默认情况下, 数组大小(DEFAULT_INITIAL_CAPACITY)为16, 那么当HashMap中元素个数超过160.75=12(这个值就是代码中的threshold值, 也叫做临界值) 的时候, 就把数组的大小扩展为 2*16=32, 即扩大一倍, 然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作, 所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

基本属性
    //默认的初始化长度  2的几次幂
    static final int default_initial_capacity = 16;
    //最大长度 1左移30位
    static final int maximum_capacity = 1 << 30;
    //默认加载因子
    static final float default_load_factor = 0.75f;
    //内部类entry 存放每一个元素
    transient entry<k,v>[] table;
    //目前hashMap的大小
    transient int size;
    /**
     * the next size value at which to resize (capacity * load factor).   扩展使用的
     * @serial 
     */
    int threshold;
    final float loadfactor;
    //这个多线程的时候使用,修改次数,类似一个版本号的功能
    transient int modcount;
entry内部类
static class entry<k,v> implements map.entry<k,v> {
        final k key;
        v value;
        entry<k,v> next;
        int hash;

        /**
         * creates new entry.
         */
        entry(int h, k k, v v, entry<k,v> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }

        public final k getkey() {
            return key;
        }

        public final v getvalue() {
            return value;
        }

        public final v setvalue(v newvalue) {
            v oldvalue = value;
            value = newvalue;
            return oldvalue;
        }

        public final boolean equals(object o) {
            if (!(o instanceof map.entry))
                return false;
            map.entry e = (map.entry)o;
            object k1 = getkey();
            object k2 = e.getkey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                object v1 = getvalue();
                object v2 = e.getvalue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }

        public final int hashcode() {
            return (key==null   ? 0 : key.hashcode()) ^
                   (value==null ? 0 : value.hashcode());
        }

        public final string tostring() {
            return getkey() + "=" + getvalue();
        }

        /**
         * this method is invoked whenever the value in an entry is
         * overwritten by an invocation of put(k,v) for a key k that's already
         * in the hashmap.
         */
        void recordaccess(hashmap<k,v> m) {
        }

        /**
         * this method is invoked whenever the entry is
         * removed from the table.
         */
        void recordremoval(hashmap<k,v> m) {
        }
    }

可以发现7中的这个entry是单向的,他里面有next属性,指向下一个元素。这个图很形象
在这里插入图片描述
简单来说,HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的。
如果定位到的数组位置不含链表(当前entry的next指向null):那么对于查找,添加等操作很快,仅需一次寻址即可;
如果定位到的数组包含链表:对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。

所以,性能考虑,HashMap中的链表出现越少,性能才会越好。

构造方法
/**
     * 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
     */
    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);

        // find a power of 2 >= initialcapacity
        int capacity = 1;
        while (capacity < initialcapacity)
            capacity <<= 1;

        this.loadfactor = loadfactor;
        threshold = (int)math.min(capacity * loadfactor, maximum_capacity + 1);
        table = new entry[capacity];
        usealthashing = sun.misc.vm.isbooted() &&
                (capacity >= holder.alternative_hashing_threshold);
        init();
    }

发现里面构造的时候指名了数组的长度
table = new entry[capacity];

put操作
/**
     * associates the specified value with the specified key in this map.
     * if the map previously contained a mapping for the key, the old
     * value is replaced.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     *         <tt>null</tt> if there was no mapping for <tt>key</tt>.
     *         (a <tt>null</tt> return can also indicate that the map
     *         previously associated <tt>null</tt> with <tt>key</tt>.)
     */
    public v put(k key, v value) {
        //如果key为null,存储位置为table[0]或table[0]的冲突链上
        if (key == null)
            return putfornullkey(value);
        //对key的hashcode进一步计算,确保散列均匀
        int hash = hash(key);
        //获取在table中的实际位置
        int i = indexfor(hash, table.length);
        //如果该对应数据已存在,执行覆盖操作。用新value替换旧value,并返回旧value  包含遍历链表的操作
        for (entry<k,v> e = table[i]; e != null; e = e.next) {
            object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                v oldvalue = e.value;
                e.value = value;
                e.recordaccess(this);
                return oldvalue;
            }
        }

        //保证并发访问时,若HashMap内部结构发生变化,快速响应失败
        modcount++;
        //新增这个entry,和之前的内部类entry的构造相呼应
        addentry(hash, key, value, i);
        return null;
    }
addentry
/**
     * adds a new entry with the specified key, value and hash code to
     * the specified bucket.  it is the responsibility of this
     * method to resize the table if appropriate.
     *
     * subclass overrides this to alter the behavior of put method.
     */
    void addentry(int hash, k key, v value, int bucketindex) {
        if ((size >= threshold) && (null != table[bucketindex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketindex = indexfor(hash, table.length);
        }

        createentry(hash, key, value, bucketindex);
    }

这里面判断了大小是否进行扩容

扩容resize

看到了调用扩容的时候是原来的2倍

/**
     * rehashes the contents of this map into a new array with a
     * larger capacity.  this method is called automatically when the
     * number of keys in this map reaches its threshold.
     *
     * if current capacity is maximum_capacity, this method does not
     * resize the map, but sets threshold to integer.max_value.
     * this has the effect of preventing future calls.
     *
     * @param newcapacity the new capacity, must be a power of two;
     *        must be greater than current capacity unless current
     *        capacity is maximum_capacity (in which case value
     *        is irrelevant).
     */
    void resize(int newcapacity) {
        entry[] oldtable = table;
        int oldcapacity = oldtable.length;
        if (oldcapacity == maximum_capacity) {
            threshold = integer.max_value;
            return;
        }

        entry[] newtable = new entry[newcapacity];
        boolean oldalthashing = usealthashing;
        usealthashing |= sun.misc.vm.isbooted() &&
                (newcapacity >= holder.alternative_hashing_threshold);
        boolean rehash = oldalthashing ^ usealthashing;
        transfer(newtable, rehash);
        table = newtable;
        threshold = (int)math.min(newcapacity * loadfactor, maximum_capacity + 1);
    }

当发生哈希冲突并且size大于阈值的时候,需要进行数组扩容,扩容时,需要新建一个长度为之前数组2倍的新的数组,然后将当前的Entry数组中的元素全部传输过去,扩容后的新数组长度为之前的2倍,所以扩容相对来说是个耗资源的操作。

JDK8

JDK 8版本发布以后: HashMap是数组+链表+红黑树实现。
在这里插入图片描述

存储结构

1、 HashMap的内部存储结构其实是数组+链表+树的结合。 当实例化一个HashMap时, 会初始化initialCapacity和loadFactor, 在put第一对映射关系时, 系统会创建一个长度为initialCapacity的Node数组, 这个长度在哈希表中被称为容量(Capacity), 在这个数组中可以存放元素的位置我们称之为“桶” (bucket), 每个bucket都有自己的索引, 系统可以根据索引快速的查找bucket中的元素。
2、每个bucket中存储一个元素, 即一个Node对象, 但每一个Node对象可以带一个引用变量next, 用于指向下一个元素, 因此, 在一个桶中, 就有可能生成一个Node链。 也可能是一个一个TreeNode对象, 每一个TreeNode对象可以有两个叶子结点left和right, 因此, 在一个桶中, 就有可能生成一个TreeNode树。 而新添加的元素作为链表的last, 或树的叶子结点。

添加元素过程
扩容
扩容时机

那么HashMap什么时候进行扩容和树形化呢?
当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)loadFactor 时 , 就 会 进 行 数 组 扩 容 , loadFactor 的 默 认 值(DEFAULT_LOAD_FACTOR)为0.75, 这是一个折中的取值。 也就是说, 默认情况下, 数组大小(DEFAULT_INITIAL_CAPACITY)为16, 那么当HashMap中元素个数超过160.75=12(这个值就是代码中的threshold值, 也叫做临界值)的时候, 就把数组的大小扩展为 2*16=32, 即扩大一倍, 然后重新计算每个元素在数组中的位置, 而这是一个非常消耗性能的操作, 所以如果我们已经预知HashMap中元素的个数, 那么预设元素的个数能够有效的提高HashMap的性能。
当HashMap中的其中一个链的对象个数如果达到了8个,此时如果capacity没有达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链会变成树,结点类型由Node变成TreeNode类型。当然,如果当映射关系被移除后,下次resize方法时判断树的结点个数低于6个,也会把树再转为链表。

key

关于映射关系的key是否可以修改? answer:不要修改
映射关系存储到HashMap中会存储key的hash值,这样就不用在每次查找时重新计算
每一个Entry或Node(TreeNode)的hash值了,因此如果已经put到Map中的映射关
系,再修改key的属性,而这个属性又参与hashcode值的计算,那么会导致匹配不上。

node内部类

node就是entry换了个马甲,里面都是一样的。

/**
     * 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;
        }
    }
构造方法
/**
     * 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
     */
    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);
    }

可以发现这个的构造里面没有初始化长度是16

put操作
 Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

先截取一点看,put方法首先调用了putVal,里面执行了扩容,即首先进行的是初始化操作。

扩容resize
if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }

扩容里面就先放这点代码,zero initial threshold signifies using defaults
这里就是初始化了,啥都没有的时候按之前的默认值进行初始化成16的长度。

重新看put
/**
     * Implements Map.put and related methods
     *
     * @param hash hash for key
     * @param key the key
     * @param value the value to put
     * @param onlyIfAbsent if true, don't change existing value
     * @param evict if false, the table is in creation mode.
     * @return previous value, or null if none
     */
    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;
        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) {
                    if ((e = p.next) == null) {
                        //搞一下之前元素的指针指向当前的元素
                        p.next = newNode(hash, key, value, null);
                        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))))
                        break;
                    p = e;
                }
            }
            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;
    }
总结:
JDK1.8相较于之前的变化:

1.HashMap map = new HashMap();//默认情况下,先不创建长度为16的数组
2.当首次调用map.put()时,再创建长度为16的数组
3.数组为Node类型,在jdk7中称为Entry类型
4.形成链表结构时,新添加的key-value对在链表的尾部(七上八下)
5.当数组指定索引位置的链表长度>8时,且map中的数组的长度> 64时,此索引位置上的所有key-value对使用红黑树进行存储。
在这里插入图片描述

负载因子

1、负载因子的大小决定了HashMap的数据密度。
2、负载因子越大密度越大,发生碰撞的几率越高,数组中的链表越容易长,造成查询或插入时的比较次数增多,性能会下降。
3、 负载因子越小,就越容易触发扩容,数据密度也越小,意味着发生碰撞的几率越小,数组中的链表也就越短,查询和插入时比较的次数也越小,性能会更高。但是会浪费一定的内容空间。而且经常扩容也会影响性能,建议初始化预设大一点的空间。
4、 按照其他语言的参考及研究经验,会考虑将负载因子设置为0.7~0.75,此时平均检索长度接近于常数。

LinkedHashMap

LinkedHashMap 是 HashMap 的子类
在HashMap存储结构的基础上,使用了一对双向链表来记录添加元素的顺序
与LinkedHashSet类似, LinkedHashMap 可以维护 Map 的迭代顺序:迭代顺序与 Key-Value 对的插入顺序一致

    /**
     * HashMap.Node subclass for normal LinkedHashMap entries.
     */
    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);
        }
    }

LinkedHashMap里面的entry继承了HashMap的内部类node,为啥不叫node,又改名叫entry了,里面定义了before和after,这实现了双向链表的功能,自己的entry又调用了父类的node的构造。

put方法

LinkedHashMap没有重写put方法,所以还是调用HashMap得到put方法
1.7put里面有addEntry方法,linkedhashmap把这个重写了。
1.8里面是这几个方法

    // Callbacks to allow LinkedHashMap post-actions
    void afterNodeAccess(Node<K,V> p) { }
    void afterNodeInsertion(boolean evict) { }
    void afterNodeRemoval(Node<K,V> p) { }
扩容

他的扩容还是借鉴的父类hashmap的扩容方法
在这里插入图片描述

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

static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;
        V value;
        Entry<K,V> left;
        Entry<K,V> right;
        Entry<K,V> parent;
        boolean color = BLACK;

Entry里面是left和right,BLACK是false,TreeMap使用的存储结构是平衡二叉树,也称为红黑树。它首先是一棵二叉树,具有二叉树所有的特性,即树中的任何节点的值大于它的左子节点,且小于它的右子节点,如果是一棵左右完全均衡的二叉树,元素的查找效率将获得极大提高。最坏的情况就是一边倒,只有左子树或只有右子树,这样势必会导致二叉树的检索效率大大降低。为了维持二叉树的平衡,程序员们提出了各种实现的算法,其中平衡二叉树就是其中的一种算法。
在这里插入图片描述

Hashtable

Hashtable是个古老的 Map 实现类, JDK1.0就提供了。不同于HashMap,Hashtable是线程安全的。
Hashtable实现原理和HashMap相同,功能相同。底层都使用哈希表结构,查询速度快,很多情况下可以互用。
与HashMap不同, Hashtable 不允许使用 null 作为 key 和 value
与HashMap一样, Hashtable 也不能保证其中 Key-Value 对的顺序
Hashtable判断两个key相等、两个value相等的标准, 与HashMap一致。

Properties:

Properties 类是 Hashtable 的子类,该对象用于处理属性文件
由于属性文件里的 key、 value 都是字符串类型,所以 Properties 里的 key和 value 都是字符串类型
存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值