JDK源码-HashMap

1,Map:映射表数据结构,通过key-value完成映射。HashMap的子实现主要包括:HashMap、LinkedHashMap、TreeMap、WeakHashMap、ConcurrentHashMap、IdentityHashMap。以下总结摘录自《Thingking In Java》

-1,HashMap:使用Map集合的默认选择。因为HashMap对速度进行了优化。HashMap是Map基于散列表的实现,并取代了Hashtable。插入和查询的效率相对固定。可以通过构造器设置容量和负载因子以调整容器的性能。

-2,LinkedHashMap:类似于HashMap,但是迭代遍历的时候,取得的顺序是其插入顺序。只比HashMap慢一点,但是在迭代访问时反而更快,因为其使用链表维护内部次序。

-3,TreeMap:基于红黑树的实现(大学毕业面试百度的时候,被问道红黑树,然后被鄙视了。)。查看键值对时,会被排序(次序由Comparable或Comparator决定)。TreeMap的特点是得到的结果是排序后的。TreeMap是唯一一个带有subMap方法的Map,可以返回一个子Map。

-4,WeakHashMap:弱键映射,允许释放映射所指向的对象。这是为了解决特殊问题设置的,比如希望对象能够被垃圾回收机制尽快回收。

-5,ConcurrentHashMap:一种线程安全的Map,不涉及到同步加锁。

-6,IdentityHashMap:使用==代替equals对key值进行比较。


/**

 *

 * @(#) Main.java

 * @Package com.map

 * 

 * Copyright ? JING Corporation. All rights reserved.

 *

 */

 

package com.map;

 

import java.util.HashMap;

import java.util.Iterator;

import java.util.LinkedHashMap;

import java.util.Map;

import java.util.Set;

import java.util.TreeMap;

 

/**

 * 类描述:Run Config设置vm参数:-Xms128M -Xmx512M -XX:PermSize=64M -XX:MaxPermSize=128M

 * 

 * @author: Jing History: Jan 21, 2015 10:14:20 AM Jing Created.

 * 

 */

public class Main {

 

	public static void main(String[] args) {

		// HashMap 插入效率

		Map<Integer, Integer> hashMap = new HashMap<Integer, Integer>();

		long startTime = System.currentTimeMillis();

		addData(hashMap);

		long endTime = System.currentTimeMillis();

		System.out.println("HashMap插入1000000条数据,用时: " + (endTime - startTime));// 272

 

		startTime = System.currentTimeMillis();

		iterMap(hashMap);

		endTime = System.currentTimeMillis();

		System.out.println("HashMap遍历1000000条数据,用时: " + (endTime - startTime));// 84

		// System.out.println(hashMap.toString());

		// LinkedHashMap

		Map<Integer, Integer> linkedHashMap = new LinkedHashMap<Integer, Integer>();

		startTime = System.currentTimeMillis();

		addData(linkedHashMap);

		endTime = System.currentTimeMillis();

		System.out.println("LinkedHashMap插入1000000条数据,用时: "

				+ (endTime - startTime));// 551 LinkedHashMap HashMap

											// 插入和遍历的数据越大,数值的差异越明显

		startTime = System.currentTimeMillis();

		iterMap(linkedHashMap);

		endTime = System.currentTimeMillis();

		System.out.println("LinkedHashMap遍历1000000条数据,用时: "

				+ (endTime - startTime));// 85 LinkedHashMap HashMap

											// 插入和遍历的数据越大,数值的越接近

		// System.out.println(linkedHashMap.toString());

 

		Map<Integer, Integer> treeMap = new TreeMap<Integer, Integer>();

		startTime = System.currentTimeMillis();

		addData(treeMap);

		endTime = System.currentTimeMillis();

		System.out.println(" TreeMap插入1000000条数据,用时: " + (endTime - startTime));// 491

		startTime = System.currentTimeMillis();

		iterMap(treeMap);

		endTime = System.currentTimeMillis();

		System.out.println(" TreeMap遍历1000000条数据,用时: " + (endTime - startTime));// 538

//		System.out.println(treeMap.toString());

	}

 

	/**

	 * 

	 * 方法说明:向Map中添加数据

	 * 

	 * Author: Jing Create Date: Jan 21, 2015 10:40:18 AM

	 */

	static void addData(Map<Integer, Integer> map) {

		for (int i = 0; i <= 1000000; i++) {

			int value = (int) (Math.random() * i);

			map.put(i, value);

		}

 

	}

 

	/**

	 * 

	 * 方法说明:遍历Map

	 * 

	 * Author: Jing Create Date: Jan 21, 2015 10:41:00 AM

	 */

	static void iterMap(Map<Integer, Integer> map) {

 

		Set<Integer> keySet = map.keySet();

		Iterator<Integer> iter = keySet.iterator();

		while (iter.hasNext()) {

 

			int key = iter.next();

			map.get(key);

		}

 

	}

}

2,HashMap:

-1,基于哈希表的Map接口实现,并允许null值和null键。此类不保证映射顺序,特别是不保证该顺序恒久不变。

