HashMap,TreeMap以及LinkedHashMap的区别

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HashMap,TreeMap以及LinkedHashMap的区别相关的知识,希望对你有一定的参考价值。


HashMap:HashMap数据是无序的,根据键的hashCode进行数据的存取,对数据的访问速度非常快,在map中插入删除

和定位元素,hashMap无疑是最好的选择,

TreeMap:里面的数据是有序的,底层是一个红黑树,如果想按照自定义顺序或者自然顺序存储数据,TreeMap是一个最好的选择

LinkedHashMap:他是hashMap的一个子类,底层维护了一个双向链表,他可以实现输入的顺序和输出的顺序相同

下面来讲讲LinkedHashMap是如何实现有序的:


LinkedHashMap具有可预知的迭代顺序,根据链表中元素的顺序可以分为:按插入顺序的链表,和按访问顺序(调用get方法)的链表。  

默认是按插入顺序排序,如果指定按访问顺序排序,那么调用get方法后,会将这次访问的元素移至链表尾部,不断访问可以形成按访问顺序排序的链表。  可以重写removeEldestEntry方法返回true值指定插入元素时移除最老的元素。


如何实现迭代有序?


  1. 重新定义了数组中保存的元素Entry(继承于HashMap.Entry),该Entry除了保存当前对象的引用外,还保存了其上一个元素before和下一个元素after的引用,从而在哈希表的基础上又构成了双向链接列表。仍然保留next属性,所以既可像HashMap一样快速查找,用next获取该链表下一个Entry,也可以通过双向链接,通过after完成所有数据的有序迭代。

  2. accessOrder为true时,按访问顺序排序,false时,按插入顺序排序。默认false,即下文中recordAccess方法没有改变什么。 copy

  1. private final boolean accessOrder
  2. 存储put

    LinkedHashMap并未重写父类HashMap的put方法,而是重写了父类HashMap的put方法调用的子方法void recordAccess(HashMap m),void addEntry(int hash, K key, V value, int bucketIndex) 和void createEntry(int hash, K key, V value, int bucketIndex),提供了自己特有的双向链接列表的实现。

    • put时,key已存在,替换value(同HashMap),并调用recordAccess方法,方法作用为根据accessOrder的值保持链表顺序不变或者将将访问的当前节点移到链表尾部(头结点的前一个节点)。

    • key不存在,添加新的Entry,仍然是Table[i]= newEntry,旧链表首个为newEntry.next(同HashMap),将newEntry加到双向链表末尾(即header前,这样就保留了插入顺序)。copy 

    • HashMap.put:  
        
      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;    
          }

  1.  重写方法:  

      

  2. void recordAccess(HashMap<K,V> m) {    
                LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;    
                if (lm.accessOrder) {    
                    lm.modCount++;    
                    remove();    
                    addBefore(lm.header);    
                }    
            }    
    void addEntry(int hash, K key, V value, int bucketIndex) {    
        // 调用create方法,将新元素以双向链表的的形式加入到映射中。    
        createEntry(hash, key, value, bucketIndex);    
        
        // 删除最近最少使用元素的策略定义    
        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;    
        // 调用元素的addBrefore方法,将元素加入到哈希、双向链接列表。    
        e.addBefore(header);    
        size++;    
    }    
    private void addBefore(Entry<K,V> existingEntry) {    
        after  = existingEntry;    
        before = existingEntry.before;    
        before.after = this;    
        after.before = this;    
    }


4.读取

同样调用recordAccess方法,是否将访问的当前节点移到链表尾部,与HashMap的区别是:当LinkedHashMap按访问顺序排序的时候,会将访问的当前节点移到链表尾部(头结点的前一个节点)。

public V get(Object key) {    
    // 调用父类HashMap的getEntry()方法,取得要查找的元素。    
    Entry<K,V> e = (Entry<K,V>)getEntry(key);    
    if (e == null)    
        return null;    
    // 记录访问顺序。    
    e.recordAccess(this);    
    return e.value;    
}


view plai

5.迭代view plain copy

//返回链表下个节点的引用  
Entry<K,V> nextEntry() {  
    //快速失败机制  
    if (modCount != expectedModCount)  
        throw new ConcurrentModificationException();  
    //链表为空情况  
    if (nextEntry == header)  
        throw new NoSuchElementException();  
      
    //给lastReturned赋值,最近一个从迭代器返回的节点对象  
    Entry<K,V> e = lastReturned = nextEntry;  
    nextEntry = e.after;  
    return e;  
}

 

下面来讲讲TreeMap是如何实现有序的:

TreeMap底层是一个红黑树,那么他的中序遍历就是有序的,因此treeMap是可以实现有序的,那么他又是如何实现自定义排序的呢?

1、让元素自身具备比较功能

		实现Comparable接口,重写comparaTo()方法。
		@Override
		public int compareTo(Object o) {
			
			Person p = (Person) o;
			int temp = this.age - p.age;
			return temp == 0?this.name.compareTo(p.name):temp;

	//		Person p = (Person) o;
	//		if(this.age > p.age)
	//			return 1;
	//		if(this.age < p.age)
	//			return -1;
	//		else {
	//			return this.name.compareTo(p.name);
	//		}
		}

2、如果不要按照对象中具备的自然顺序进行排序。如果对象中不具备自然顺序。也就是对象不是自己定义的,怎么办?

可以使用TreeSet集合的第二种排序方式:

让集合自身具备比较功能,使用比较器,定义一个类实现Comparator接口,覆盖compare方法,将该类对象作为参数

传递给TreeSet集合的构造函数


以上是关于HashMap,TreeMap以及LinkedHashMap的区别的主要内容,如果未能解决你的问题,请参考以下文章

java LinkedHashMap,TreeMap,HashMap

您将使用哪种数据结构:TreeMap 或 HashMap? (Java)[重复]

您将使用哪种数据结构:TreeMap 或 HashMap? (Java)[重复]

TreeMap源码分析

深入理解HashMap和TreeMap的区别

简单比较HashMap和TreeMap