现在的位置: 首页 > 综合 > 正文

HashMap、Hashtable、LinkedHashMap和TreeMap 比较

2014年02月22日 ⁄ 综合 ⁄ 共 9403字 ⁄ 字号 评论关闭

一、相同点与不同点

共同点: 

HashMap,LinkedHashMap,TreeMap都属于Map;Map 主要用于存储键(key)值(value)对,根据键得到值,因此键不允许键重复,但允许值重复。
不同点:

1.HashMap里面存入的键值对在取出的时候是随机的,也是我们最常用的一个Map.它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。在Map 中插入、删除和定位元素,HashMap 是最好的选择。

2.TreeMap取出来的是排序后的键值对。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。

3. LinkedHashMap 是HashMap的一个子类,如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现.

二、HashMap源码分析

HashMap主要是用数组来存储数据的,其对key进行哈希运算,哈希运算会有重复的哈希值,对于哈希值的冲突,HashMap采用链表来解决的。

1、几个关键的属性

transient Entry[] table; //存储数据的数组
static final int DEFAULT_INITIAL_CAPACITY = 16;//默认容量
static final int MAXIMUM_CAPACITY = 1 << 30;//最大容量
static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认加载因子,加载因子是一个比例,当HashMap的数据大小>=容量*加载因子时,HashMap会将容量扩容
int threshold;//当实际数据大小超过threshold时,HashMap会将容量扩容,threshold=容量*加载因子
final float loadFactor;//加载因子

2、HashMap初始化

 /**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;//默认加载因子,加载因子是一个比例,当HashMap的数据大小>=容量*加载因子时,HashMap会将容量扩容
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY];//数组来存储数据
        init();
    }

3、放入HashMap

/**
     * 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) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        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;
            }
        }

        modCount++;
        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) {
	Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        if (size++ >= threshold)
            resize(2 * table.length);
    }

看resize

 /**
     * 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];
        transfer(newTable);
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }

看transfer

 /**
     * Transfers all entries from current table to newTable.
     */
    void transfer(Entry[] newTable) {
        Entry[] src = table;
        int newCapacity = newTable.length;
        for (int j = 0; j < src.length; j++) {
            Entry<K,V> e = src[j];
            if (e != null) {
                src[j] = null;
                do {
                    Entry<K,V> next = e.next;
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
    }

4、图示

三、Hashtable分析

Hashtable与HashMap的实现机制是一致的,只是其实线程安全的,几乎每个public方法都是synchronized的。
如向表中放入数据:
/**
     * Maps the specified <code>key</code> to the specified
     * <code>value</code> in this hashtable. Neither the key nor the
     * value can be <code>null</code>. <p>
     *
     * The value can be retrieved by calling the <code>get</code> method
     * with a key that is equal to the original key.
     *
     * @param      key     the hashtable key
     * @param      value   the value
     * @return     the previous value of the specified key in this hashtable,
     *             or <code>null</code> if it did not have one
     * @exception  NullPointerException  if the key or value is
     *               <code>null</code>
     * @see     Object#equals(Object)
     * @see     #get(Object)
     */
    public synchronized V put(K key, V value) {
	// Make sure the value is not null
	if (value == null) {
	    throw new NullPointerException();
	}

	// Makes sure the key is not already in the hashtable.
	Entry tab[] = table;
	int hash = key.hashCode();
	int index = (hash & 0x7FFFFFFF) % tab.length;
	for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
	    if ((e.hash == hash) && e.key.equals(key)) {
		V old = e.value;
		e.value = value;
		return old;
	    }
	}

	modCount++;
	if (count >= threshold) {
	    // Rehash the table if the threshold is exceeded
	    rehash();

            tab = table;
            index = (hash & 0x7FFFFFFF) % tab.length;
	}

	// Creates the new entry.
	Entry<K,V> e = tab[index];
	tab[index] = new Entry<K,V>(hash, key, value, e);
	count++;
	return null;
    }

四、LinkedHashMap分析

HashMap与LinkedHashMap最大的不同在于,后者维护者一个运行于所有条目的双向链表。有了这个双向链表,就可以在迭代的时候按照插入的顺序迭代出元素。
继承关系
public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>

1、初始化

/**
     * Called by superclass constructors and pseudoconstructors (clone,
     * readObject) before any entries are inserted into the map.  Initializes
     * the chain.
     */
    void init() {
        header = new Entry<K,V>(-1, null, null, null);
        header.before = header.after = header;
    }

2、新增before、after

/**
     * LinkedHashMap entry.
     */
    private static class Entry<K,V> extends HashMap.Entry<K,V> {
        // These fields comprise the doubly linked list used for iteration.
        Entry<K,V> before, after;

	Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
            super(hash, key, value, next);
        }

3、覆写了HashMap的方法

/**                                                                         
 * This override alters behavior of superclass put method. It causes newly 
 * allocated entry to get inserted at the end of the linked list and       
 * removes the eldest entry if appropriate.                                 
 */                                                                         
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);                                       
    }                                                                       
}                                                                            
/**                                                                 
 * This override differs from addEntry in that it doesn't resize the
 * table or remove the eldest entry.                                 
 */                                                                  
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++;                                                          
}                                                                    
/**                                                                   
 * Inserts this entry before the specified existing entry in the list.
 */                                                                    

private void addBefore(Entry<K, V> existingEntry)                      
{                                                                      
    after = existingEntry;                                             
    before = existingEntry.before;                                     
    before.after = this;                                               
    after.before = this;                                                
}                           