-2,HashMap的实例有两个参数影响性能分布:初始容量和加载因子。容量是哈希表中桶的数量,出事容量只是哈希表在初始化是创建的容量。加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子和当前容量的乘时,则要对哈希表进行rehash操作,从而哈希表将拥有之前两倍的桶数。即,哈希表中存储的数据量=加载因子 * 当前容量(桶数)。

所以不能将初始容量设置的太高,或将加载因子设置的太低。默认加载因子为0.75。加载因子过小会造成空间的浪费,加载因子过高会造成哈希表中冲突的增加。

此处需要自己阅读下哈希表的数据结构。

3,HashMap继承关系:

4,Map接口:Map接口虽然不继承Collection接口,但是仍然可视为一种Collection视图。Map接口定义了Map子集的所有共用特性方法,并定义了内部类Map.Entry类。Map.Entry存储了Map的key-value对,该类提供了getValue和getKey方法。

5,AbstractMap:实现Map接口的主要实现,方便开发人员重写Map接口。

6,HashMap的静态变量:

-1,初始化容量


 static final int DEFAULT_INITIAL_CAPACITY = 16;

-2,最大容量: 2的30次方


static final int MAXIMUM_CAPACITY = 1 << 30;

-3,默认加载因子


static final float DEFAULT_LOAD_FACTOR = 0.75f;

7,HashMap的成员变量:

-1,Entry[] table:存储Entry键值对对象的数组。相当于hash算法中的初始数组。


transient Entry[] table;

-2, int size:key-value键值对的数量。


transient int size;

-3,int threshold:需要下次扩展的长度,结果为capacity * load


int threshold;

-4,int modCount:HashMap结构化修改次数


  transient volatile int modCount;

-5,entrySet


 // Views

 

    private transient Set<Map.Entry<K,V>> entrySet = null;

8,主要方法:

-1,put(K key, V value):


    public V put(K key, V value) {

        if (key == null)

            return putForNullKey(value);//如果key为空,执行putForNullKey方法

        int hash = hash(key.hashCode());//获取到key对应的hashCode值

        int i = indexFor(hash, table.length);//求取对应hashCode值在table中的位置

        for (Entry<K,V> e = table[i]; e != null; e = e.next) {//判断对应算出哈希表中的位置是否存在元素,如果存在元素,判断该Entry链。如果hash值相等,并且key相同,则替换值

            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++;//结构化修改计数器加1

        addEntry(hash, key, value, i);//在对应哈希表的位置中增加Entry对象。

        return null;

    }

此处的for循环判断Entry链,即HashMap是使用哈希表的数据结构存放,但是对应哈希值相同的元素,使用链表在该位置存放。我们可以看Entry的源码:


 static class Entry<K,V> implements Map.Entry<K,V> {

        final K key;

        V value;

        Entry<K,V> next;//链表,标识同一哈希值的下一实体

        final int hash;

根据put方法,可以查看对应的几个方法。

putForNullKey:放置null Key。


  private V putForNullKey(V value) {

        for (Entry<K,V> e = table[0]; e != null; e = e.next) {

            if (e.key == null) {//查找table中的null key替换value

                V oldValue = e.value;

                e.value = value;

                e.recordAccess(this);

                return oldValue;

            }

        }

        modCount++;

        addEntry(0, null, value, 0);

        return null;

    }

addEntry方法:在对应table数组的buketIndex位置上,增加新的Entry对象,并使新Entry对象的next指向原有Entry对象。


  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)//存储元素加1,并在加1后判断是否超过了阀值,阀值在初始化HashMap时初始化。

            resize(2 * table.length);

    }

参考Entry对象的构造方法,很容易理解此处的代码:


   Entry(int h, K k, V v, Entry<K,V> n) {

            value = v;

            next = n;

            key = k;

            hash = h;

        }

-2,get(Object k):


    public V get(Object key) {

        if (key == null)

            return getForNullKey();

        int hash = hash(key.hashCode());//获取哈希值

        for (Entry<K,V> e = table[indexFor(hash, table.length)];//获取对应哈希值的Entry链,遍历Entry链,查找到对应key的Entry对像,返回对应Entry的value。

             e != null;

             e = e.next) {

            Object k;

            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

                return e.value;

        }

        return null;

    }

-3,remove(Object key):


   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());//获取hash值

        int i = indexFor(hash, table.length);//hash在哈希表中的位置

        Entry<K,V> prev = table[i];//获取对应值的Entry链的首元素

        Entry<K,V> e = prev;

 

        while (e != null) {

            Entry<K,V> next = e.next;//遍历Entry链,查找值,从Entry链中移除

            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;

    }

-4,HashIterator:HashMap对应的迭代器


 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;

        }

 

    }

-5,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();

        }

    }

-6,三个迭代器


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();

        }

    }

9,HashMap子类,LinekedHashMap

LinekedHashMap使用链表实现其底层结构。


  private transient Entry<K,V> header;

