`

深入JDK源代码之HashMap实现

阅读更多
以下是JDK1.6中文版的对HashMap的具体介绍: 
    基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
    此实现假定哈希函数将元素适当地分布在各桶之间,可为基本操作(get 和 put)提供稳定的性能。迭代 collection 视图所需的时间与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数)成比例。所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。
    HashMap 的实例有两个参数影响其性能:初始容量 和加载因子。容量 是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。
    通常,默认加载因子 (0.75) 在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。
    如果很多映射关系要存储在 HashMap 实例中,则相对于按需执行自动的 rehash 操作以增大表的容量来说,使用足够大的初始容量创建它将使得映射关系能更有效地存储。

下面是HashMap中的方法:
void clear() 
          从此映射中移除所有映射关系。 
 Object clone() 
          返回此 HashMap 实例的浅表副本:并不复制键和值本身。 
 boolean containsKey(Object key) 
          如果此映射包含对于指定键的映射关系,则返回 true。 
 boolean containsValue(Object value) 
          如果此映射将一个或多个键映射到指定值,则返回 true。 
 Set<Map.Entry<K,V>> entrySet() 
          返回此映射所包含的映射关系的 Set 视图。 
 V get(Object key) 
          返回指定键所映射的值;如果对于该键来说,此映射不包含任何映射关系,则返回 null。 
 boolean isEmpty() 
          如果此映射不包含键-值映射关系,则返回 true。 
 Set<K> keySet() 
          返回此映射中所包含的键的 Set 视图。 
 V put(K key, V value) 
          在此映射中关联指定值与指定键。 
 void putAll(Map<? extends K,? extends V> m) 
          将指定映射的所有映射关系复制到此映射中,这些映射关系将替换此映射目前针对指定映射中所有键的所有映射关系。 
 V remove(Object key) 
          从此映射中移除指定键的映射关系(如果存在)。 
 int size() 
          返回此映射中的键-值映射关系数。 
 Collection<V> values() 
          返回此映射所包含的值的 Collection 视图。 

下面深入HashMap源代码,详解它的具体实现:

一、HashMap的数据结构: HashMap用了一个名字为table的Entry类型数组;数组中的每一项又是一个Entry链表。


 // 默认的初始化大小
	static final int DEFAULT_INITIAL_CAPACITY = 16;
	// 最大的容量
	static final int MAXIMUM_CAPACITY = 1 << 30;
	// 负载因子
	static final float DEFAULT_LOAD_FACTOR = 0.75f;
	// 储存key-value键值对的数组,一个键值对对象映射一个Entry对象
	transient Entry[] table;
	// 键值对的数目
	transient int size;
	// 调整HashMap大小门槛,该变量包含了HashMap能容纳的key-value对的极限,它的值等于HashMap的容量乘以负载因子
	int threshold;
	// 加载因子
	final float loadFactor;
	// HashMap结构修改次数,防止在遍历时,有其他的线程在进行修改
	transient volatile int modCount;
 public HashMap(int initialCapacity, float loadFactor) {
		if (initialCapacity < 0)
			throw new IllegalArgumentException("Illegal initial capacity: "
					+ initialCapacity);
		if (initialCapacity > MAXIMUM_CAPACITY)
			initialCapacity = MAXIMUM_CAPACITY;
		if (loadFactor <= 0 || Float.isNaN(loadFactor))
			throw new IllegalArgumentException("Illegal load factor: "
					+ loadFactor);

		// Find a power of 2 >= initialCapacity
		int capacity = 1;
		// 使得capacity 的大小为2的幂,至于为什么,请看下面
		while (capacity < initialCapacity)
			capacity <<= 1;

		this.loadFactor = loadFactor;
		threshold = (int) (capacity * loadFactor);
		table = new Entry[capacity];
		init();
	}

下面是用于包装key-value映射关系的Entry,它是HashMap的静态内部类:
static class Entry<K, V> implements Map.Entry<K, V> {
		final K key;
		V value;
		Entry<K, V> next;
		final int hash;

		/**
		 * Creates new entry.
		 */
		Entry(int h, K k, V v, Entry<K, V> n) {
			value = v;
			next = n;
			key = k;
			hash = h;
		}

		public final K getKey() {
			return key;
		}

		public final V getValue() {
			return value;
		}

		public final V setValue(V newValue) {
			V oldValue = value;
			value = newValue;
			return oldValue;
		}

		public final boolean equals(Object o) {
			if (!(o instanceof Map.Entry))
				return false;
			Map.Entry e = (Map.Entry) o;
			Object k1 = getKey();
			Object k2 = e.getKey();
			if (k1 == k2 || (k1 != null && k1.equals(k2))) {
				Object v1 = getValue();
				Object v2 = e.getValue();
				if (v1 == v2 || (v1 != null && v1.equals(v2)))
					return true;
			}
			return false;
		}

		public final int hashCode() {
			return (key == null ? 0 : key.hashCode())
					^ (value == null ? 0 : value.hashCode());
		}

		public final String toString() {
			return getKey() + "=" + getValue();
		}

		/**
		 * This method is invoked whenever the value in an entry is overwritten
		 * by an invocation of put(k,v) for a key k that's already in the
		 * HashMap.
		 */
		void recordAccess(HashMap<K, V> m) {
		}

		/**
		 * This method is invoked whenever the entry is removed from the table.
		 */
		void recordRemoval(HashMap<K, V> m) {
		}
	}

二、下面我们看看HashMap的put和get及remove方法源码,就知道为什么说它数据结构是链表和数组了
  
// 根据key获取value
	public V get(Object key) {
		if (key == null)
			return getForNullKey();
		//根据key的hashCode值计算它的hash码
		int hash = hash(key.hashCode());
		//直接取出table数组中指定索引处的值
		for (Entry<K, V> e = table[indexFor(hash, table.length)]; 
		e != null; 
		//搜索该Entry链的下一个Entry
		e = e.next) {
			Object k;
			//如果该Entry的key与被搜索key相同
			if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
				return e.value;
		}
		return null;
	}

	private V getForNullKey() {
		//key为null,hash码为0,也就是说key为null的Entry位于table[0]的Entry链上
		for (Entry<K, V> e = table[0]; e != null; e = e.next) {
			if (e.key == null)
				return e.value;
		}
		return null;
	}
    public V put(K key, V value) {
		if (key == null)
			return putForNullKey(value);
		//根据key的hashCode值计算它的hash码
		int hash = hash(key.hashCode());
		//搜索指定hash值对应table中的索引值
		int i = indexFor(hash, table.length);
		for (Entry<K, V> e = table[i]; e != null; e = e.next) {
			Object k;
			//如果找到指定key与需要放入的key相等(hash值相同,通过equals比较返回true)
			if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
				V oldValue = e.value;
				//新的值覆盖旧值
				e.value = value;
				//这个方法是个空方法,可能是表示个标记,字面意思是表示记录访问
				e.recordAccess(this);
				//返回旧值
				return oldValue;
			}
		}

		modCount++;
		//如果i处索引处的Entry为null,表示此处还没有Entry
		//将key、value添加到i索引处
		addEntry(hash, key, value, i);
		return null;
	}

	//key=null的键值对,默认存放table[0]的Entry链
	private V putForNullKey(V value) {
		for (Entry<K, V> e = table[0]; e != null; e = e.next) {
			if (e.key == null) {
				V oldValue = e.value;
				e.value = value;
				e.recordAccess(this);
				return oldValue;
			}
		}
		modCount++;
		addEntry(0, null, value, 0);
		return null;
	}
        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);
	}
//根据键值移除key-value映射对象
	public V remove(Object key) {
		Entry<K, V> e = removeEntryForKey(key);
		return (e == null ? null : e.value);
	}

	final Entry<K, V> removeEntryForKey(Object key) {
		int hash = (key == null) ? 0 : hash(key.hashCode());
		int i = indexFor(hash, table.length);
		Entry<K, V> prev = table[i];
		Entry<K, V> e = prev;

		while (e != null) {
			Entry<K, V> next = e.next;
			Object k;
			if (e.hash == hash
					&& ((k = e.key) == key || (key != null && key.equals(k)))) {
				modCount++;
				size--;
				if (prev == e)
					table[i] = next;
				else
					prev.next = next;
				//空方法,表示移除记录
				e.recordRemoval(this);
				return e;
			}
			prev = e;
			e = next;
		}

		return e;
	}

 

三、HashMap的hash算法和size大小调整,为什么说HashMap此类不保证映射的顺序,特别是它不保证该顺序恒久不变请看以下分解:
  static int hash(int h) {//这里不是很懂,得向他人请教
		// This function ensures that hashCodes that differ only by
		// constant multiples at each bit position have a bounded
		// number of collisions (approximately 8 at default load factor).
		h ^= (h >>> 20) ^ (h >>> 12);
		return h ^ (h >>> 7) ^ (h >>> 4);
	}

	/**
	 * Returns index for hash code h.
	 */
	// 根据hash码求的数组小标并返回,当length为2的幂时,h & (length-1)等价于h%(length-1),这里也就是为什么前面说table的长度必须是2的幂
	static int indexFor(int h, int length) {
		return h & (length - 1);
	}
// 调整大小
	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);
	}

	/**
	 * 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 {    
                                        //注意这里哈,HashMap不保证顺序恒久不变
                                        //在这里可以找到答案
					Entry<K, V> next = e.next;
					int i = indexFor(e.hash, newCapacity);
					e.next = newTable[i];
					newTable[i] = e;
					e = next;
				} while (e != null);
			}
		}
	}

四、HashMap与Set的关系,Set代表一种集合元素无序、集合元素不可重复的集合。如果只考察HashMap中的key,不难发现集合中的key有一个特征:所有的key不能重复,key之间无序。具备了Set的特征,所有的key集合起来组成一个Set集合。同理所有的Entry集合起来,也是一个Set集合。而value是可以重复的,不能组成一个Set集合,在HashMap源代码中提供了values()方法把value集合起来组成Collection集合。
 private abstract class HashIterator<E> implements Iterator<E> {
		Entry<K, V> next; // next entry to return
		int expectedModCount; // For fast-fail
		int index; // current slot
		Entry<K, V> current; // current entry

		HashIterator() {
			expectedModCount = modCount;
			if (size > 0) { // advance to first entry
				Entry[] t = table;
				while (index < t.length && (next = t[index++]) == null)
					;
			}
		}

		public final boolean hasNext() {
			return next != null;
		}

		final Entry<K, V> nextEntry() {
			if (modCount != expectedModCount)
				throw new ConcurrentModificationException();
			Entry<K, V> e = next;
			if (e == null)
				throw new NoSuchElementException();

			if ((next = e.next) == null) {
				Entry[] t = table;
				while (index < t.length && (next = t[index++]) == null)
					;
			}
			current = e;
			return e;
		}

		public void remove() {
			if (current == null)
				throw new IllegalStateException();
			if (modCount != expectedModCount)
				throw new ConcurrentModificationException();
			Object k = current.key;
			current = null;
			HashMap.this.removeEntryForKey(k);
			expectedModCount = modCount;
		}

	}
     private final class ValueIterator extends HashIterator<V> {
		public V next() {
			return nextEntry().value;
		}
	}

	private final class KeyIterator extends HashIterator<K> {
		public K next() {
			return nextEntry().getKey();
		}
	}

	private final class EntryIterator extends HashIterator<Map.Entry<K, V>> {
		public Map.Entry<K, V> next() {
			return nextEntry();
		}
	}
	Iterator<K> newKeyIterator() {
		return new KeyIterator();
	}
	Iterator<V> newValueIterator() {
		return new ValueIterator();
	}
	Iterator<Map.Entry<K, V>> newEntryIterator() {
		return new EntryIterator();
	}
	// Views

	private transient Set<Map.Entry<K, V>> entrySet = null;
	 //把所有的key集合成Set集合
	public Set<K> keySet() {
		Set<K> ks = keySet;
		return (ks != null ? ks : (keySet = new KeySet()));
	}
 
	private final class KeySet extends AbstractSet<K> {
		public Iterator<K> iterator() {
			return newKeyIterator();
		}

		public int size() {
			return size;
		}

		public boolean contains(Object o) {
			return containsKey(o);
		}

		public boolean remove(Object o) {
			return HashMap.this.removeEntryForKey(o) != null;
		}

		public void clear() {
			HashMap.this.clear();
		}
	}
    //把所有的values集合成Collection集合
	public Collection<V> values() {
		Collection<V> vs = values;
		return (vs != null ? vs : (values = new Values()));
	}
	
	private final class Values extends AbstractCollection<V> {
		public Iterator<V> iterator() {
			return newValueIterator();
		}
		public int size() {
			return size;
		}
		public boolean contains(Object o) {
			return containsValue(o);
		}

		public void clear() {
			HashMap.this.clear();
		}
	}
	  //把所有的Entry对象集合成Set集合
	public Set<Map.Entry<K, V>> entrySet() {
		return entrySet0();
	}

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

	private final class EntrySet extends AbstractSet<Map.Entry<K, V>> {
		public Iterator<Map.Entry<K, V>> iterator() {
			return newEntryIterator();
		}
		public boolean contains(Object o) {
			if (!(o instanceof Map.Entry))
				return false;
			Map.Entry<K, V> e = (Map.Entry<K, V>) o;
			Entry<K, V> candidate = getEntry(e.getKey());
			return candidate != null && candidate.equals(e);
		}
		public boolean remove(Object o) {
			return removeMapping(o) != null;
		}
		public int size() {
			return size;
		}
		public void clear() {
			HashMap.this.clear();
		}
	}

五、fail-fast策略(速错)HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛ConcurrentModificationException,这就是所谓fail-fast策略(速错),这一策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了
private abstract class HashIterator<E> implements Iterator<E> {
		Entry<K, V> next; // next entry to return
		int expectedModCount; // For fast-fail
		int index; // current slot
		Entry<K, V> current; // current entry

		HashIterator() {
			expectedModCount = modCount;
			if (size > 0) { // advance to first entry
				Entry[] t = table;
				while (index < t.length && (next = t[index++]) == null)
					;
			}
		}

		public final boolean hasNext() {
			return next != null;
		}

		final Entry<K, V> nextEntry() {
			if (modCount != expectedModCount)
				throw new ConcurrentModificationException();
			Entry<K, V> e = next;
			if (e == null)
				throw new NoSuchElementException();

			if ((next = e.next) == null) {
				Entry[] t = table;
				while (index < t.length && (next = t[index++]) == null)
					;
			}
			current = e;
			return e;
		}

		public void remove() {
			if (current == null)
				throw new IllegalStateException();
			if (modCount != expectedModCount)
				throw new ConcurrentModificationException();
			Object k = current.key;
			current = null;
			HashMap.this.removeEntryForKey(k);
			expectedModCount = modCount;
		}

	}

  • 大小: 34.1 KB
0
0
分享到:
评论

相关推荐

    java jdk 宝典 源代码

    总之,Java JDK源代码是开发者不可或缺的学习资料,它为我们打开了Java世界的大门,让我们能够深入探究这个平台的内在运作机制。无论是为了学习、调试还是创新,都应该充分利用这些宝贵的资源,不断提升自己的技术...

    jdk源代码src.zip

    深入研究JDK源代码,不仅可以帮助我们更好地理解和使用Java提供的各种API,还能让我们学习到优秀的编程实践和设计模式。比如,观察Collections类的静态工厂方法,可以学习如何编写高效且易于使用的工具类;研究...

    通过分析 JDK 源代码研究 Hash 存储机制

    本文将深入探讨JDK源代码中的哈希存储机制,以帮助我们理解这些数据结构的工作原理。 首先,我们要了解的是哈希函数。哈希函数是哈希存储的核心,它的作用是将任意长度的输入(也叫做键或关键字)转化为固定长度的...

    java-jdk源代码免费分享 src.zip

    Java JDK源代码是Java开发人员深入理解平台内部工作原理、优化代码和解决问题的重要参考资料。这份名为"src.zip"的压缩包文件包含了Java Development Kit的源代码,为开发者提供了丰富的学习和研究材料。以下是对...

    JDK1.6 源代码

    JDK1.6的源代码还包含了HotSpot虚拟机的实现,这是一台解释型和即时编译(JIT)的混合型虚拟机。通过分析`src/share/vm`目录下的代码,可以学习到以下内容: - 解释器:理解Java字节码如何被解释执行。 - JIT编译器...

    jdk-1.6.0 源代码 三

    《深入解析JDK 1.6.0源代码——第三部分》 JDK 1.6.0作为Java发展史上的一座里程碑,它的源代码对于理解Java编程语言、虚拟机工作原理以及Java类库的实现至关重要。源代码的深度学习能够帮助开发者提升编程技艺,...

    jdk1.7源代码

    《深入解析JDK1.7源代码》 Java开发人员在面对面试时,经常会遇到关于JDK源码的问题。然而,对于大多数开发者来说,能够详细解答这些源码问题的人并不多。阅读并理解JDK源码,无疑能提升我们的技术水平,增强问题...

    java JDK 实例开发宝典 源代码

    在深入探讨源代码之前,我们需要了解Java JDK的基本组成部分: 1. **Java Runtime Environment (JRE)**:这是Java程序运行的基础,包括Java虚拟机(JVM)、类库和其他运行时需要的组件。 2. **Java Compiler ...

    JDK各种类、方法源代码

    在Java开发中,深入理解JDK的源代码是提升编程技能的重要步骤。JDK,全称为Java Development Kit,是Oracle公司提供的Java编程语言的标准开发工具集。它包含了编译器、运行时环境以及一系列用于创建和运行Java应用...

    java jdk 实例宝典 源代码

    Java JDK实例宝典源代码是Java开发者的重要参考资料,它涵盖了JDK中的各种核心类库、API及其实现机制。这份源代码提供了丰富的示例,帮助开发者深入理解Java语言的使用和内部工作原理。通过研究这些实例,我们可以...

    jdk8源代码

    以上只是JDK 8源代码中部分关键特性的概述,实际源代码包含了大量的类库和实现细节,深入研究这些源代码将有助于我们更好地理解和利用Java平台,提升编程技能。在`launcher`、`org`、`javax`、`java`、`com`这些目录...

    JDK1.5的源代码

    通过学习JDK 1.5的源代码,开发者可以深入了解这些特性的实现原理,提升编程技巧,更好地利用Java平台提供的功能。将src包导入到IDE中,逐行阅读和分析源代码,是学习和理解这些概念的绝佳途径。同时,也可以通过...

    深入jdk6.0源码

    这个主题涵盖了Java语言的基础特性、语法规范以及开发环境的配置和使用,同时也深入到JDK6.0的核心源代码层面,为开发者提供了全面理解Java平台的窗口。 在Java语言特点方面,JDK6.0引入了许多增强,如改进的Swing...

    JDK1.6.0-src.zip(源代码)

    《深入探索JDK1.6.0源代码:解析核心组件与原理》 JDK(Java Development Kit)是Java编程语言的核心工具集,它包含了编译器、运行时环境、库以及各种工具,使得开发者能够编写、调试和运行Java应用程序。JDK 1.6.0...

    JDK实例开发宝典 例子 源代码 ,很经典的

    这份压缩包中包含了丰富的源代码,旨在帮助开发者深入理解和运用Java JDK的各种工具和类库,从而提升开发效率和代码质量。下面我们将详细探讨其中可能涵盖的一些关键知识点。 1. **基础语法与数据类型**:Java的...

    jdk1.7最全源代码

    《深入解析JDK1.7源代码:揭示Java编程的秘密》 JDK1.7,全称为Java Development Kit 1.7,是Java编程语言的重要版本,它的源代码对于开发者来说是一份宝贵的资源,能帮助我们深入了解Java语言的内部机制,提升编程...

    jdk1.8源码

    通过阅读`src.zip`中的源代码,开发者可以洞察到类的内部实现,如`ArrayList`的扩容策略,`StringBuilder`的拼接原理,以及`HashMap`的冲突解决方式等。这将有助于提升我们的编程技巧,使我们能够写出更高效、更健壮...

    jdk1.6 源码jdk1.6 源码

    7. **集合框架**:JDK 1.6的`java.util`包包含了各种集合实现,如ArrayList、LinkedList、HashMap等。源码分析可以揭示它们的内部结构和操作算法,这对于理解性能和选择合适的集合类型很有帮助。 8. **网络编程**:...

    jdk-source-analysis:java jdk源代码分析-java source code analysis

    《深入解析JDK源代码:Java JDK源代码分析》 JDK,全称为Java Development Kit,是Java编程语言的核心组成部分,包含了编译器、运行时环境以及各种API。本项目专注于JDK 1.8版本的源代码分析,旨在帮助开发者更深入...

Global site tag (gtag.js) - Google Analytics