从Java1.6开始:
网络上分析HashMap的版本也主要是JDK1.6,
我的是java1.6.0_45
HashMap有3个构造方法
无参构造
public HashMap() {}
设置the initial capacity的构造方法
public HashMap(int initialCapacity) {}
设置the initial capacity和the load factor的构造方法
public HashMap(int initialCapacity, float loadFactor) {}
通过HashMap构造器放入一个HashMap直接初始化。
public HashMap(Map
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//MAXIMUM_CAPACITY MAXIMUM_CAPACITY = 1 << 30 最大容量 结果为 1073741824
while (capacity < initialCapacity)
capacity <<= 1;
HashMap的本质是 数组+链表
//数组原因 :
//源码是在初始化创建时:
//transient关键字标记的成员变量不参与序列化过程
transient Entry[] table;
table = new Entry[capacity];
//链表原因:
//Entry:
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
/** * Creates new entry. */
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
}
这个就是HashMap的结构
下面分析put方法
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;
}
先分析 key==null的情况,HashMap中允许 key==null
private V putForNullKey(V value) {
//从这里可以了解到 null是存放在 table[0]中的,也就是第一行数组中
//如果存在e.key==null的这种情况
//重写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字面意思就是修改次数
//用modCount记录修改次数
modCount++;
//添加Entry,那么看下addEntry
//了解到key=null的hash值为0,bucketIndex=0
addEntry(0, null, value, 0);
return null;
}
Entry方法:
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
//对象集合默认初始化为null
//所以从这里可以看出它是前插法
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
//如果大小超过需要resize,这个后面讲.
if (size++ >= threshold)
resize(2 * table.length);
}
此时再画一张图:
注意空字符串的hashCode为0,所以当key=”“的时候 key.hash也为0,
问当hash为0时.
static int indexFor(int h, int length) {
return h & (length-1);
}
可知 hash值为0时,indexFor的返回值为0
他就放在bucketIndex为0,在Entry[0]里。
那么再看看删除的情况吧!
remove(Object)方法
public V remove(Object key) {
//hashMap.remove本质是 removeEntryForKey方法
//那么紧接着看removeEntryForKey
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
removeEntryForKey:
//没加修饰符就是friendly。friendly只是一种说法,把它认为是defaul
//这里为了方便变色 写成protected
protected 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;
//以上就是 1.先找到hash值 2.通过hash找到bucketIndex 3.通过bucketIndex找到Entry链
while (e != null) {
//其本质是能否找到e,如果能找到e
Entry<K,V> next = e.next;
//指针向后移动
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
//k=e.key
modCount++;
size--;
//删除就是删除当前节点。链表删除。
if (prev == e)
table[i] = next;
else
prev.next = next;
//删除返回
e.recordRemoval(this);
//空方法
return e;
}
prev = e;
e = next;
}
//找不到的话很明显 e=null
return e;
}
有些版本上说的是对: h%length 原因如下:
当length总是 2 的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。
提高复用性,和解耦的关系
代码的封装是为了提高复用性
解耦是为了提高复用性
table表的建立转到了put方法中,先判断table是否存在,不存在创建
增加putVal方法,在putVal中判断table是否存在,通过resize方法进行初始化表
将新增数据的节点插入到尾部
关于HashMap扩容是,Bucket扩容为原来的2倍, bucket<<=1;
//MAXIMUM_CAPACITY==1<<30
//Integer.MAX_VALUE==1<<31-1
//目的为了防止溢出
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
}
HashMap的容量是不小于输入数的
1<<N
;
Capacity指的是桶的大小,即纵向大小,并不是说总的大小。
它的目的是让“1”变的均匀一点,散列的本意就是要尽量均匀分
当计算出来的hash函数h和hashMap的length做了&运算后,会得到[0,length-1]其中的一个值,而散列的均匀也会使这个值分布的均匀,从而达到HashMap高效的一点。
hash对一个对象的hashCode进行重新计算,而IndexFor生成这个对象的index。
hash值重新计算,是为了防止质量低下的hashCode()函数实现。在hashMap数组长度中长度是初始长度的2倍。通过右移造成地位的数据尽量的不同。
而 在计算index上使用的是h&(length-1)的方法。简单而效率高。
看了Java的代码,自己在设计hash函数的时候,就有选择了。尽量使用位运算符,少使用+-*/%的运算符,这样可以提高hash的效率
JAVA1.6的hash
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
JAVA1.7的hash
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
JAVA1.8的hash
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
1.就table创建区别
2.HashMap1.6底层结构 1.7和1.8底层结构
3.hash的作用,每个版本不同 hash的作用和hash的实际案例 用处
4.具体求桶
5.最大值和初始化值
6.前插和后插
7.putIfAbsent
8.TreeBin的使用
9.涉及到线程安全的包 此时聊到HashTable
10.什么情况下用到hashMap
学习还是需要看源码的,被面试打击的不轻
本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。