HashMap源码分析
2015-11-04 15:16
393 查看
在上一篇博客中,大致介绍了散列以及Java Map的结构,这一篇主要分析HashMap的源代码,主要理解HashMap是如何保存数据、取数据、如何扩容、遍历的效率的对比。分析的源码版本为:java version “1.7.0_71”。
(1)构造函数
我个人最常用的构造函数是参数为空的,这个构造函数会调用另外一个带参数的构造函数, 并传入默认容量16和默认的loadFactor 0.75。这个构造函数比较简单,只进行校验参数和赋值的操作。
(2)接下来是重要的put方法,put方法用于将键值对保存到Map中,让我们来具体分析一下。
然后看看addEntry的实现。
至此put方法已经分析完毕,总结一下:(1)如果key为null,将Entry放到第一个位置,如果存在则覆盖。(2)对key进行hash,找到位置,如果存在相同的key则覆盖。(3)判断一下是否需要扩容,如需要则将size变成2倍,根据hashSeed计算是否需要rehash,将已经存在的Entry赋值到新table。(4)将新的Entry放到合适的位置。
接下来分析一下get方法。
get方法总结如下:(1)判断key是否为null,如果是去第0个位置取值。(2)计算hash值,找到在table中的位置,遍历那个位置上的Entry链表,找到key相等的并返回value。
HashMap有两种典型的遍历方式。
这两种遍历方式调用的keySet和entrySet方法,本质上没有太大的区别,都调用了HashIterator的nextEntry方法,keySet调用的是nextEntry.getKey方法。但是从最终的遍历来看,entrySet的遍历效率应该是比keySet稍高的,推荐用这个方式。
(1)构造函数
我个人最常用的构造函数是参数为空的,这个构造函数会调用另外一个带参数的构造函数, 并传入默认容量16和默认的loadFactor 0.75。这个构造函数比较简单,只进行校验参数和赋值的操作。
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); this.loadFactor = loadFactor; threshold = initialCapacity; init(); }
(2)接下来是重要的put方法,put方法用于将键值对保存到Map中,让我们来具体分析一下。
public V put(K key, V value) { //若table为空的table,首先进行一些初始化操作 //table初始长度为16 if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) //若key为null,则将value放到table第0个位置 //若有值,将覆盖 return putForNullKey(value); //对key进行一次hash,目的在于使得获得的hash值中的0、1均匀 int hash = hash(key); //获得该key在table中的位置 int i = indexFor(hash, table.length); //获得i这个位置的Entry对象,判断是否为空,若不为空则遍历i这个位置的链表,如果有相同的key,将旧的value覆盖 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++; //往一个位置i添加Entry addEntry(hash, key, value, i); return null; }
然后看看addEntry的实现。
void addEntry(int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) { //两倍扩充容量 resize(2 * table.length); //重新计算hash值 hash = (null != key) ? hash(key) : 0; //计算在新table中的位置 bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); } //扩容 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的过程其实也挺简单,只是根据initHashSeedAsNeeded(newCapacity)的结果决定是否需要rehash //如果需要rehash就重新hash一次,找到新位置,否则就根据原来的hash值找到在新表中的位置 transfer(newTable, initHashSeedAsNeeded(newCapacity)); table = newTable; threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); }
至此put方法已经分析完毕,总结一下:(1)如果key为null,将Entry放到第一个位置,如果存在则覆盖。(2)对key进行hash,找到位置,如果存在相同的key则覆盖。(3)判断一下是否需要扩容,如需要则将size变成2倍,根据hashSeed计算是否需要rehash,将已经存在的Entry赋值到新table。(4)将新的Entry放到合适的位置。
接下来分析一下get方法。
public V get(Object key) { //key为null,去第0个位置找 if (key == null) return getForNullKey(); Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue(); } final Entry<K,V> getEntry(Object key) { if (size == 0) { return null; } //计算hash值 int hash = (key == null) ? 0 : hash(key); for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; //找到相应位置并开始遍历,找到key相等的Entry然后返回value if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } return null; }
get方法总结如下:(1)判断key是否为null,如果是去第0个位置取值。(2)计算hash值,找到在table中的位置,遍历那个位置上的Entry链表,找到key相等的并返回value。
HashMap有两种典型的遍历方式。
Map<String, String> map = new HashMap<String, String>(); for (Map.Entry<String, String> entry : map.entrySet()) { System.out.println("key : " + entry.getKey() + ", value" + entry.getKey()); } Set<String> set = map.keySet(); Iterator<String> it = set.iterator(); while (it.hasNext()) { String key = it.next(); System.out.println("key : " + key + ", value" + map.get(key)); }
这两种遍历方式调用的keySet和entrySet方法,本质上没有太大的区别,都调用了HashIterator的nextEntry方法,keySet调用的是nextEntry.getKey方法。但是从最终的遍历来看,entrySet的遍历效率应该是比keySet稍高的,推荐用这个方式。
相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- 从源码安装Mysql/Percona 5.5
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- PropertyChangeListener简单理解
- c语言实现hashmap(转载)
- 插入排序
- 冒泡排序
- 堆排序
- 快速排序