HashMap的无序性与LinkedHashMap的有序性

2021年9月19日 5点热度 0条评论 来源: 向前-

概述

HashMap

HashMap存储元素是无序的,在通过Iterator遍历元素时也是无序的。

 

LinkedHashMap

LinkedHashMap存储元素是无序的,在通过Iterator遍历元素时是有序的;put数据的顺序和输出顺序是一致的。

LinkedHashMap遍历元素的有序性,是采用了双向链表来保存节点。

 

HashMap源码分析

HashMap数据存储结构

// 主结构 -- 数组
// java.util.HashMap#table
transient Node<K,V>[] table;

// 元素结构之Node
// java.util.HashMap.Node
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
}

// 元素升级结构之TreeNode(红黑树)
// java.util.HashMap.TreeNode
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;
}

 

Iterator

HashMap<String, String> hashMap = new HashMap<>();
for (int i = 0; i < 6; i++) {
	hashMap.put("k-" + i, "v-" + i);
}

// 获取Iterator实例
Iterator<Map.Entry<String, String>> iterator = hashMap.entrySet().iterator();
Map.Entry<String, String> entry = null;
while (iterator.hasNext()) {
	entry = iterator.next();
	System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}

java.util.HashMap#entrySet 

public Set<Map.Entry<K,V>> entrySet() {
	Set<Map.Entry<K,V>> es;
	return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}

java.util.HashMap.EntrySet 

final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
	public final int size()                 { return size; }
	public final void clear()               { HashMap.this.clear(); }

	// 获取Iterator实例
	public final Iterator<Map.Entry<K,V>> iterator() {
		return new EntryIterator();
	}

	// 省略多行代码... 
}

 

java.util.HashMap.EntryIterator 

注意,EntryIterator继承于HashIterator,Iterator接口主要实现方法都在abstract HashIterator中

final class EntryIterator extends HashIterator
	implements Iterator<Map.Entry<K,V>> {
	public final Map.Entry<K,V> next() { return nextNode(); }
}

java.util.HashMap.HashIterator 

abstract class HashIterator {
	Node<K,V> next;        // next entry to return
	Node<K,V> current;     // current entry
	int expectedModCount;  // for fast-fail
	int index;             // current slot

	HashIterator() {
		expectedModCount = modCount;
		Node<K,V>[] t = table;
		current = next = null;
		index = 0;
		
		// 初始化next节点值 -- 注意此处
		if (t != null && size > 0) { // advance to first entry
			do {} while (index < t.length && (next = t[index++]) == null);
		}
	}

	// 判断是否有下一个节点
	public final boolean hasNext() {
		return next != null;
	}

	// 获取下一个节点
	final Node<K,V> nextNode() {
		Node<K,V>[] t;
		Node<K,V> e = next; // 将下一个节点(next节点)赋予e,最后将会返回e节点值
		if (modCount != expectedModCount)
			throw new ConcurrentModificationException();
		if (e == null)
			throw new NoSuchElementException();
		
		// 1. 如果当前节点current.next不为null时,则将 current.next(这是链表上的下一个节点,不是table数组上的元素)存储到成员变量next中
		if ((next = (current = e).next) == null && (t = table) != null) {
			// 2. current.next == null && table != null
			// index从0开始,index是HashIterator的成员变量。
			// 当index < table.length && table[index++] == null时,说明该index位置上没有元素,则将会继续循环查找下一个index位置;
			// 直至 index >= table.length || table[index++] != null 时,将会退出循环,将结果存储到成员变量next中
			do {} while (index < t.length && (next = t[index++]) == null);
		}
		
		return e;
	}

	// 省略多行代码... 
}

由nextNode()方法可以看出,HashMap遍历元素时是无序的。

 

LinkedHashMap源码分析

LinkedHashMap数据存储结构(注意和HashMap数据结构对比)

// 主结构 -- 数组
// java.util.HashMap#table
transient Node<K,V>[] table;

