java之Map源码浅析

文章来自http://blog.csdn.net/windsunmoon/article/details/46390451


看起来晕过段时间看

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存储键值对,这是一个接口,其定义如下,简单明了,接口方法主要是对键和值进行操作。

[java]  view plain  copy
  1. interface Entry<K,V> {  
  2.      
  3.     K getKey();  
  4.    
  5.     V getValue();  
  6.    
  7.    V setValue(V value);  
  8.    
  9.     boolean equals(Object o);  
  10.    
  11.     int hashCode();  
  12.     }  

AbstractMap

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

[java]  view plain  copy
  1. Map<String,String> a = /** 
  2.         * 
  3.         *抽象map实现示意,根据文档说,和list接口及其类似。 
  4.         * 
  5.         *map分为可变和不可变两种,不可变只需实现 entrySet方法即可,且返回的 set的迭代器不能支持修改操作。 
  6.         * 
  7.         *可变map,需要实现put方法,然后 entrySet的迭代器也需要支持修改操作 
  8.         * 
  9.         * 
  10.         *AbstractMap 里面实现了map的梗概,但是其效率难说,比如其get方法中时采用方法entrySet实现的。 
  11.         * 
  12.         *通常子类用更有效率的方法覆盖之。如hashMap中覆盖了keySet 、values 、get方法等 
  13.         */  
  14.        new AbstractMap<String,String>(){  
  15.    
  16.            /* 
  17.             * 返回map中的元素集合,返回的集合通常继承AbstractSet 即可。 
  18.             */  
  19.            @Override  
  20.            public Set<Map.Entry<String, String>> entrySet() {  
  21.               return new AbstractSet<Map.Entry<String,String>>() {  
  22.    
  23.                   @Override  
  24.                   public Iterator<java.util.Map.Entry<String, String>> iterator() {  
  25.                      return null;  
  26.                   }  
  27.    
  28.                   @Override  
  29.                   public int size() {  
  30.                      return 0;  
  31.                   }  
  32.               };  
  33.            }  
  34.             
  35.            /* 
  36.             * 默认实现抛出异常,可变map需要实现此方法 
  37.             */  
  38.            @Override  
  39.            public String put(String key, String value) {  
  40.                
  41.               return null;  
  42.            }  
  43.             
  44.        };  

HashMap

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

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

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