五、代码示例

/**
 * Map用于存储键值对,不允许键重复,值可以重复。
 * (1)HashMap是一个最常用的Map,它根据键的hashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。
 * HashMap最多只允许一条记录的键为null,允许多条记录的值为null。
 * HashMap不支持线程的同步,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。
 * 如果需要同步,可以用Collections.synchronizedMap(HashMap map)方法使HashMap具有同步的能力。
 * (2)Hashtable与HashMap类似,不同的是:它不允许记录的键或者值为空;
 * 它支持线程的同步,即任一时刻只有一个线程能写Hashtable,然而,这也导致了Hashtable在写入时会比较慢。
 * (3)LinkedHashMap保存了记录的插入顺序,在用Iteraor遍历LinkedHashMap时,先得到的记录肯定是先插入的。
 * 在遍历的时候会比HashMap慢。
 * (4)TreeMap能够把它保存的记录根据键排序,默认是按升序排序,也可以指定排序的比较器。当用Iteraor遍历TreeMap时,
 * 得到的记录是排过序的。
 */
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;

/**
 * 演示各个Map的实现类
 */
public class TestMap {
    
    /**
     * 初始化一个Map
     * @param map
     */
    public static void init(Map map){
        if (map != null){
            String key = null;
            for (int i=5; i>0; i--){
                key = new Integer(i).toString() + ".0";
                map.put(key, key.toString());
                //Map中的键是不重复的,如果插入两个键值一样的记录,
                //那么后插入的记录会覆盖先插入的记录
                map.put(key, key.toString() + "0");            }
        }
    }
    /**
     * 输出一个Map
     * @param map
     */
    public static void output(Map map){
        if (map != null){
            Object key = null;
            Object value = null;
            //使用迭代器遍历Map的键,根据键取值
            Iterator it = map.keySet().iterator();
            while (it.hasNext()){
                key = it.next();
                value = map.get(key);
                System.out.println("key: " + key + "; value: " + value );
            }
            //或者使用迭代器遍历Map的记录Map.Entry
            Map.Entry entry = null;
            it = map.entrySet().iterator();
            while (it.hasNext()){
                //一个Map.Entry代表一条记录
                entry = (Map.Entry)it.next();
                //通过entry可以获得记录的键和值
                //System.out.println("key: " + entry.getKey() + "; value: " + entry.getValue());
            }
        }
    }
    /**
     * 判断map是否包含某个键
     * @param map
     * @param key
     * @return
     */
    public static boolean containsKey(Map map, Object key){
        if (map != null){
            return map.containsKey(key);
        }
        return false;
    }
    /**
     * 判断map是否包含某个值
     * @param map
     * @param value
     * @return
     */
    public static boolean containsValue(Map map, Object value){
        if (map != null){
            return map.containsValue(value);
        }
        return false;
    }
    /**
     * 演示HashMap
     */
    public static void testHashMap(){
        Map myMap = new HashMap();
        init(myMap);
        //HashMap的键可以为null
        myMap.put(null,"ddd");
        //HashMap的值可以为null
        myMap.put("aaa", null);
        output(myMap);
    }
    /**
     * 演示Hashtable
     */
    public static void testHashtable(){
        Map myMap = new Hashtable();
        init(myMap);
        //Hashtable的键不能为null
        //myMap.put(null,"ddd");
        //Hashtable的值不能为null
        //myMap.put("aaa", null);
        output(myMap);
    }
    /**
     * 演示LinkedHashMap
     */
    public static void testLinkedHashMap(){
        Map myMap = new LinkedHashMap();
        init(myMap);
        //LinkedHashMap的键可以为null
        myMap.put(null,"ddd");
        //LinkedHashMap的值可以为null
        myMap.put("aaa", null);
        output(myMap);
    }
    /**
     * 演示TreeMap
     */
    public static void testTreeMap(){
        Map myMap = new TreeMap();
        init(myMap);
        //TreeMap的键不能为null
        //myMap.put(null,"ddd");
        //TreeMap的值不能为null
        //myMap.put("aaa", null);
        output(myMap);
    }

    public static void main(String[] args) {
        System.out.println("采用HashMap");
        TestMap.testHashMap();
        System.out.println("采用Hashtable");
        TestMap.testHashtable();
        System.out.println("采用LinkedHashMap");
        TestMap.testLinkedHashMap();
        System.out.println("采用TreeMap");
        TestMap.testTreeMap();
        
        Map myMap = new HashMap();
        TestMap.init(myMap);
        System.out.println("新初始化一个Map: myMap");
        TestMap.output(myMap);
        //清空Map
        myMap.clear();
        System.out.println("将myMap clear后,myMap空了么?  " + myMap.isEmpty());
        TestMap.output(myMap);
        myMap.put("aaa", "aaaa");
        myMap.put("bbb", "bbbb");
        //判断Map是否包含某键或者某值
        System.out.println("myMap包含键aaa?  "+ TestMap.containsKey(myMap, "aaa"));
        System.out.println("myMap包含值aaaa?  "+ TestMap.containsValue(myMap, "aaaa"));
        //根据键删除Map中的记录
        myMap.remove("aaa");
        System.out.println("删除键aaa后,myMap包含键aaa?  "+ TestMap.containsKey(myMap, "aaa"));
        //获取Map的记录数
        System.out.println("myMap包含的记录数:  " + myMap.size());
    }
}

抱歉!评论已关闭.