java之Map源码浅析

Map是键值对,也是常用的数据结构。Map接口定义了map的基本行为,包括最核心的get和put操作,此接口的定义的方法见下图:

JDK中有不同的的map实现,分别适用于不同的应用场景,如线程安全的hashTable和非线程安全的hashMap.

如下图是JDK中map接口的子类UML类图,其中有个特例Dictionary已经不建议使用:

Map接口中的方法我们需要关注的就是get、put 和迭代器相关的方法如entrySet()、keySet()、values()方法。

Entry

在开始分析map之前,首先了解map中元素的存储,我们知道map可以认为是键值对的集合,java中map使用Entry存储键值对,这是一个接口,其定义如下,简单明了,接口方法主要是对键和值进行操作。

interface Entry<K,V> {

    K getKey();

    V getValue();

   V setValue(V value);

    boolean equals(Object o);

    int hashCode();
    }

AbstractMap

Map接口的抽象实现,见以下示例实现代码:

Map<String,String> a = /**
        *
        *抽象map实现示意,根据文档说,和list接口及其类似。
        *
        *map分为可变和不可变两种,不可变只需实现 entrySet方法即可,且返回的 set的迭代器不能支持修改操作。
        *
        *可变map,需要实现put方法,然后 entrySet的迭代器也需要支持修改操作
        *
        *
        *AbstractMap 里面实现了map的梗概,但是其效率难说,比如其get方法中时采用方法entrySet实现的。
        *
        *通常子类用更有效率的方法覆盖之。如hashMap中覆盖了keySet 、values 、get方法等
        */
       new AbstractMap<String,String>(){

           /*
            * 返回map中的元素集合,返回的集合通常继承AbstractSet 即可。
            */
           @Override
           public Set<Map.Entry<String, String>> entrySet() {
              return new AbstractSet<Map.Entry<String,String>>() {

                  @Override
                  public Iterator<java.util.Map.Entry<String, String>> iterator() {
                     return null;
                  }

                  @Override
                  public int size() {
                     return 0;
                  }
              };
           }

           /*
            * 默认实现抛出异常,可变map需要实现此方法
            */
           @Override
           public String put(String key, String value) {

              return null;
           }

       };

HashMap

hashMap继承abstractMap,是相当常用的数据结构,采用hash散列的思想,可以在O(1)的时间复杂度内插入和获取数据。其基本实现可以分析上个小节中的抽象方法,文章

浅析HashMap的实现和性能分析 已经对hashMap的实现、put和get操作进行了较详细的说明。这里不再赘述,关键看他的迭代器实现,这里只分析下entrySet()方法,而keySet()和values()方法实现与之一脉相承。

关于迭代器,见下面摘出的部分源码和相关注释:

/**
         * 返回map中所有的键值对集合,用于遍历
         */
        public Set<Map.Entry<K,V>> entrySet() {
       return entrySet0();
        }

        /**
         * 延迟初始化,只有使用的时候才构建。
         *
         * values()和 keySet()方法也使用了类似的机制
         */
        private Set<Map.Entry<K,V>> entrySet0() {
            Set<Map.Entry<K,V>> es = entrySet;
            return es != null ? es : (entrySet = new EntrySet());
        }

        /**
         * 真正的 enterySet,是一个内部类,其关键实现是迭代器实现。
         *
         * values()和 keySet()方法也对应了相应的内部类。
         * 对应的自己的迭代器实现。关键在于这个迭代器
         *
         */
        private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
            public Iterator<Map.Entry<K,V>> iterator() {
                return newEntryIterator();
            }
            public boolean contains(Object o) {
                if (!(o instanceof Map.Entry))
                    return false;
                Map.Entry<K,V> e = (Map.Entry<K,V>) o;
                Entry<K,V> candidate = getEntry(e.getKey());
                return candidate != null && candidate.equals(e);
            }
            public boolean remove(Object o) {
                return removeMapping(o) != null;
            }
            public int size() {
                return size;
            }
            public void clear() {
                HashMap.this.clear();
            }
        }

        /**
         * entrySet迭代器,继承HashIterator,实现next方法。
         * values()和 keySet()方法,也是继承HashIterator,只是实现next 的方法不同,
         *
         * 可以对比下。
         *
         * 关键在于HashIterator
         *
         *
         */
        private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
            public Map.Entry<K,V> next() {
                return nextEntry();
            }
        }

        /**
         *
         *keySet()对应的迭代器
         */
        private final class KeyIterator extends HashIterator<K> {
            public K next() {
                return nextEntry().getKey();
            }
        }

        /**
         *
         * hashmap entrySet() keySet() values()的通用迭代器
         */
        private abstract class HashIterator<E> implements Iterator<E> {
            Entry<K,V> next;   // next entry to return
            int expectedModCount; // For fast-fail
            int index;     // current slot
            Entry<K,V> current;   // current entry

            HashIterator() {
                expectedModCount = modCount;
                if (size > 0) { // advance to first entry
                    Entry[] t = table;
                    //构造时候,在数组中查找第一个不为null的数组元素,即Entry链表,关于hashmap的实现请看
                    //本人前面的博文
                    while (index < t.length && (next = t[index++]) == null)
                        ;
                }
            }

            public final boolean hasNext() {
                return next != null;
            }

            /**
             * 关键实现,很容易看懂,查找next的时候,和构造迭代器的时候一样
             */
            final Entry<K,V> nextEntry() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
                Entry<K,V> e = next;
                if (e == null)
                    throw new NoSuchElementException();

                if ((next = e.next) == null) {
                    Entry[] t = table;
                    while (index < t.length && (next = t[index++]) == null)
                        ;
                }
           current = e;
                return e;
            }

            public void remove() {
                if (current == null)
                    throw new IllegalStateException();
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
                Object k = current.key;
                current = null;
                HashMap.this.removeEntryForKey(k);
                expectedModCount = modCount;
            }

        }

