`
zy19982004
  • 浏览: 661853 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
博客专栏
F6f66edc-1c1a-3859-b76b-a22e740b7aa7
Hadoop学习
浏览量:251950
社区版块
存档分类
最新评论

容器学习三:LinkedHashMap源码分析

 
阅读更多

一.LinkedHashMap的存储结构

LinkedHashMap存储结构

  1. LinkedHashMap是继承HashMap,也就继承了HashMap的结构,也就是图中的结构2,在下文中我用"Entry数组+next链表"来描述。而LinkedHashMap有其自己的变量header,也就是图中的结构1,下文中我用"header链表"来描述。
  2. 结构1中的Entry和结构2中的Entry本是同一个,结构1中应该就只有一个header,它指向的是结构2中的e1 e2,但这样会使结构图难画。为了说明问题的方便,我把结构2里的e1 e2在结构1中多画一个。

 

二.LinkedHashMap成员变量

	// LinkedHashMap维护了一个链表,header是链表头。此链表不同于HashMap里面的那个next链表
	private transient Entry<K, V> header;

	// LRU:Least Recently Used最近最少使用算法
	// accessOrder决定是否使用此算法,accessOrder=true使用
	private final boolean accessOrder;

 

三.LinkedHashMap里的Entry对象

        // 继承了HashMap.Entry,其他几个方法边用边分析
	private static class Entry<K, V> extends HashMap.Entry<K, V> {
		// 增加了两个属性,每个Entry有before Entry和after Entry,就构成了一个链表
		Entry<K, V> before, after;

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

		private void addBefore(Entry<K, V> existingEntry) {
			.....
		}

		void recordAccess(HashMap<K, V> m) {
			.....
		}
		
		void recordRemoval(HashMap<K, V> m) {
			.....
		}
		
		private void remove() {
			.....
		}
	}
 

四.构造函数

	//默认accessOrder为false
	//调用HashMap构造函数
	public LinkedHashMap() {
		super();
		accessOrder = false;
	}

	//如果想实现LRU算法,参考这个构造函数
	public LinkedHashMap(int initialCapacity, float loadFactor,
			boolean accessOrder) {
		super(initialCapacity, loadFactor);
		this.accessOrder = accessOrder;
	}
	
	//模板方法模式,HashMap构造函数里面的会调用init()方法
	//初始化的时候map里没有任何Entry,让header.before = header.after = header
	void init() {
		header = new Entry<K, V>(-1, null, null, null);
		header.before = header.after = header;
	}
 

五.存数据

	//LinkedHashMap没有put(K key, V value)方法,只重写了被put调用的addEntry方法
	//1是HashMap里原有的逻辑,23是LinkedHashMap特有的
	void addEntry(int hash, K key, V value, int bucketIndex) {
		createEntry(hash, key, value, bucketIndex);

		Entry<K, V> eldest = header.after;
		//3.如果有必要,移除LRU里面最老的Entry,否则判断是否该resize
		if (removeEldestEntry(eldest)) {
			removeEntryForKey(eldest.key);
		} else {
			if (size >= threshold)
				resize(2 * table.length);
		}
	}
	
	void createEntry(int hash, K key, V value, int bucketIndex) {
		//1.同HashMap一样:在Entry数组+next链表结构里面加入Entry
		HashMap.Entry<K, V> old = table[bucketIndex];
		Entry<K, V> e = new Entry<K, V>(hash, key, value, old);
		table[bucketIndex] = e;
		//2.把新Entry也加到header链表结构里面去
		e.addBefore(header);
		size++;
	}
	
	//默认是false,我们可以重写此方法
	protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
		return false;
	}

	private static class Entry<K, V> extends HashMap.Entry<K, V> {
		//链表插入元素四个步骤,对着图看
		private void addBefore(Entry<K, V> existingEntry) {
			after = existingEntry;                //1
			before = existingEntry.before;     //2
			before.after = this;                   //3
			after.before = this;                   //4
		}
        }
      
         //如果走到resize,会调用这里重写的transfer
	//HashMap里面的transfer是n * m次运算,LinkedHashtable重写后是n + m次运算
	void transfer(HashMap.Entry[] newTable) {
		int newCapacity = newTable.length;
		//直接遍历header链表,HashMap里面是遍历Entry数组
		for (Entry<K, V> e = header.after; e != header; e = e.after) {
			int index = indexFor(e.hash, newCapacity);
			e.next = newTable[index];
			newTable[index] = e;
		}
	 }

     下面三个图是初始化LinkedHashMap------->添加Entry e1------>添加Entry e2时,LinkedHashMap结构的变化。

LinkedHashMap存储结构

LinkedHashMap存储结构

LinkedHashMap存储结构

 

六.取数据

	//重写了get(Object key)方法
	public V get(Object key) {
		//1.调用HashMap的getEntry方法得到e
		Entry<K, V> e = (Entry<K, V>) getEntry(key);
		if (e == null)
			return null;
		//2.LinkedHashMap牛B的地方
		e.recordAccess(this);
		return e.value;
	}
 
        // 继承了HashMap.Entry
	private static class Entry<K, V> extends HashMap.Entry<K, V> {
		//1.此方法提供了LRU的实现
		//2.通过12两步,把最近使用的当前Entry移到header的before位置,而LinkedHashIterator遍历的方式是从header.after开始遍历,先得到最近使用的Entry
		//3.最近使用是什么意思:accessOrder为true时,get(Object key)方法会导致Entry最近使用;put(K key, V value)/putForNullKey(value)只有是覆盖操作时会导致Entry最近使用。它们都会触发recordAccess方法从而导致Entry最近使用
		//4.总结LinkedHashMap迭代方式:accessOrder=false时,迭代出的数据按插入顺序;accessOrder=true时,迭代出的数据按LRU顺序+插入顺序
		//  HashMap迭代方式:横向数组 * 竖向next链表
		void recordAccess(HashMap<K, V> m) {
			LinkedHashMap<K, V> lm = (LinkedHashMap<K, V>) m;
			//如果使用LRU算法
			if (lm.accessOrder) {
				lm.modCount++;
				//1.从header链表里面移除当前Entry
				remove();
				//2.把当前Entry移到header的before位置
				addBefore(lm.header);
			}
		}
		
		//让当前Entry从header链表消失
		private void remove() {
			before.after = after;
			after.before = before;
		}
        }
 

七.删数据

        // 继承了HashMap.Entry
	private static class Entry<K, V> extends HashMap.Entry<K, V> {
		//LinkedHashMap没有重写remove(Object key)方法,重写了被remove调用的recordRemoval方法
		//这个方法的设计也和精髓,也是模板方法模式
		//HahsMap remove(Object key)把数据从横向数组 * 竖向next链表里面移除之后(就已经完成工作了,所以HashMap里面recordRemoval是空的实现调用了此方法
		//但在LinkedHashMap里面,还需要移除header链表里面Entry的after和before关系
		void recordRemoval(HashMap<K, V> m) {
			remove();
		}
		
		//让当前Entry从header链表消失
		private void remove() {
			before.after = after;
			after.before = before;
		}
	}
 

八.LinkedHashMap EntrySet遍历

        private abstract class LinkedHashIterator<T> implements Iterator<T> {
		//从header.after开始遍历
		Entry<K, V> nextEntry = header.after;
		
		Entry<K, V> nextEntry() {
			if (modCount != expectedModCount)
				throw new ConcurrentModificationException();
			if (nextEntry == header)
				throw new NoSuchElementException();

			Entry<K, V> e = lastReturned = nextEntry;
			nextEntry = e.after;
			return e;
		}
	}
  1. 上图中,遍历的结果是先e1然后e2。
  2. accessOrder为true时,get(e1.key)或者put(e1.key, value)一下,则结构1变成e2------e1------header,遍历的结果就是先e2然后e1。

 

九.总结

  1. LinkedHashMap继承HashMap,结构2里数据结构的变化交给HashMap就行了。
  2. 结构1里数据结构的变化就由LinkedHashMap里重写的方法去实现。
  3. 简言之:LinkedHashMap比HashMap多维护了一个链表。

 

4
4
分享到:
评论
1 楼 jkguowen 2013-10-31  
非常谢谢作者,看完您的文章后,一切关于LinkedHashMap实现原理的疑惑,都豁然开朗了!然后又看到容器类的其他文章,非常高兴,可以一次过弄明他们的实现原理了

相关推荐

    免费开源-【Java学习+面试指南】部分内容大部分是Java程序员所需要掌握的核心知识

    Unsafe 详细解Java SPI 机制详解Java语法糖详解集合知识点/面试题总结:Java集合常见知识点&面试题总结(上)(必看)Java集合常见知识点&面试题总结(下)(必看)Java容器使用注意事项总结源码分析:ArrayList核心...

    【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识

    源码分析: ArrayList 核心源码+扩容机制分析 LinkedList 核心源码分析 HashMap 核心源码+底层数据结构分析 ConcurrentHashMap 核心源码+底层数据结构分析 LinkedHashMap 核心源码分析 CopyOnWriteArrayList 核心...

    javaSourceLearn:jdk源码构建

    List、Set、Map三大接口以及其实现类(ArrayList、LinkedList、HashSet、TreeSet、HashMap、LinkedHashMap等)的源码分析能帮助我们更高效地利用数据结构,优化程序性能。 标签“系统开源”表明这个项目鼓励开放和...

    Java 容器.pdf_电子版pdf版

    源码分析 ArrayList 是一个基于动态数组实现的 List 实现类。它实现了 RandomAccess 接口,因此支持随机访问。ArrayList 的默认大小为 10,添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,如果不够...

    JAVA容器对象整理

    这些知识点仅仅是Java容器对象的一部分,实际的博客可能会包含更多细节,如源码分析、性能对比和最佳实践。通过阅读博客中的`持有对象.xmind`文件,可以进一步了解博主对这些概念的详细整理和分类。如果你对Java容器...

    java_collection_source_code_analyze:Java集合部分源码分析-Source code collection

    2. **源码分析** - **容器实现**:这些类的实现通常包括一个内部存储结构(如数组或链表)和一些基本操作,如添加、删除、查找等。例如,ArrayList的`add()`方法会检查容量并根据需要进行扩容,LinkedList的`remove...

    Java就业面试题大全

    以上内容仅是对Java面试题可能涵盖的一些关键知识点的概述,实际的面试题可能会更加深入和具体,包括但不限于性能调优、源码分析、算法实现等。对于求职者来说,全面理解和熟练应用这些知识点是提升面试成功率的关键...

    java集合详细解释

    3. **源码分析**: - 对于集合框架中的实现类,深入理解其源码是非常有价值的。例如,`ArrayList`的扩容机制,`HashMap`的哈希冲突解决方法,以及`LinkedList`的链表操作效率等,都是面试和实际开发中常见的问题。 ...

    JAVA 集合操作

    11. **源码分析**: 分析集合类的源码有助于理解其实现细节,例如`ArrayList`的扩容机制、`HashMap`的哈希冲突解决策略等。 12. **测试**: 对集合操作的测试是确保代码正确性的关键步骤。可以使用JUnit等单元...

    java集合框架全面进阶

    5. **源码分析**:深入理解Java集合框架的源码,有助于优化代码性能。例如,了解HashMap的扩容机制、LinkedList的节点操作、ArrayList的动态数组管理等,能够帮助开发者在设计和实现自己的数据结构时避免常见陷阱。 ...

    高速缓存实现源码

    通过深入研究这个项目的源码,我们可以学习如何在Java中高效地实现高速缓存,以及如何利用并发优化性能。这对于开发高性能、高并发的应用程序是非常有价值的。同时,理解并掌握缓存的原理和实践,对于提升软件系统的...

    java集合(自学整理)

    下面以 `ArrayList` 为例进行源码分析: #### ArrayList 概览 `ArrayList` 实现了 `RandomAccess` 接口,这意味着它支持快速随机访问。这是因为 `ArrayList` 基于数组实现,所以可以快速地获取任意位置的元素。 `...

    advanced-java-collections:高级Java课程-集合框架的源代码-Source code collection

    5. **源码分析**: 深入源码可以发现,例如ArrayList的扩容策略、HashMap的冲突解决以及LinkedList的插入删除效率等,这些都是优化程序性能的关键。 6. **性能优化**: 学习如何选择合适的集合类型、如何使用适当...

    java.util源码-JavaUtility-SourceCode:JavaUtility-SourceCode

    这个源码分析将深入探讨`java.util`包中的关键组件,了解它们的工作原理,这对于任何Java开发者来说都是至关重要的。`java.util`包包括集合框架、日期时间API、事件模型、散列映射、队列、栈、优先队列、随机数生成...

    java面试大全

    2. **集合框架**:Java集合框架是面试中的热门话题,包括List(ArrayList、LinkedList)、Set(HashSet、TreeSet)和Map(HashMap、LinkedHashMap、TreeMap)等接口及其实现类的使用、性能分析和源码解读。...

    Java面试高频题.pdf

    #### 十三、源码分析 1. **Spring启动容器** - 通过`AnnotationConfigApplicationContext.refresh()`方法实现。 - 核心步骤包括环境配置、BeanFactory创建、BeanFactory预处理、Web生命周期管理等。

    java源码剖析-Analysis-of-java-source-code:Java源代码

    由于作者尚未提供更详细的内容,我们可以预期这个项目可能涵盖了以上几个主题的源码分析,包括但不限于Java集合框架的实现细节、Git的工作流解析以及MySQL的关键特性与优化策略。对于开发者来说,这是一份宝贵的参考...

    鹅厂面试题、大厂面试题、JVM面试题

    TCP 的三次握手是为了确保数据的可靠传输,而 UDP 则没有这种机制。 接下来,让我们讨论 Dubbo 和 Dubbox 之间的区别。Dubbox 和 Dubbo 本质上没有区别,都是基于 Java 的远程调用框架,但是 Dubbox 提供了更多的...

    aula-[removed]用Java编写实验库

    标题 "aula-[removed]用Java编写实验库" 暗示了这是...压缩包子文件 "aula-javascript-main" 可能包含的是项目的主代码库,可能涵盖上述部分或全部知识点,通过阅读和分析源码,开发者可以深入理解Java编程的各个方面。

    java基础案例与开发详解案例源码全

    2.3.2 Java程序开发三步曲21 2.3.3 开发Java第一个程序21 2.3.4 Java代码中的注释23 2.3.5 常见错误解析24 2.4 Java类库组织结构和文档27 2.5 Java虚拟机简介28 2.6 Java技术两种核心运行机制29 2.7 上机练习30 第3...

Global site tag (gtag.js) - Google Analytics