本部分转载参考【http://www.importnew.com/16301.html】
Java语言中,最基础的数据结构 只有 数组 + 模拟指针 (引用),所有的数据结构底层都是这么构造出来的。
HashMap的本质:链表散列的数据结构(链表+数组),数据每一项是个数组,数组中每一项是链表【链表太长时,用红黑树存,加快查找速度(好开心,知识连起来了)】。以空间换时间。
//HashMap的源码
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
HashMap实现了Map接口的非同步实现,不是线程安全的。【resize死循环,抛出异常后的fail-fast策略】
HashMap实现了Cloneable接口,实现了clone( )方法,即clone了一个HashMap对象并返回。
HashMap实现了Serializable接口,实现了串行读取、写入功能。
writeObject( ),将HashMap"总容量、实际容量、所有Entry"写入输出流中。
readObject( ),将HashMap"总容量、实际容量、所有Entry"依次读出。
- HashMap不是线程安全的,如果使用迭代器过程中,其他线程修改了map,那么会抛出ConcurrentModificationException 异常。
允许使用nulll值,和 null 键。不保证映射顺序的恒久不变。
- 向HashMap中存储put值时,根据key的hashcode 重新计算hash 值,根据hash值得到元素在数组中的下标,如果数组的该位置中已经有元素了,那么本位置元素开始呈链表 结构存放,新加入的放在链表头部。若没有元素,则直接放置,暂时不涉及链表结构。
- addEntry(hash,key,value,i)方法根据计算出的hash值,将key-value对放在数组table的bucketIndex索引处。
- HashMap存储key-value对是,只考虑了key来决定Entry放置的位置。可以把value视为key的附属,系统确定key位置后,顺便把value 保存在那里。
- HashMap找元素时,根据key的hash值,确定对应数组中的位置。应该尽量优化hash算法,这样每个位置均匀分布(下条说道hashMap长度都是2^n),减少了遍历链表的时间,大大优化查询效率。
- 任意对象 hashCode() 返回值相同,那么程序调用 hash(int h)方法计算得到的hash码也是相同的。对hash值对数组长度取模运算??元素可以相对均匀分布。大致懂了。而HashMap底层数组的长度总是 2 的 n 次方,这是HashMap在速度上的优化。那么 length总是2的n次方时,hash & (length-1) eg:length=16=2^4时,是hash & 1111 = hash??有意义???等价于对length取模,也就是 hash%length ,方便理解,毕竟&比%有更高的效率。【当数组长度为2的n次幂的时候,不同的key算得得index相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。】
- HashMap底层用Entry[]数组保存所有的 key-value对,需要存储Entry对象时,根据hash算法来决定存储位置,根据equals方法决定在数组位置上的链表中的存储位置。取Entry时,用hash找到数组中的位置,再用equals找到位置中存放的链表中的位置。
HashMap的扩容resize
- 本来数组长度是固定的,但元素越来越多 hash冲突越来越高,为了提高查找效率,需要对hashmap数组扩容。(那么已经存咋的数据必须重新计算扩容后新的位置,再放进入)。
transient Entry<k,v>[] table;//存储(位桶)的数组</k,v>
【
http://liyanblog.cn/articles/2012/09/28/1348814751429.html
】
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;}
HashMap<String , Double> map = new HashMap<String , Double>(2);
map.put("语文" , 80.0);
map.put("数学" , 89.0);
map.put("英语" , 78.2);
hashCode( )方法 得到key的hash值
int hash = hash(key.hashCode());
indexFor( )方法 找到对于位置
int i = indexFor(hash, table.length);
HashMap的存储
public V put(K key, V value) {
// HashMap允许存放null键和null值。
// 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。
if (key == null)
return putForNullKey(value);
// 根据key的hashCode重新计算hash值。
int hash = hash(key.hashCode());
// 搜索指定hash值在对应table数组中的索引位置。
int bucketIndex = indexFor(hash, table.length);
// 如果 bucketIndex 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。
for (Entry<K,V> e = table[bucketIndex]; e != null; e = e.next) {
Object k;
//判断条件要 hash值相同 && key值相同(用==或equals两种方法判断前者更快,因为只是比较引用)。 因为存在key值不同却得到相同hash值的可能。
//“==” 比较的是值:变量(栈内存)中存放的对象的堆内存地址
//“equals”比较两个对象的内容(栈内存中存放的内容,不是比地址)equals(k)的k已经成了e.key了
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;//若存在相同hash值,key相同,那么直接覆盖value,返回旧value
e.value = value;
e.recordAccess(this);
return oldValue;//??为啥返回oldValue???
}
}
// 如果bucketIndex索引处的Entry为null,表明此处还没有Entry。
modCount++;//修改次数增加1,用来实现 fail-fast机制???
// 将key、value添加到i索引处。
addEntry(hash, key, value, bucketIndex);//这个不管是否Entry为null,都要执行的下面addEntry()方法
return null;
}
- import java.util.HashMap;
- import java.util.Map;
- public class Test {
- public static void main(String[] args) {
- Map<String, String> map = new HashMap<String, String>();
- String p1 = map.put("11", "22");
- System.out.println("p1:" + p1);
- String p2 = map.put("33", "44");
- System.out.println("p2:" + p2);
- String value1 = map.get("11");
- System.out.println("value1:" + value1);
- String p3 = map.put("11", "44");
- System.out.println("p3:" + p3);
- String value2 = map.get("11");
- System.out.println("value2:" + value2);
- }
- }
输出结果:
- p1:null
- p2:null
- value1:22
- p3:22
- value2:44
说明:put方法返回值为null或者value;
如果key没有重复,put成功,则返回null,如p1、p2;
如果key重复了,返回的是map.get(key),也就是当前这个key对应的value,如上面的p3,key="11",而p1的key也是"11",p1与p3重复,返回的是p1的value="22",并且将p3覆盖掉p1
HashMap的Entry添加
void addEntry(int hash, K key, V value, int bucketIndex) {
//已知hashCode值->bucketIndex索引值,key,value。
// 获取指定 bucketIndex 索引处的 Entry
Entry<K,V> e = table[bucketIndex];
// 将新创建的 Entry 放入 bucketIndex 索引处,本来该位置没有Entry,现在是新建立一个Entry.
//后面是错的---->并让新的 Entry 指向原来的 Entry,
//这句话是错的---> 新的Entry做为链表头,e是本Entry的next,本位置记录为链表头
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
// 如果 Map 中的 key-value 对的数量超过了极限
if (size++ >= threshold)
// 把 table 对象的长度扩充到原来的2倍。
resize(2 * table.length);
}
HashMap的读取
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode()); //hash算法得到hashCode值
for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null;e = e.next) {
Object k
//找到数组中对应的某一个元素(table数组中的hash位),本位存放了好多Entry<K,V>,通过key的equals方法在对应位置的链表上找到需要的元素。
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
HashMap的遍历方式
Map map = new HashMap();
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Object key = entry.getKey();
Object val = entry.getValue();
}
补充部分:
接口实现,红黑树代码转载自【http://blog.csdn.net/tuke_tuke/article/details/51588156】
用Entry<k,v>实现Map.Entry接口。
//Node是单向链表,它实现了Map.Entry接口
static class Node<k,v> implements Map.Entry<k,v> {
final int hash;
final K key;
V value;
Node<k,v> next;
//构造函数Hash值 键 值 下一个节点
Node(int hash, K key, V value, Node<k,v> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + = + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
//判断两个node是否相等,若key和value都相等,返回true。可以与自身比较为true
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<!--?,?--> e = (Map.Entry<!--?,?-->)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
红黑树???没看懂
//红黑树
static final class TreeNode<k,v> extends LinkedHashMap.Entry<k,v> {
TreeNode<k,v> parent; // 父节点
TreeNode<k,v> left; //左子树
TreeNode<k,v> right;//右子树
TreeNode<k,v> prev; // needed to unlink next upon deletion
boolean red; //颜色属性
TreeNode(int hash, K key, V val, Node<k,v> next) {
super(hash, key, val, next);
}
//返回当前节点的根节点
final TreeNode<k,v> root() {
for (TreeNode<k,v> r = this, p;;) {
if ((p = r.parent) == null)
return r;
r = p;
}
}