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值指定插入元素时移除最老的元素。
如何实现迭代有序?
重新定义了数组中保存的元素Entry(继承于HashMap.Entry),该Entry除了保存当前对象的引用外,还保存了其上一个元素before和下一个元素after的引用,从而在哈希表的基础上又构成了双向链接列表。仍然保留next属性,所以既可像HashMap一样快速查找,用next获取该链表下一个Entry,也可以通过双向链接,通过after完成所有数据的有序迭代。
accessOrder为true时,按访问顺序排序,false时,按插入顺序排序。默认false,即下文中recordAccess方法没有改变什么。 copy
private final boolean accessOrder
存储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; }
重写方法:
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; }
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)[重复]