HashTable

实现和hashMap基本一致,只是在方法上加上了同步操作。多线程环境可以使用它。不过现在有ConcurrentHashMap了,在高并发的时候,可以用它替换hashtable.

LinkedHashMap

hashMap可能在某些场景下不符合要求,因为放入到其中的元素是无序的。而LinkedHashMap则在一定程度上解决这个问题。

其在实现上继承了HashMap,在存储上扩展haspMap.enteySet,加入了before、after字段,把hashMap的元素用双向链表连接了起来。这个双向链表决定了它的遍历顺序。其顺序通常是插入map中的顺序,但是它有一个字段accessOrder当为true时,遍历顺序将是LRU的效果。

研究它的有序性,我们可以从put方法、get方法和遍历的方法入手,首先看get方法:

/**
         * 直接调用父类的getEntry方法。关键在于
         *  e.recordAccess(this) 这句代码
         */
        public V get(Object key) {
            Entry<K,V> e = (Entry<K,V>)getEntry(key);
            if (e == null)
                return null;
            e.recordAccess(this);
            return e.value;
        }

        /**
         * Entry.recordAccess 方法
         *
         * 如果是访问顺序(accessOrder=true),那么就把它放到头结点的下一个位置
         * 否则什么也不做,
         * 这样就可以根据初始 accessOrder 属性,来决定遍历的顺序。
         */
        void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            if (lm.accessOrder) {
                lm.modCount++;
                remove();
                addBefore(lm.header);
            }
        }

Put方法:

/**
         * put方法调用此方法,覆盖了父类中的实现,
         */
        void addEntry(int hash,K key, V value, int bucketIndex) {
            createEntry(hash, key, value, bucketIndex);

            // Remove eldest entry if instructed, else grow capacity if appropriate
            Entry<K,V> eldest = header.after;
            //回调。如果有必要移除在老的元素,最新的元素在链表尾部。
            if (removeEldestEntry(eldest)) {
                removeEntryForKey(eldest.key);
            } else {
                if (size >= threshold)
                    resize(2 * table.length);
            }
        }

        /**
         *
         */
        void createEntry(int hash,K key, V value, int bucketIndex) {
            HashMap.Entry<K,V> old = table[bucketIndex];
       Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
            table[bucketIndex] = e;
            //本质是插入双向链表的末尾
            e.addBefore(header);
            size++;
        }

        /**
         * 插入到 existingEntry的前面,因为是双向链表。当existingEntry是 header时,
         * 相当于插入到链表最后。
         *
         */
        private void addBefore(Entry<K,V> existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;
        }

遍历迭代直接使用双向链表进行迭代接口,这里不赘述,可以看源码很容易理解。注意的是实现上市覆盖了父类中相关的生成迭代器的方法。