基于HashMap.Entry对象,其定义了链表的Entry对象:


 private static class Entry<K,V> extends HashMap.Entry<K,V> {

        // These fields comprise the doubly linked list used for iteration.

        Entry<K,V> before, after;

 

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

            super(hash, key, value, next);

        }

 

        /**

         * Removes this entry from the linked list.

         */

        private void remove() {

            before.after = after;

            after.before = before;

        }

 

        /**

         * Inserts this entry before the specified existing entry in the list.

         */

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

            after  = existingEntry;

            before = existingEntry.before;

            before.after = this;

            after.before = this;

        }

删除和新增操作等,与LinkedList实现方式基本保持一致。

时间: 2024-10-05 20:12:49

JDK源码-HashMap的相关文章

jdk源码——HashMap

JDK1.7 从源码上看,HashMap 实现了Map接口 cloneable接口,和序列化接口 public class HashMap<K,V>    extends AbstractMap<K,V>    implements Map<K,V>, Cloneable, Serializable{ HashMap的默认初始容量为16 static final int DEFAULT_INITIAL_CAPACITY = 16; HashMap最大容量为2^30 st

JDK源码--HashMap(之resize)

1.HashMap源码阅读目标了解具体的数据结构(hash及冲突链表.红黑树)和重要方法的具体实现(hashCode.equals.put.resize...) 2.重要方法 hashCode 与 equals都是在AbstractMap中定义的 hashCode是各元素hash的累加 h += iter.next().hashCode(); equals 1.是否是本身; 2.是否是Map实例; 3.size是否相等; 4.比较每个value 重点在于put.resize具体实现步骤: put

jdk源码hashMap的1.7与1.8的比较

1.8链表的定义基本上与1.7相同,但是类名改为Node,但是node实现了Map.Entry接口,实质是一样的 static class Node<K,V> implements Map.Entry<K,V> { 1.8的hash值的算法更加直观一点,就是key的hashcode与无符号右移16位的hashcode异或,然后返回.这是为了当length比较小的时候,也能保证考虑到高低Bit位都参与到Hash的计算中,同时不会有太大的开销. static final int has

由JDK源码学习HashMap

HashMap基于hash表的Map接口实现,它实现了Map接口中的所有操作.HashMap允许存储null键和null值.这是它与Hashtable的区别之一(另外一个区别是Hashtable是线程安全的).另外,HashMap中的键值对是无序的.下面,我们从HashMap的源代码来分析HashMap的实现,以下使用的是Jdk1.7.0_51. 一.HashMap的存储实现 HashMap底层采用的是数组和链表这两种数据结构.当我们把key-value对put到HashMap时,系统会根据ha

【图解JDK源码】HashMap的基本原理与它的线程安全性

1. 前言 能用图说清楚的,就坚决不用代码.能用代码撸清楚的,就坚决不写解释(不是不写注释哦). 以下所有仅针对JDK 1.7及之前中的HashMap. 2. 数据结构 HashMap内部通过维护一个Entry<K, V>数组(变量为table),来实现其基本功能,而Entry<K, V>是HashMap的内部类,其主要作用便是存储键值对,其数据结构大致如下图所示. 从Entry的数据结构可以看出,多个Entry是可以形成一个单向链表的,HashMap中维护的Entry<K,

jdk源码阅读-HashMap

前置阅读: jdk源码阅读-Map : http://www.cnblogs.com/ccode/p/4645683.html 在前置阅读的文章里,已经提到HashMap是基于Hash表实现的,所以在讲解HashMap之前 ,有必要提前了解下Hash的原理. 参考<算法导论><算法>

JDK源码笔记-java.util.HashMap

HashMap 的存储实现 当程序试图将多个 key-value 放入 HashMap 中时,以如下代码片段为例: Java代码 HashMap<String , Double> map = new HashMap<String , Double>(); map.put("语文" , 80.0); map.put("数学" , 89.0); map.put("英语" , 78.2); HashMap 采用一种所谓的&quo

【图解JDK源码】HashMap的容量大小增长原理(JDK1.6/1.7/1.8)

1. 前言 HashMap的容量大小会根据其存储数据的数量多少而自动扩充,即当HashMap存储数据的数量到达一个阈值(threshold)时,再往里面增加数据,便可能会扩充HashMap的容量. 可能? 事实上,由于JDK版本的不同,其阈值(threshold)的默认大小也变得不同(主要是计算公式的改变),甚至连判断条件也变得不一样,所以如果说threshold = capacity * loadFactor(容量 * 负载因子)将不再绝对正确,甚至说超过阈值容量就会增长也不再绝对正确,下面就

阅读JDK源码有感

最近加班不是很严重,爱上了查看JDK源码,每天回来,准备一杯咖啡,开始阅读,受益良多.从上周开始阅读,觉得还是写下感想和学习心得比较好.以后每天阅读,每天记下收获.总体来说,我觉得JDK源码写得十分漂亮,无论是从代码风格还是从重用性来说,都是相当出色的.之前阅读过Thinking in java,感觉很多东西都不能深入理解,太过于理论化,现在结合JDK看来,又别有一番感悟.以前每次有面试,都会从网上收集一些面试题,死记硬背一些知识,HashMap与HashTable的区别啊之类的,但是看了JDK