[java]  view plain  copy
  1. /** 
  2.          * 返回map中所有的键值对集合,用于遍历 
  3.          */  
  4.         public Set<Map.Entry<K,V>> entrySet() {  
  5.        return entrySet0();  
  6.         }  
  7.    
  8.         /** 
  9.          * 延迟初始化,只有使用的时候才构建。 
  10.          * 
  11.          * values()和 keySet()方法也使用了类似的机制 
  12.          */  
  13.         private Set<Map.Entry<K,V>> entrySet0() {  
  14.             Set<Map.Entry<K,V>> es = entrySet;  
  15.             return es != null ? es : (entrySet = new EntrySet());  
  16.         }  
  17.          
  18.          
  19.    
  20.         /** 
  21.          * 真正的 enterySet,是一个内部类,其关键实现是迭代器实现。 
  22.          * 
  23.          * values()和 keySet()方法也对应了相应的内部类。 
  24.          * 对应的自己的迭代器实现。关键在于这个迭代器 
  25.          * 
  26.          */  
  27.         private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {  
  28.             public Iterator<Map.Entry<K,V>> iterator() {  
  29.                 return newEntryIterator();  
  30.             }  
  31.             public boolean contains(Object o) {  
  32.                 if (!(o instanceof Map.Entry))  
  33.                     return false;  
  34.                 Map.Entry<K,V> e = (Map.Entry<K,V>) o;  
  35.                 Entry<K,V> candidate = getEntry(e.getKey());  
  36.                 return candidate != null && candidate.equals(e);  
  37.             }  
  38.             public boolean remove(Object o) {  
  39.                 return removeMapping(o) != null;  
  40.             }  
  41.             public int size() {  
  42.                 return size;  
  43.             }  
  44.             public void clear() {  
  45.                 HashMap.this.clear();  
  46.             }  
  47.         }  
  48.          
  49.          
  50.         /** 
  51.          * entrySet迭代器,继承HashIterator,实现next方法。 
  52.          * values()和 keySet()方法,也是继承HashIterator,只是实现next 的方法不同, 
  53.          * 
  54.          * 可以对比下。 
  55.          * 
  56.          * 关键在于HashIterator 
  57.          * 
  58.          * 
  59.          */  
  60.         private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {  
  61.             public Map.Entry<K,V> next() {  
  62.                 return nextEntry();  
  63.             }  
  64.         }  
  65.          
  66.         /** 
  67.          * 
  68.          *keySet()对应的迭代器 
  69.          */  
  70.         private final class KeyIterator extends HashIterator<K> {  
  71.             public K next() {  
  72.                 return nextEntry().getKey();  
  73.             }  
  74.         }  
  75.          
  76.          
  77.         /** 
  78.          * 
  79.          * hashmap entrySet() keySet() values()的通用迭代器 
  80.          */  
  81.         private abstract class HashIterator<E> implements Iterator<E> {  
  82.             Entry<K,V> next;   // next entry to return  
  83.             int expectedModCount; // For fast-fail  
  84.             int index;     // current slot  
  85.             Entry<K,V> current;   // current entry  
  86.    
  87.             HashIterator() {  
  88.                 expectedModCount = modCount;  
  89.                 if (size > 0) { // advance to first entry  
  90.                     Entry[] t = table;  
  91.                     //构造时候,在数组中查找第一个不为null的数组元素,即Entry链表,关于hashmap的实现请看  
  92.                     //本人前面的博文  
  93.                     while (index < t.length && (next = t[index++]) == null)  
  94.                         ;  
  95.                 }  
  96.             }  
  97.    
  98.             public final boolean hasNext() {  
  99.                 return next != null;  
  100.             }  
  101.    
  102.             /** 
  103.              * 关键实现,很容易看懂,查找next的时候,和构造迭代器的时候一样 
  104.              */  
  105.             final Entry<K,V> nextEntry() {  
  106.                 if (modCount != expectedModCount)  
  107.                     throw new ConcurrentModificationException();  
  108.                 Entry<K,V> e = next;  
  109.                 if (e == null)  
  110.                     throw new NoSuchElementException();  
  111.    
  112.                 if ((next = e.next) == null) {  
  113.                     Entry[] t = table;  
  114.                     while (index < t.length && (next = t[index++]) == null)  
  115.                         ;  
  116.                 }  
  117.            current = e;  
  118.                 return e;  
  119.             }  
  120.    
  121.             public void remove() {  
  122.                 if (current == null)  
  123.                     throw new IllegalStateException();  
  124.                 if (modCount != expectedModCount)  
  125.                     throw new ConcurrentModificationException();  
  126.                 Object k = current.key;  
  127.                 current = null;  
  128.                 HashMap.this.removeEntryForKey(k);  
  129.                 expectedModCount = modCount;  
  130.             }  
  131.    
  132.         }  

HashTable

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

LinkedHashMap

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

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

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

[java]  view plain  copy
  1. /** 
  2.          * 直接调用父类的getEntry方法。关键在于 
  3.          *  e.recordAccess(this) 这句代码 
  4.          */  
  5.         public V get(Object key) {  
  6.             Entry<K,V> e = (Entry<K,V>)getEntry(key);  
  7.             if (e == null)  
  8.                 return null;  
  9.             e.recordAccess(this);  
  10.             return e.value;  
  11.         }  
  12.          
  13.         /** 
  14.          * Entry.recordAccess 方法 
  15.          * 
  16.          * 如果是访问顺序(accessOrder=true),那么就把它放到头结点的下一个位置 
  17.          * 否则什么也不做, 
  18.          * 这样就可以根据初始 accessOrder 属性,来决定遍历的顺序。 
  19.          */  
  20.         void recordAccess(HashMap<K,V> m) {  
  21.             LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;  
  22.             if (lm.accessOrder) {  
  23.                 lm.modCount++;  
  24.                 remove();  
  25.                 addBefore(lm.header);  
  26.             }  
  27.         }  


