- 浏览: 197711 次
- 性别:
- 来自: 南京
文章分类
最新评论
-
hahalzb:
请问文件解压缩的密码是什么呀
JMS简介与ActiveMQ实战 -
ershimengx:
JMS&ActiveMQ实战(JMS+ActiveMQ ...
JMS简介与ActiveMQ实战 -
lgh1992314:
zenghuiss 写道我书读的少,你不要蒙我哦。。。over ...
Java method invoke的指令简介 -
P00116:
...
JMS简介与ActiveMQ实战 -
风会停息丶:
你好,下载完成后解压密码是多少,跟网盘下载密码一样吗
JMS简介与ActiveMQ实战
自己动手写写:HashMap源码浅析
虽说论坛中有很多关于HashMap源码的分析,并且都是分析得很不错的文章,但是我还是想写出自己的一份心德!
三. HashMap
还是先来看看HashMap的类结构吧!
public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable
1. HashMap的数据存储结构
HashMap采用的是一种数组+链表的存储数据结构!先来感性地看一张图:
其中数据1,2,4,15都是属于HashMap中存储的value值,至于这些值为什么存放在不同位置,这是key经过hash运算,再计算得出的;
这里有人就会问了:”这个计算出来的结果会不会重复呢?“,答案是:这种情况是很有可能发生的。接着又会问:”重复了的话,值怎么放呢?“,
此时链表的作用就发挥了,图中4和15这两个value值就是这种情况。ps:下面会详细介绍。
2. 几个重要的成员变量
/** * The default initial capacity - MUST be a power of two. */ static final int DEFAULT_INITIAL_CAPACITY = 16; /** * The maximum capacity, used if a higher value is implicitly specified * by either of the constructors with arguments. * MUST be a power of two <= 1<<30. */ static final int MAXIMUM_CAPACITY = 1 << 30; /** * The load factor used when none specified in constructor. */ static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * The table, resized as necessary. Length MUST Always be a power of two. */ transient Entry[] table; /** * The number of key-value mappings contained in this map. */ transient int size; /** * The next size value at which to resize (capacity * load factor). * @serial */ int threshold; /** * The load factor for the hash table. * * @serial */ final float loadFactor;
DEFAULT_INITIAL_CAPACITY :其实并不是HashMap的默认初始化容量,而是table数组的长度,并且值大小必须是2的幂次方;
MAXIMUM_CAPACITY:table数组的最大长度是2的30次方;
table:存储了所有的key-value mapping!
我们先来看一下Entry的源码片段:
static class Entry<K, V> implements Map.Entry<K, V>//类结构 //重要的变量 final K key; V value; Entry<K, V> next; final int hash;
size:HashMap的已存储数据的数量;ps:不是table数组的长度
DEFAULT_LOAD_FACTOR:默认的加载因子是0.75f;
threshold:称之为闸阀,如果HashMap的size >= threadhold了,那么table数组就要扩容了,并且扩容率是100%,即table数组长度变为原来的两倍;
此时有人要问了:”这个threshold的值大小是怎么算出来的呢?“,源码中已经表述得很清楚了,下面是构造函数中的一个代码片段:
// Find a power of 2 >= initialCapacity int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; this.loadFactor = loadFactor; threshold = (int)(capacity * loadFactor);
其中initialCapacity是构造函数的一个参数,意为:初始容量;明白了吧,这个initialCapacity并不能直接拿来用,要经过一定的运算保证,
初始化的table数组大小必须是2的幂次方并且不能比initialCapacity的值小。
3. 构造函数
/** * Constructs an empty <tt>HashMap</tt> with the specified initial * capacity and load factor. * * @param initialCapacity the initial capacity * @param loadFactor the load factor * @throws IllegalArgumentException if the initial capacity is negative * or the load factor is nonpositive */ 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; while (capacity < initialCapacity) capacity <<= 1; this.loadFactor = loadFactor; threshold = (int)(capacity * loadFactor); table = new Entry[capacity]; init(); }
上面的这个构造函数是比较重要的,另外一些构造函数都是依赖于它的。在明白了上面我描述的内容后,此构造函数理解起来是相当简单的,不在累述了!
4. 几个重要的方法
put(K key, V value)
/** * Associates the specified value with the specified key in this map. * If the map previously contained a mapping for the key, the old * value is replaced. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with <tt>key</tt>, or * <tt>null</tt> if there was no mapping for <tt>key</tt>. * (A <tt>null</tt> return can also indicate that the map * previously associated <tt>null</tt> with <tt>key</tt>.) */ 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; }
这个方法时比较重要的,也是值得好好分析一下的,下面我们一步一步来分析:
1. key == null 时,看一下putForNullKey(V value)这个方法的源码:
/** * Offloaded version of put for null keys */ 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; }
/** * Adds a new entry with the specified key, value and hash code to * the specified bucket. It is the responsibility of this * method to resize the table if appropriate. * * Subclass overrides this to alter the behavior of put method. */ 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); }
这里先遍历table[0]出的链表,看是否已经存放过key为null的Entry,如果存在则替换掉此Entry的value值,否则就在table[0]处插入Entry。
ps:这里我们可以看出key为null的Entry均是放在table[0]处的,并且hash值也为0.
2. key != null 时,先通过key计算出hash值,再通过hash值运算出table的索引值i,接着循环遍历在table[i]处的链表,
看链表中的key是否已经存在,存在就替换value值,不存在就new一个Entry出来,插入的链表中,next指向插入前table[i]处的Entry!
get(Object key)
/** * Returns the value to which the specified key is mapped, * or {@code null} if this map contains no mapping for the key. * * <p>More formally, if this map contains a mapping from a key * {@code k} to a value {@code v} such that {@code (key==null ? k==null : * key.equals(k))}, then this method returns {@code v}; otherwise * it returns {@code null}. (There can be at most one such mapping.) * * <p>A return value of {@code null} does not <i>necessarily</i> * indicate that the map contains no mapping for the key; it's also * possible that the map explicitly maps the key to {@code null}. * The {@link #containsKey containsKey} operation may be used to * distinguish these two cases. * * @see #put(Object, Object) */ 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)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } return null; }
get方法也很简单,对于key值为null的做一个特殊处理,table[0]出的链表遍历一遍,有就返回value,没有就返回null,不多说了.
containsKey(Object key)和containsValue(Object value)
说一下思路吧:
containsKey就是经过一系列的运算找到key对应的table index值(当然了null key要特殊处理的,你们懂的!),再循环遍历table[index]的链表即可。
containsVlaue没有好的办法,两层循环来搞定,看源码吧:
public boolean containsValue(Object value) { if (value == null) return containsNullValue(); Entry[] tab = table; for (int i = 0; i < tab.length; i++) for (Entry e = tab[i]; e != null; e = e.next) if (value.equals(e.value)) return true; return false; }
看到了吧,遍历数组,再遍历每一个链表。
remove(Object key)
由于remove方法就是调用了removeEntryForKey,我们来看这个方法的源码:
/** * Removes and returns the entry associated with the specified key * in the HashMap. Returns null if the HashMap contains no mapping * for this key. */ 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; }
也说一下思路吧:
经过一系列的运算找到key对应的table index值,也就找到了这个链表,遍历链表得到此key的Entry,删除此Entry,再将链表接起来,
算法细节大家就自己直接看源码吧,不再累述了!
entrySet()
/** * Returns a {@link Set} view of the mappings contained in this map. * The set is backed by the map, so changes to the map are * reflected in the set, and vice-versa. If the map is modified * while an iteration over the set is in progress (except through * the iterator's own <tt>remove</tt> operation, or through the * <tt>setValue</tt> operation on a map entry returned by the * iterator) the results of the iteration are undefined. The set * supports element removal, which removes the corresponding * mapping from the map, via the <tt>Iterator.remove</tt>, * <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt> and * <tt>clear</tt> operations. It does not support the * <tt>add</tt> or <tt>addAll</tt> operations. * * @return a set view of the mappings contained in this map */ public Set<Map.Entry<K, V>> entrySet() { return entrySet0(); }
为何要将一下这个方法? 论坛中也有很多谈论map遍历的效率的问题,用哪种方法效率高! 如果你能够了解HashMap的内部数据结构的话这个问题就很简单了,
当然是遍历table这个数组就行了啊,效率杠杠地!呵呵,对entrySet就是返回的这个,不过是以Set的形式返回而已!
ps:对于这个方法的细节问题我们就不讨论了,有兴趣的可以自己看源码分析!
好了,HashMap的内容暂时就这么多了,当然了还有很多的问题我们没有讨论,比如hash运算的问题,我觉得这个是另外一块的内容了,
对于了解HashMap暂且可以抛开这个问题,hash运算是个很大的讨论内容了,这里不再累述了,有兴趣的读者可以google了解下。
ps:附件中我上传了一个jar包,可以模拟Data Structure相关的运算,非常的不错!推荐下载!命令java - jar visualization.jar 就可以运行!
里面包含了hashing模拟运算过程!
也可参考一篇文章Java Map 集合类简介
发表评论
-
Java class file format
2011-08-29 17:26 0A class file consists of a s ... -
自己动手写写:HashSet、LinkedHashSet源码浅析
2011-08-12 14:40 2019这篇文章我只是作为一个简要的分析。 首先可以看看之前写 ... -
自己动手写写:LinkedHashMap源码浅析
2011-08-11 10:31 4244此系列文章中,上一篇是关于HashMap的源码剖析,这篇文章将 ... -
自己动手写写:LinkedList源码浅析
2011-08-03 15:05 2623上篇文章浅析了ArrayList的源码相关内容!这篇文章将介绍 ... -
自己动手写写:ArrayList源码浅析
2011-08-02 17:29 3473了解你所使用的东西,最直接有效的方式莫过于源码切入的方式! ...
相关推荐
Java集合专题总结:HashMap和HashTable源码学习和面试总结 本文总结了Java集合专题中的HashMap和HashTable,涵盖了它们的源码学习和面试总结。HashMap是一种基于哈希表的集合类,它的存储结构是一个数组,每个元素...
在Java编程语言中,集合框架是开发者日常工作中不可或缺的一部分,HashMap作为其中的重要成员,它的实现原理对于理解Java性能优化和数据结构有深远的意义。HashMap是一个基于哈希表的数据结构,它实现了Map接口,...
面试总结:HashMap
根据提供的文件信息,以下是对JDK 8.0中HashMap实现原理的详细知识点介绍: 1. HashMap概述 HashMap是Java集合框架的一部分,它实现了Map接口,用于存储键值对。其核心特点在于基于哈希表的映射机制,能够通过键...
### HashMap多线程解决方案 #### 一、引言 在多线程环境下,Java的`HashMap`类在处理并发操作时容易出现线程安全问题。本文档深入探讨了`HashMap`在多线程环境中可能遇到的安全问题,并提出了一系列可行的解决方案...
HashMap 是 Java 中最常用的集合类之一,它是基于哈希表数据结构实现的,提供快速的存取操作。在深入理解 HashMap 的实现原理之前,我们先要明白哈希表的基本概念。哈希表是一种通过哈希函数将键(Key)映射到数组...
HashMap源码深度剖析,面试必备
HashMap源码(JDK1.7,含注释)
### JDK 7.0集合系列解析之HashMap实现原理 #### 一、HashMap概述 HashMap是Java集合框架中Map接口的典型实现之一,主要用于存储键值对(Key-Value)数据。其特性如下: - 线程不安全,方法为非同步方法,因此多...
HashMap的部分源码解析
《手写HashMap源码解析——深入理解数据结构与算法》 HashMap是Java编程语言中一个常用的...因此,无论是正在找工作的程序员还是经验丰富的开发者,都应该对手写HashMap的源码进行深入研究,以提升自己的奇数水平。
hashmap源码,可以看看http://blog.csdn.net/wabiaozia/article/details/50684556
HashMap 总结 HashMap 是 Java 中的一种常用的数据结构,用于存储键值对(Key-Value)数据。下面是 HashMap 的一些特性和使用方法总结。 键(Key)的特性 1. 键可以为 null:HashMap 中的键可以为 null,这意味着...
精确的版本号是jdk-7u80。想不通,竟然很多人都收费,这个明明可以在安装JDK的目录中找到啊!自己下一个JDK就可以得到。
·企业级笔试面试题目深入源码级讲解,拒绝死记硬背 4.代码量更大、案例更丰富、更贴近实战: ·Java语言基础阶段:12720行代码,Java语言高级阶段:11684行代码 ·课堂实战项目3套,课后实战项目2套 ·近百道企业...
HashMap源码流程图 一图解析HashMap源码流程 // 默认的HashMap中数组的长度 16 static final int DEFAULT_INITIAL_CAPACITY = 1 ; // aka 16 // HashMap中的数组的最大容量 static final int MAXIMUM_CAPACITY = 1 ...
精确的版本号是jdk-8u181。想不通,竟然很多人都收费,这个明明可以在安装JDK的目录中找到啊!自己下一个JDK就可以得到。
在这个压缩包“HashMap类.rar”中,包含的是易语言版本的HashMap类源码,这将有助于我们了解易语言如何实现类似Java中HashMap的功能。 HashMap类的核心概念和特性包括以下几点: 1. **哈希表**:HashMap使用哈希表...
HashMap 是 Java 中最常用的集合类之一,它是基于哈希表实现的,提供了高效的数据存取功能。HashMap 的核心原理是将键(key)通过哈希函数转化为数组索引,然后将键值对(key-value pair)存储在数组的对应位置。...