JAVA-Map 详解

作者: 不高山 | 来源:发表于2018-02-22 21:43 被阅读43次

    1 Map

    Map 定义了键值存储的数据操作的接口, 主要用于键值的存储,使用键可以得到值,并且不允许重复的键,值可以重复,可以起到数据的快速获取的目的。Java 1.5 后有了 concurrent 包,因此处理 java.utils 里面有 Map 的实现外,concurrent 包中也增加了同步 Map 的实现。

    1.1 java.util 中的 Map 实现

    1.1.1 HashMap

    1. HashMap 继承自AbstractMap, 实现了 Map、Serializable, Cloneable 接口。

    2. HashMap是单向链表数组的存储结构(拉链法),根据 Key 的 HashCode 值运算后作为数组的 index 存储数据,根据 key 可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序时完全随机的。

    3. HashMap 只允许一条记录的键为 Null;

    4. HashMap 不支持多线程的同步,即任意时刻可以有多个线程同时写 HashMap,可能会导致数据的不一致。如果需要同步,可以使用 Collections 的 synchronizedMap 方法使 HashMap 具有同步的能力, 或者使用 ConcurrentHashMap。

    5. 不能使用 get() 方法返回 null值判断是否包含是否包含该键,而应该使用 constainsKey() 方法来判断。

    6. 默认的长度为16, 扩充是按照 2 的指数倍进行扩充,这一点可以看下 HashMap 的 resize() 方法。

    final Node[] resize() {

        Node[] oldTab = table;

        int oldCap = (oldTab == null) ? 0 : oldTab.length;

        int oldThr = threshold;

        int newCap, newThr = 0;

        if (oldCap > 0)

        {

            if (oldCap >= MAXIMUM_CAPACITY) // 长度超过最大限制时不再扩充

            {

                threshold = Integer.MAX_VALUE;

                return oldTab;

            } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)

                // 原 size 扩充一倍小于最大限制且旧的 size 大于等于默认大小时,

                //设置新的大小原来 threshold 一倍, 那么 threshold 在使用无参构造函数时是怎么设置值呢?在本函数中,继续向下看

                newThr = oldThr << 1; // double threshold

            } else if (oldThr > 0)

                // 默认 threshold 为 0, 默认的初始化方法,第一次存放数据时该代码不会执行

                newCap = oldThr;

             else // zero initial threshold signifies using defaults

            {

                //此处为默认的初始化的 Map ,第一次存放数据执行的地方,为设置 size 为默认的大小即:16,

                //下次调整大小的为默认的 factor (0.75) * 16 。

                newCap = DEFAULT_INITIAL_CAPACITY;

                newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);

                }

         if (newThr == 0)

        {

                float ft = (float)newCap * loadFactor;

                newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE);

         }

            threshold = newThr;

            // 。。。。。。。。

            return newTab;

    }

    7. 计算 Key 的 hash() 值

    static final int hash(Objectkey)

     {

        int h;

         return(key ==null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

    }

    8. hashMap 实际存储的是一个单项链表的数组;

    9. 新增元素时,key 为 null 的值直接放到数组的首位,否则使用 hash()计算的值,与当前的存储数组的长度length-1 后有 key 的 hash 做 按位与(&)的操作或许下标,如果该 index 下已经存在项,则创建新的 node,并做存储的链表的开始位置,将 next 指向想一个元素,否则,存储新的节点,下一个元素为 null。

    10. get() 元素时, 使用 key 的 hashcode 运算后获取到 index, 使用 index 获取链表后进行遍历, 找到 key 对应的值;

    11. HashMap 提供了 keySet() 方法集获取 key 的 set 集合, values() 方法获取值的 collection, entrySet() 方法返回了 entry 的 set 集合。

    1.1.2 Hashtable

    1. Hashtable 继承自 java.util.Dictionary 抽象类,实现了 Map,Cloneable, Serializable 接口;

    2. Hashtable 存储结构与 HashMap 一样,使用的单向链表数组,但是,hastable 不允许 key 和 value 为 null;

    3. key 的 hashCode 是使用的 key 本身的 hashkey;

    4. Hashtable 是线程安全,它使用 synchronized 对public 的函数加锁实现到了同步;

    5. Hashtable 默认大小是 11,这个在其默认的构造函数里面可以看到:

    public Hashtable()

    {

        this(11, 0.75f);

    }

    当需要扩充是,Hashtable 是扩充为原来大小的 1 倍 + 1

    protected void rehash()

    {

        int oldCapacity= table.length;

        Entry[] oldMap= table;

        // overflow-conscious code

        int newCapacity= (oldCapacity << 1) + 1;

        if(newCapacity - MAX_ARRAY_SIZE > 0)

            {

                    if(oldCapacity == MAX_ARRAY_SIZE)

                    // Keep running with MAX_ARRAY_SIZE buckets

                    return;       

            newCapacity = MAX_ARRAY_SIZE;   

            } 

          .........

    }

    1.1.3 LinkedHashMap

    LinkedHashMap 继承自 HashMap,实现了 Map 接口。LinedHashMap 存入元素默认是按照插入的顺序保存。遍历LinkedHashMap时,先得到的记录肯定是先插入的。也可以在构造时用带参数,按照应用次数排序。在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比 LinkedHashMap慢,因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。

    LinkedHashMap 存储元素 Entry 在 HashMap.Node 的基础上增加了如下的before 和 after,使之变成了双向的链表结构,源码如下:

    static class Entry extends HashMap.Node 

    {

        Entry before,after;

        Entry(int hash,K key,V value,Node next)

        {

            super(hash, key, value, next);   

        }

    }

    1.1.4 TreeMap

    1. TreeMap 继承自 AbstractMap, 实现了 SortedMap 的接口。实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。

    2. 基于红黑树实现。TreeMap没有调优选项,因为该树总处于平衡状态。

    3. TreeMap 会按照 key 的存储顺序进行排序,默认使用使用字典升序排列, 可以自己实现 Comparator 来自定义排序规则。

    TreeMap 提供构造函数如下:

    // 默认构造函数。使用该构造函数,TreeMap中的元素按照自然排序进行排列。

    TreeMap()

    // 创建的TreeMap包含

    Map TreeMap(Map copyFrom)

    // 指定Tree的比较器

    TreeMap(Comparator comparator)

    // 创建的TreeSet包含copyFrom

    TreeMap(SortedMap copyFrom)

    关于详细的介绍,大家可以去看这里 http://www.cnblogs.com/skywang12345/p/3310928.html , 介绍的非常详细,同时对红黑树也相应的文章进行介绍。

    1.1.5 WeakHashMap

    WeakHashMap 实现了 Map 接口,继承自 AbstractMap, 与 HashMap 的用法基本相同,不同的是它使用弱引用作为存储内部数据的方案,当系统内存不够的时候,垃圾收集器会自动的清除没有在其他任何地方被引用的键值对。在使用 WeakHashMap 进行大量数据的进行缓存时,如果超过 JVM 的内存大小,会先对引用的数据进行 GC,然后再存放新的数据。可以看看在执行 put(K k, V v) 方法时,会执行 getTable 的方法

    public V put(K key, V value)

    {

        Object k = maskNull(key);

        int h = hash(k);

        Entry[] tab = getTable();

        int i = indexFor(h, tab.length);

        ……

     return null;

    }

    而 getTable() 的方法会执行 expungeStaleEntries(), 移除没有引用的键值对。

    private void expungeStaleEntries()

    {

        for (Object x; (x = queue.poll()) != null; )

        {

        synchronized (queue)

        {

                @SuppressWarnings("unchecked")

                Entry e = (Entry) x;

            int i = indexFor(e.hash, table.length);

            Entry prev = table[i];

            Entry p = prev;

            while (p != null)

            {

                    Entry next = p.next;

                    if (p == e)

                    {

                            if (prev == e)

                                    table[i] = next;

                            else prev.next = next;

                            // Must not null out e.next;

                            // stale entries may be in use by a HashIterator

                            e.value = null; // Help GC

                            size--; break;

                    }

                    prev = p;

                    p = next;

                }

            }

        }

    }

    这就是有人觉得使用 WeakHashMap 不靠谱的地方吧。

    1.2 java.util.concurrent

    concurrent 包是java1.5 后添加的,包含许多线程安全、测试良好、高性能的并发构建块。

    1.2.1 ConcurrentHashMap

    ConcurrentHashMap 与 HashMap 非常相似,

    ConcurrentHashMap 在线程安全的基础上提供了更好的写并发能力。

    继承自 AbstractMap,实现了 ConcurrentMap 接口。

    基本的操作与 HashMap 是一致的,一些关键的属性增加了线程同步的控制。

    1.2.2 ConcurrentSkipListMap

    首先简单的介绍下 SkipList 跳表,跳表也是使用的一种扩展的单向链表的数据结构,普通的单向链表是一种线性结构,前一个节点指向后一个节点,而跳表则是前一个节点可能执行后一个和后续的其他节点,以空间换时间,提高了查找的效率。

    ConcurrentSkipListMap 提供了一种线程安全的排序的映射表。内部是SkipList(跳表)结构实现,在理论上能够 O(log(n)) 时间内完成查找、插入、删除等操作。

    2 参考资料

    http://blog.csdn.net/io_field/article/details/53281884

    http://www.importnew.com/22007.html

    http://blog.csdn.net/ns_code/article/details/36034955

    http://blog.csdn.net/sunxianghuang/article/details/52221913

    相关文章

      网友评论

        本文标题:JAVA-Map 详解

        本文链接:https://www.haomeiwen.com/subject/sykxlxtx.html