Put方法:

[java]  view plain  copy
  1. /** 
  2.          * put方法调用此方法,覆盖了父类中的实现, 
  3.          */  
  4.         void addEntry(int hash,K key, V value, int bucketIndex) {  
  5.             createEntry(hash, key, value, bucketIndex);  
  6.    
  7.             // Remove eldest entry if instructed, else grow capacity if appropriate  
  8.             Entry<K,V> eldest = header.after;  
  9.             //回调。如果有必要移除在老的元素,最新的元素在链表尾部。  
  10.             if (removeEldestEntry(eldest)) {  
  11.                 removeEntryForKey(eldest.key);  
  12.             } else {  
  13.                 if (size >= threshold)  
  14.                     resize(2 * table.length);  
  15.             }  
  16.         }  
  17.    
  18.         /** 
  19.          * 
  20.          */  
  21.         void createEntry(int hash,K key, V value, int bucketIndex) {  
  22.             HashMap.Entry<K,V> old = table[bucketIndex];  
  23.        Entry<K,V> e = new Entry<K,V>(hash, key, value, old);  
  24.             table[bucketIndex] = e;  
  25.             //本质是插入双向链表的末尾  
  26.             e.addBefore(header);  
  27.             size++;  
  28.         }  
  29.          
  30.          
  31.         /** 
  32.          * 插入到 existingEntry的前面,因为是双向链表。当existingEntry是 header时, 
  33.          * 相当于插入到链表最后。 
  34.          * 
  35.          */  
  36.         private void addBefore(Entry<K,V> existingEntry) {  
  37.             after  = existingEntry;  
  38.             before = existingEntry.before;  
  39.             before.after = this;  
  40.             after.before = this;  
  41.         }  


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

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

 

 

package com.te;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

class Test {
   
	public void fun() {
		
		Map<String,Object> map=new ConcurrentHashMap();//数据存放无序的
	    map.put("arg1", 1);
	    map.put("arg2", 2);
	    map.put("arg3", 3);
	    map.put("arg4", 4);
	    
	    Map<String,Object> map1=new ConcurrentHashMap();//数据存放无序的
	    map1.put("arg1", 1);
	    map1.put("arg2", 2);
	    map1.put("arg3", 3);
	    map1.put("arg4", 4);
	    
	    Map<String,Object> map2=new LinkedHashMap();//数据存放有序
	    map2.put("arg1", 1);
	    map2.put("arg2", 2);
	    map2.put("arg3", 3);
	    map2.put("arg4", 4);
	    
	    for(String key:map.keySet()) {
	    	System.out.println(key);
	    	System.out.println(map.get(key));
	    	System.out.println(map.containsKey(key));
	    	System.out.println(map.containsValue(map.containsKey(key)));
	    }
	    
	    
	    for(String key:map1.keySet()) {
	    	System.out.println(key);
	    	System.out.println(map1.get(key));
	    	System.out.println(map1.containsKey(key));
	    	System.out.println(map1.containsValue(map1.containsKey(key)));
	    }
	    
	    
	    for(String key:map2.keySet()) {
	    	System.out.println(key);
	    	System.out.println(map2.get(key));
	    	System.out.println(map2.containsKey(key));
	    	System.out.println(map2.containsValue(map2.containsKey(key)));
	    }
	    
	    
		
	}
    
}
public class Testnull {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
    	//应该是引用不同于指针,引用中既包含指向对象的指针、又包含指向类的指针,
    	//test中指向对象的指针确实为空,但指向Test的指针可不为空啊
        Test test=new Test();
        //test.hello();
         test.fun();
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值