- 浏览: 468418 次
- 性别:
- 来自: 杭州
文章分类
- 全部博客 (146)
- Maven (3)
- Quartz (10)
- Hessian (3)
- JDK (42)
- eclipse (4)
- 设计思想 (6)
- XML (8)
- JavaMail (1)
- Spring (11)
- mina (1)
- HsqlDb (1)
- Cache (2)
- Tool (6)
- 心情 (5)
- JQuery (0)
- Hadoop (5)
- Hbase (3)
- 自动构建 (7)
- JNDI (0)
- 代码赏析 (5)
- Oracle (1)
- Excel (4)
- Effective Java (5)
- JAXB (4)
- fdafasdf (1)
- ccc (0)
- web (3)
- concurrent (1)
- CVS (1)
- eclipse plugin (2)
- Apache (10)
最新评论
-
chxiaowu:
nice!
Quartz实现固定执行次数 -
zxjlwt:
学习了。http://surenpi.com
自定义ClassLoader -
kadlly:
public static final Logger log ...
Hessian 权限认证 -
spring_springmvc:
java程序语言学习教程 地址http://www.zuida ...
Java-Final -
liushuiwuyan:
[img][/img]
设计模式-单例
HashMap源码解析
HashMap是继承自AbstractMap<K,V>,那我们先来看一个AbstractMap<K,V>
AbstractMap<K,V>是接口Map<K,V>的实现,那我们看一个Map<K,V>
Map<K,V>定义了一系列方法的接口,AbstractMap<K,V>对Map<K,V>进行了方法的抽象,基本
上实现在Map的方法,留了一个接口
以下是子类的这现方法:
有人可能郁闷了,这怎么实现的?,我们来看一个EntrySet类
这里使用了一个AbstractSet,抽象集合类型,使用Hashmap的属性来初始化Set中的四个方法,从而达到初始化entrySet的目的.
我们来看一个这个Iterator:
这个遍历器实现了hasNext(),Hashmap下一个元素是否为空,nextEntry()为Itarator的next()方法.
这HashIterator()方法初始化好了第一个元素,从HashMap的最后一个数开始查找,只到第一个不为空的元素,设置为next
此下标的设置为index,在调用next()时,首先把next附值给e,然后再调用e.next,如果e.next不为空,则下一个元素为同一木桶链,
如果为空,则查找数组table中的下一个元素.然后把当前元互设置为e,下一个元素设置为e.next(table[index]).这里还重写了Itrator的
remove()方法,移除当前key实体.
这是移除一个木桶的方法,如果当前待移除的木桶对象和当前待移除的木桶的上一个元素相同,则移走这个木桶,当前下标为下一个元素的对象,则当前元素的和下一个元素不相同,则当前的下一个为下一个元素.当前素和下一个元素相同的情况只有在一级木桶的时候出现.
有人称table对象为一个视图,我称之为木桶,如下图:
蓝色框里的为一级木桶,下面的为木桶链.其实HashMap使用的就是这种原理实现的.
知道了HashMap是如何实例化实体对象后,以下我来介绍一个HashMap是如何put,get相对就应的实体对象.
这是put方法,很简单,首先通过你put进来的对象的hashCode()(经过位运算)和table对象的大小进行位&运算得到一个0到table.length的下标,如果此下标有值,并且key也相同,则发生替代行为,如果此下标没值,或者有值,则添加这个实体对象.
这里添加这个实体对象,如果大小不够的话,会自动添加Hashmap的大小.这里的bucketIndex是一个木桶,可能只有一个也可能是一个链.
这是实体对象,本身就有一个next指向实体对象本身,这就形成了一个木桶链.
以上是put方法,下面介绍一个get方法:
通过key的hashCode()换算,直接到得对象在table[]中的index.得到对象后,循环得到这个对象链直到e.hash=hash,eq(k,e.key).然后返回这个值,如果找到最后都没有则返回null.
HashMap之后以速度会快,是因为对象的HashCode()都是唯一的,经过优化的hash算法可以实现对象链的减少,能够快速定位到对象本身[数组下标].
2010-10-14 日添加说明如下:
HashMap:
其实现方式
transient Entry[] table;
采用实体数组的方式存储,以一个hashCode[经过换算后的数组下标]
具体算法如下:
int hash = hash(key.hashCode());
static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1);
}
这里先根据对象本身的hashCode()方法,然后再移位操作,最后执行与操作,生成数组下标。
有人会问了,原来HashMap就是一个数组啊,对了,就是数组,只不过他在数组的级别上稍微做了改变,比如其下标的生成方式,还有一点也是HashMap特有的,散列表:
如果数组下标相同,那么这个数组的值就会被重写,HashMap也一样:
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;
}
}
这里的e.value = value就是把原先的值改为新的值,这里的e是对象的引用.Entry是一个Map的内部封装类型,其作为对象存储在table的值中.
接下来当hash值在同一个范围,key不同时,那么生成的下标原先存在,而值却不同,接下来就看HashMap的散列表:
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);
}
如果pucketIndex[根据hash生成的数组下标]在table中不存在,那么e就为空,也就是next为空。Table[pucketIndex]就为当前对象。如果不为空,那么原先存在于这个下标下的元素就成为当前的next,这个列表可能会不停的加长,10个,20个….
如果你的HashMap效率很低,那么你要考虑是不是对象的HashCode()方法有问题.
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
HashMap是继承自AbstractMap<K,V>,那我们先来看一个AbstractMap<K,V>
public abstract class AbstractMap<K,V> implements Map<K,V> {
AbstractMap<K,V>是接口Map<K,V>的实现,那我们看一个Map<K,V>
public interface Map<K,V> {
Map<K,V>定义了一系列方法的接口,AbstractMap<K,V>对Map<K,V>进行了方法的抽象,基本
上实现在Map的方法,留了一个接口
public abstract Set<Entry<K,V>> entrySet();
以下是子类的这现方法:
/** * Returns a collection view of the mappings contained in this map. Each * element in the returned collection is a <tt>Map.Entry</tt>. The * collection is backed by the map, so changes to the map are reflected in * the collection, and vice-versa. */ public Set<Map.Entry<K,V>> entrySet() { Set<Map.Entry<K,V>> es = entrySet; return (es != null ? es : (entrySet = (Set<Map.Entry<K,V>>) (Set) new EntrySet())); }
有人可能郁闷了,这怎么实现的?,我们来看一个EntrySet类
private 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(); } }
这里使用了一个AbstractSet,抽象集合类型,使用Hashmap的属性来初始化Set中的四个方法,从而达到初始化entrySet的目的.
我们来看一个这个Iterator:
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; Entry[] t = table;//table数据过去了 int i = t.length; Entry<K,V> n = null; if (size != 0) { while (i > 0 && (n = t[--i]) == null) ; } next = n; index = i; } public boolean hasNext() { return next != null; } Entry<K,V> nextEntry() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); Entry<K,V> e = next; if (e == null) throw new NoSuchElementException(); Entry<K,V> n = e.next; Entry[] t = table; int i = index; while (n == null && i > 0) n = t[--i]; index = i; next = n; return current = 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; } }
这个遍历器实现了hasNext(),Hashmap下一个元素是否为空,nextEntry()为Itarator的next()方法.
这HashIterator()方法初始化好了第一个元素,从HashMap的最后一个数开始查找,只到第一个不为空的元素,设置为next
此下标的设置为index,在调用next()时,首先把next附值给e,然后再调用e.next,如果e.next不为空,则下一个元素为同一木桶链,
如果为空,则查找数组table中的下一个元素.然后把当前元互设置为e,下一个元素设置为e.next(table[index]).这里还重写了Itrator的
remove()方法,移除当前key实体.
/** * Removes and returns the entry associated with the specified key * in the HashMap. Returns null if the HashMap contains no mapping * for this key. */ Entry<K,V> removeEntryForKey(Object key) { Object k = maskNull(key); int hash = hash(k); 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; if (e.hash == hash && eq(k, e.key)) { modCount++; size--; if (prev == e) table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; }
这是移除一个木桶的方法,如果当前待移除的木桶对象和当前待移除的木桶的上一个元素相同,则移走这个木桶,当前下标为下一个元素的对象,则当前元素的和下一个元素不相同,则当前的下一个为下一个元素.当前素和下一个元素相同的情况只有在一级木桶的时候出现.
transient Entry[] table;
有人称table对象为一个视图,我称之为木桶,如下图:
蓝色框里的为一级木桶,下面的为木桶链.其实HashMap使用的就是这种原理实现的.
知道了HashMap是如何实例化实体对象后,以下我来介绍一个HashMap是如何put,get相对就应的实体对象.
public V put(K key, V value) { K k = maskNull(key); int hash = hash(k); int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { if (e.hash == hash && eq(k, e.key)) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, k, value, i); return null; }
这是put方法,很简单,首先通过你put进来的对象的hashCode()(经过位运算)和table对象的大小进行位&运算得到一个0到table.length的下标,如果此下标有值,并且key也相同,则发生替代行为,如果此下标没值,或者有值,则添加这个实体对象.
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); }
这里添加这个实体对象,如果大小不够的话,会自动添加Hashmap的大小.这里的bucketIndex是一个木桶,可能只有一个也可能是一个链.
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; final int hash; Entry<K,V> next; Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } public K getKey() { return HashMap.<K>unmaskNull(key); } public V getValue() { return value; } public V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public 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 int hashCode() { return (key==NULL_KEY ? 0 : key.hashCode()) ^ (value==null ? 0 : value.hashCode()); } public String toString() { return getKey() + "=" + getValue(); } void recordAccess(HashMap<K,V> m) { } void recordRemoval(HashMap<K,V> m) { } }
这是实体对象,本身就有一个next指向实体对象本身,这就形成了一个木桶链.
以上是put方法,下面介绍一个get方法:
public V get(Object key) { Object k = maskNull(key); int hash = hash(k); int i = indexFor(hash, table.length); Entry<K,V> e = table[i];//得到Entry. while (true) { if (e == null) return null; if (e.hash == hash && eq(k, e.key)) return e.value; e = e.next; } }
通过key的hashCode()换算,直接到得对象在table[]中的index.得到对象后,循环得到这个对象链直到e.hash=hash,eq(k,e.key).然后返回这个值,如果找到最后都没有则返回null.
HashMap之后以速度会快,是因为对象的HashCode()都是唯一的,经过优化的hash算法可以实现对象链的减少,能够快速定位到对象本身[数组下标].
2010-10-14 日添加说明如下:
HashMap:
其实现方式
transient Entry[] table;
采用实体数组的方式存储,以一个hashCode[经过换算后的数组下标]
具体算法如下:
int hash = hash(key.hashCode());
static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1);
}
这里先根据对象本身的hashCode()方法,然后再移位操作,最后执行与操作,生成数组下标。
有人会问了,原来HashMap就是一个数组啊,对了,就是数组,只不过他在数组的级别上稍微做了改变,比如其下标的生成方式,还有一点也是HashMap特有的,散列表:
如果数组下标相同,那么这个数组的值就会被重写,HashMap也一样:
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;
}
}
这里的e.value = value就是把原先的值改为新的值,这里的e是对象的引用.Entry是一个Map的内部封装类型,其作为对象存储在table的值中.
接下来当hash值在同一个范围,key不同时,那么生成的下标原先存在,而值却不同,接下来就看HashMap的散列表:
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);
}
如果pucketIndex[根据hash生成的数组下标]在table中不存在,那么e就为空,也就是next为空。Table[pucketIndex]就为当前对象。如果不为空,那么原先存在于这个下标下的元素就成为当前的next,这个列表可能会不停的加长,10个,20个….
如果你的HashMap效率很低,那么你要考虑是不是对象的HashCode()方法有问题.
发表评论
-
Java Application Cache
2016-09-27 19:25 884Application Cache is used very ... -
Java 字符串分词
2015-01-02 14:43 1749在Java的世界里有个类型 ... -
jdk 1.6 新特性,集成Groovy, 性能很差
2014-04-02 14:27 1276性能都是相对的,如果调用量不是很大的话,可以忽略,毕竟使用为主 ... -
Fake Code easy implements
2014-04-01 15:41 1028package org.miniframe.modules ... -
JDK regex 用法及用途
2014-03-31 15:48 1215查找 Boolean flag = pattern.mat ... -
生产者消费者(四)
2014-03-04 12:32 1148需求: 多个生产者不断的生产产品,多个消费者不断的消费产品,仓 ... -
生产者消费者(三)
2014-03-04 10:59 961需求: 多个生产者不断的生产产品,多个消费者不断的消费产品,仓 ... -
生产者消费者(二)
2014-03-03 15:40 695需求: 多个生产者不断的生产产品,多个消费者不断的消费产品,仓 ... -
生产者消费者模式(一)
2014-02-28 14:30 1031需求: 多个生产者不断的生产产品,多个消费者不断的消费产品,仓 ... -
查看Class文件使用的JDK版本
2013-10-30 14:17 1115由于JDK一般是向下兼容的,所以有时候本地的JDK版本比类库的 ... -
Java源代码转码
2012-12-20 17:22 1323现在中国的项目很多,编码无非是UTF-8,GBK,GB2312 ... -
Tomcat集成OSGI,并通过JNDI开放Web调用
2012-12-03 11:22 3135Tomcat集成OSGi,首先要选择OSGI服务器,我这里采用 ... -
JDK的Logging
2012-11-07 15:49 1683jdk自带有一个log日志,对于一般的使用,仅够了. 代码如下 ... -
java.util.*
2012-11-06 14:23 1377java.util 工具包,灰常的有用,有机会一定要研读源码。 ... -
java.util.concurrent.*
2012-11-02 10:38 17761. java.util.concurrent.ArrayBl ... -
java.util.rt.*
2012-10-31 13:51 11131. java.util.HashMap 散列表,主要是以离散 ... -
巧秒设计方法,不返回null
2016-09-27 19:32 723/** * {@inheritDoc} * ... -
java doc 代码文档
2012-07-13 13:58 1330对于代码规范不解释了,网上很多。 在编写代码的时候,有一点灰 ... -
接口与抽象类
2012-07-11 16:53 11241. 接口设计必谨慎,除非业务变更,否则打死不能动接口。[不变 ... -
JVM优化机制好诡异
2012-04-20 08:43 1466long i[] = new long[1000000]; ...
相关推荐
HashMap的源码解析涉及到的数据结构主要包括数组和链表,以及在负载过大时升级为红黑树的优化策略。理解这些机制对于高效使用HashMap和解决相关问题至关重要。此外,HashMap是非线程安全的,如果在多线程环境下使用...
HashMap源码深度剖析,面试必备
《手写HashMap源码解析——深入理解数据结构与算法》 HashMap是Java编程语言中一个常用的集合类,它提供了一种高效、灵活的键值对存储方式。在面试过程中,尤其是2020年及以后的技术面试中,深入理解HashMap的实现...
HashMap的部分源码解析
《HashMap 源码解析——JDK11版本》 HashMap是Java中广泛使用的非同步散列表,其设计和实现是高效且灵活的。在JDK1.8之前,HashMap的底层数据结构采用的是数组+链表的方式,这种方式称为“拉链法”来解决哈希冲突。...
一图解析HashMap源码流程 // 默认的HashMap中数组的长度 16 static final int DEFAULT_INITIAL_CAPACITY = 1 ; // aka 16 // HashMap中的数组的最大容量 static final int MAXIMUM_CAPACITY = 1 ; // 默认的扩容的...
5. HashMap源码解析 - put过程:涉及哈希计算、链表/红黑树的插入,以及扩容逻辑。 - get过程:通过哈希值快速定位到数组槽位,然后根据链表或红黑树结构进行查找。 - resize过程:当负载因子达到阈值时,HashMap...
根据提供的文件信息,以下是对JDK 8.0中HashMap实现原理的详细知识点介绍: 1. HashMap概述 HashMap是Java集合框架的一部分,它实现了Map接口,用于存储键值对。其核心特点在于基于哈希表的映射机制,能够通过键...
03-01-HashMap源码解析-monkey 03-并发List、Set、 ConcurrentHashMap底层原理剖析-monkey 04-Java并发线程池底层原理详解与源码分析-monkey 05-并发编程之深入理解Java线程-fox 06-并发编程之CAS&Atomic原子操作...
另外,"HashMap源码解析"(文件"03-01")虽然不是直接关于并发的,但HashMap在并发环境下可能会出现线程安全问题。在并发编程中,不合适的并发操作HashMap可能导致数据不一致或者死循环。了解HashMap的内部实现有助...
该文件是拷贝的HashMap源码,在源码之上我对核心的代码都做了完整的注释,相信读者一定能够一看就懂,哈哈,HashMap是面试重灾区,我也是面试时才通读HashMap的。
### JDK 7.0集合系列解析之HashMap实现原理 #### 一、HashMap概述 HashMap是Java集合框架中Map接口的典型实现之一,主要用于存储键值对(Key-Value)数据。其特性如下: - 线程不安全,方法为非同步方法,因此多...
源码解析及手写框架(Spring、Tomcat、Hashmap、Mybatis、SpringBoot)阿里面试官都被镇住了
Java源码解析HashMap简介 HashMap是Java开发中最常用的集合之一,它基于hash表的实现,提供了map的所有可选操作,并且允许null值和一个null的key。HashMap的实现提供了常量级的性能,假设hash函数把元素们比较好的...
HashMap源码解析 HashMap的put()方法是核心操作之一,它首先计算键的哈希值,然后根据哈希值定位到数组的索引位置。如果该位置没有元素,就直接插入;如果有元素,可能需要遍历链表找到插入位置。当元素数量达到...
黑马程序员HashMap的笔记,面试必问,笔记很好,内容言简意赅,看完收获很多,希望能帮助大家的学习
HashMap 中红黑树 TreeNode 的 split 方法源码解读 HashMap 中红黑树 TreeNode 的 split 方法是...通过对 split 方法的源码解析,我们可以更好地理解红黑树的实现机制和关键算法,从而更好地应用 Java 中的 HashMap。