TreeMap和CurrentHashMap都可以单独开一篇文章来分析了。这里简单说下。TreeMap是基于b树map,根据key排序。CurrentHashMap是并发包中的一个强大的类,适合多线程高并发时数据读写。

时间: 2024-10-11 01:53:44

java之Map源码浅析的相关文章

java.util.HashMap源码浅析之解决hash冲突

HashMap是java无论是企业管理系统还是web或者其他应用层的程序开发,都是应用比较多的一种数据结构,正好最近面试有问到与HashMap解决hash冲突的方式(本人菜比没答上来),现浅析源码以解惑 且记录,将来在项目上尽量避免此类问题的出现,大家都知道HashMap为key-value存储,在HashMap中,HashMap本身拥有一个Entry数组,Entry则存有key-value,且对于Hashmap来讲一个key只能对应一个value     首先是put方法          

java之list源码浅析

三大数据结构链表.树和图,顺序表作为其中的一种,可以说是平时编程中最长使用到的.List接口是顺序表在java中的实现,它有很多子接口和实现类,平时的编程中使用起来非常方便.但是更进一步,我们有必要对其实现和原理进行理解,并和数据结构中所学比较,并应用于平时的编程中,编写出高效率的代码. 首先看下list接口的层次关系,下图由本人根据jdk的类结构简单画的: 从上图可以看出,list接口有Collection接口衍生而出,那么首先了解下Collection接口. Collection colle

java.util.Map源码分析

/** * An object that maps keys to values. A map cannot contain duplicate keys; * each key can map to at most one value. * * <p>This interface takes the place of the <tt>Dictionary</tt> class, which * was a totally abstract class rather t

java之Set源码浅析

Set的接口和实现类是最简单的,说它简单原因是因为它的实现都是基于实际的map实现的.如 hashSet 基于hashMap,TreeSet 基于TreeMap,CopyOnWriteArraySet 基于 CopyOnWriteArrayList . 故对其实现简要分析.首先看下面的类图: Set Set接口的意义是,含有相等的元素,相等由集合内元素的equals方法决定.Set接口继承Collection接口,但是其实其所含的方法和Collection接口一样. AbstractSet 从类

java的Iterator源码浅析

Iterator经常用来遍历List,如下所示: List<String> list=new ArrayList<String>(); list.add("apple"); list.add("banana"); list.add("watermelon"); for (Iterator<String> iterator=list.iterator();iterator.hasNext();) { System

我对java String的理解 及 源码浅析

每天起床告诉自己,自己的目标是 ”技术 + 英语 还有生活“! -泥沙砖瓦浆木匠 一.char说起到String 这也是自己第二次回过头来啃java基础书,小生自认为愚昧无知.如果大神有好的教育,可以评论私信.以下都是我的看法:为什么说char呢,我这里先卖个关子.在java中,char是用unicode编码的,占16位(2字节).从ansi编码(1字节)到unicode编码(2字节).Java中使用Unicode的原因是,Java的Applet(网页)运行,Unicode里面包含最多最广比如:

java并发:jdk1.8中ConcurrentHashMap源码浅析

ConcurrentHashMap是线程安全的.可以在多线程中对ConcurrentHashMap进行操作. 在jdk1.7中,使用的是锁分段技术Segment.数据结构是数组+链表. 对比jdk1.7,在jdk1.8中,ConcurrentHashMap主要使用了CAS(compareAndSwap).volatile.synchronized锁. 跟jdk1.8中的HashMap一样,数据结构是数组+链表+红黑树.当链表长度过长时,会转变为红黑树. jdk1.8的HashMap源码浅析,见

Volley框架源码浅析(一)

尊重原创http://blog.csdn.net/yuanzeyao/article/details/25837897 从今天开始,我打算为大家呈现关于Volley框架的源码分析的文章,Volley框架是Google在2013年发布的,主要用于实现频繁而且粒度比较细小的Http请求,在此之前Android中进行Http请求通常是使用HttpUrlConnection和HttpClient进行,但是使用起来非常麻烦,而且效率比较地下,我想谷歌正式基于此种原因发布了Volley框架,其实出了Voll

Volley框架源码浅析(二)

尊重原创 http://write.blog.csdn.net/postedit/25921795 在前面的一片文章Volley框架浅析(一)中我们知道在RequestQueue这个类中,有两个队列:本地队列和网络队列 /** The cache triage queue. */ private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<