// 元素结构之Entry
// java.util.LinkedHashMap.Entry
static class Entry<K,V> extends HashMap.Node<K,V> {
    Entry<K,V> before, after;
}

// 元素升级结构之TreeNode(红黑树)
// java.util.HashMap.TreeNode
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;
}

 

Iterator

LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
for (int i = 0; i < 6; i++) {
	linkedHashMap.put("k-" + i, "v-" + i);
}

// 获取Iterator实例
Iterator<Map.Entry<String, String>> iterator = linkedHashMap.entrySet().iterator();
Map.Entry<String, String> entry = null;
while (iterator.hasNext()) {
	entry = iterator.next();
	System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}

java.util.LinkedHashMap#entrySet

public Set<Map.Entry<K,V>> entrySet() {
	Set<Map.Entry<K,V>> es;
	return (es = entrySet) == null ? (entrySet = new LinkedEntrySet()) : es;
}

java.util.LinkedHashMap.LinkedEntrySet

final class LinkedEntrySet extends AbstractSet<Map.Entry<K,V>> {
	public final int size()                 { return size; }
	public final void clear()               { LinkedHashMap.this.clear(); }
	public final Iterator<Map.Entry<K,V>> iterator() {
		return new LinkedEntryIterator();
	}

	// 省略多行代码... 
}

java.util.LinkedHashMap.LinkedEntryIterator

 

注意,LinkedEntryIterator继承于LinkedHashIterator,Iterator接口的主要实现都在abstract LinkedHashIterator中

final class LinkedEntryIterator extends LinkedHashIterator
	implements Iterator<Map.Entry<K,V>> {
	public final Map.Entry<K,V> next() { return nextNode(); }
}

java.util.LinkedHashMap.LinkedHashIterator

abstract class LinkedHashIterator {
	LinkedHashMap.Entry<K,V> next;
	LinkedHashMap.Entry<K,V> current;
	int expectedModCount;

	LinkedHashIterator() {
		// 初始化next,将头结点(head)赋予next
		next = head;
		
		expectedModCount = modCount;
		current = null;
	}

	// 判断是否有下一个节点
	public final boolean hasNext() {
		return next != null;
	}

	// 获取下一个节点
	final LinkedHashMap.Entry<K,V> nextNode() {
	
		// 将next赋予e,将e返回
		LinkedHashMap.Entry<K,V> e = next;
		
		if (modCount != expectedModCount)
			throw new ConcurrentModificationException();
		if (e == null)
			throw new NoSuchElementException();
		current = e;
		
		// next重新赋予新值,存储当前节点(current = e)的后置节点(after)
		next = e.after;
		
		return e;
	}
	
	// 省略多行代码... 
}

LinkedHashMap是采用了双向链表来保存节点,当在获取下一个节点时,会通过after(后置节点)查找,从而达到遍历的有序性。

接下来在看看LinkedHashMap put的一些源码细节,将会更明白为啥after会获取有序的下一个节点。

LinkedHashMap put

java.util.LinkedHashMap#newNode

/**
 * 创建新的节点
 * @param hash key的hash值
 * @param key
 * @param value
 * @param e 新添加节点的下一个节点,默认是null
 * @return
 */
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
	LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e);
	
	// 此方法将会把元素的首尾节点记录下来
	linkNodeLast(p);
	return p;
}

java.util.LinkedHashMap#linkNodeLast

// link at the end of list
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
	LinkedHashMap.Entry<K,V> last = tail;
	
	// 将新添加的节点p设置为LinkedHashMap的成员变量tail(tail是当前链表的尾节点);
	tail = p;
	
	// 尾结点为null时,说明没有任何节点,则将p设置为头结点(head)
	if (last == null)
		head = p;
	
	// 
	else {
		// 将当前链表的尾结点(last)设置为 新添加节点p的前置节点(before)
		p.before = last;
		
		// 在将尾结点(last)的后置节点(after)设置为 新添加节点p
		last.after = p;
	}
}

 

    原文作者:向前-
    原文地址: https://blog.csdn.net/xiangqian_/article/details/103252188
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。