JDK1.7
//HashMap get方法解析
public V get(Object key) {
if (key == null)//如果传入key 为null 直接调用getForNullKey()方法
return getForNullKey();
//否则调用内部链表Entry<K,V> 的getEntry()方法
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
//内部类 Entry<K,V>的 getEntry方法
final Entry<K,V> getEntry(Object key) {
//判断链表是否为kong
if (size == 0) {//size==0 代表链表没有数据 返回null
return null;
}
//hash算法 计算 key的hash值
int hash = (key == null) ? 0 : hash(key);
//调用indexFor() 进行位与运算 目的: 计算key命中的table数组的索引值
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
//遍历该table数组的链表 当前e==链表的头部. 循环下一次为e.next(链表指向下一个值)
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
//这是源码的if语句 为了理解我拆分了下. start
if (e.hash==hash) {//如果链表的头部的e.hash值和传入key的hash值一样的话.
k=e.key//给 对象k赋值==链表e.key
if (k==key||(key!=null&&key.equals(k))) {
//如果 链表的key 和 传入key相同 或者 链表key不为null,并且链表的 key与传入key相同;
//返回当前链表的的value值;
}
}
//end
}//否则返回null;
return null;
}
//indexFor
static int indexFor(int h, int length) {
//位与运算符(&)
//运算规则:两个数都转为二进制,然后从高位开始比较,如果两个数都为1则为1,否则为0。
return h & (length-1);
}
//内部 getForNullKey
private V getForNullKey() {
//如果hashmap的size==0 既没有元素.返回null
if (size == 0) {
return null;
}
//调用put方法的时候,如果key==null 会默认存入 table[]数组的 table[0]上
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
//解释: for循环 Entry<K,V> e= table[0] 的链表的当前值,下一次循环指向链表的next.
if (e.key == null)//当e.key==null 既 链表的e.key值==null 返回value;
return e.value;
}//否则返回null;既没有值
return null;
}
//HashMap put方法解析
public V put(K key, V value) {
//如果当前table数组 为空数组的话
if (table == EMPTY_TABLE) {
//这个方法初始化table数组.
inflateTable(threshold);
}
if (key == null)//如果key为null
return putForNullKey(value);//调用putForNullKey table[0]=new Entry();K=null,V=value hash=0
int hash = hash(key);//否则 计算传入对象key 的hash值;
int i = indexFor(hash, table.length);
/**计算table数组的索引值/计算桶的位置,
*indexFor(hash, table.length);//会产生hash碰撞问题.
*既根据桶的长度和hash值计算有可能会命中一个已存在的桶.而不是空桶
*/
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
//循环table[i]位置的链表 循环第一个是链表头部; 下一个是链表的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;
}
// create Mrs.zhao start
if (e.hash==hash) {//链表e.hash值和传入对象Key的hash值相等
Object k;//声明对象k
k=e.key;
if (k==key||key.equals(k)) {//链表头部 e.key==传入key或者传入对象key和k相同
//对象V OldValue=当前链表e.value;
V oldValue = e.value;
//当前链表头部e.value=传入value 链表e.value 替换;
e.value = value;
e.recordAccess(this);//不知道干嘛的
//返回旧value
return oldValue;
}
}
//end 注意: 如果
}
//当前hashMap 修改次数增加.
modCount++;
//往 table[i]的数组新增链表头部;
addEntry(hash, key, value, i);//hash碰撞以后.这里的方法会调用构造函数new Entry(),来构建一个新Entry,把原来的Entry,作为新Entry的next节点.
return null;
}
//内部Entry 方法 addEntry(int hash{hash值}, K key{key}, V value{value}, int bucketIndex{table数组/table桶的位置})
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
//如果当前hashMap的 table[]数组的size的大小>=threshold 临界值 并且 table[bucketIndex] 不为null 即新增对象不为null
resize(2 * table.length);//执行扩容操作 扩容为原来table.size的2倍.
//
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
// 扩方方法
void resize(int newCapacity) {
Entry[] oldTable = table;//oldTable赋值table
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {//判断当前table大小是否达到最大值.
threshold = Integer.MAX_VALUE;//达到,不进行扩容.
return;
}
Entry[] newTable = new Entry[newCapacity];//没有达到最大值,new一个新entry[]数组为size为原来的2倍
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;//tbale替换成新table
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);//重新计算临界值 threshold.
}
// 旧table 转新table.
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;//取table数组大小
for (Entry<K,V> e : table) {//循环旧table数组
while(null != e) {//当链表不为null
Entry<K,V> next = e.next;
if (rehash) {//判断是否需要重新计算e.key的hash值
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);//计算链表e再新table中的位置.
e.next = newTable[i];//如果i位置原来没有值,则直接插入;有值,采用链头插入法
newTable[i] = e;//赋值;
e = next;//e赋值e.next 进行下一次while循环知道e指向null
}
}
}
// 创建链表方法 createEntry(hash, key, value, bucketIndex);
void createEntry(int hash, K key, V value, int bucketIndex) {
//去除table[i]位置的链表 entry 命名为 链表e -->old entry
Entry<K,V> e = table[bucketIndex];
//创建一个新链表, 把e old entry 作为链表的next节点.存放. 并且给table[i] 赋值为新的链表.
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
hashMap 的底层实现就是:数组➕链表 Node<K,V> [] table;
hash碰撞模拟:
public static void main(String[] args) {
HashMap<User, String> map = new HashMap<>();
for (int i = 0; i < 65536; i++) {
User user = new User();
user.setId("100");
map.put(user, user.toString());
}
}
static class User {
private String id;
private String name;
public User() {
}
@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
'}';
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
hashmap应对hash碰撞的解决途径:构建新链表,传入的value 作为新链表的头部.old value 作为新链表的next节点;
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
//h=计算的hahs值,k=put方法传入key,v=put方法传入的value,n=原链表对象
value = v;
next = n;
key = k;
hash = h;
}
如何获取hashmap table数组里面的对象/既链表.
先给原文链接. https://blog.csdn.net/u013849486/article/details/52629008
方法是这样的.
String string = "nihao";
// string += "<p>" + "hao ni";
System.out.println(string);
HashMap<String,Object> hashMap = new HashMap();
for (int i = 0; i < 10; i++) {
hashMap.put(i + string, i);
hashMap.put(0 + string, UUID.randomUUID().toString());
}
// 利用反射,获取内部字段 "table"
Class clsHashMap = null;
Class clsHashMap$Node = null;
Field[] f = null;
Field t = null, fNode = null;
try {
clsHashMap = Class.forName("java.util.HashMap");
clsHashMap$Node = Class.forName("java.util.HashMap$Entry");
f = clsHashMap.getDeclaredFields();
AccessibleObject.setAccessible(f, true);
for (Field field : f) {
// System.out.println(field.getName());
if (field.getName() == "table")
t = field;
}
Object[] O = ((Object[]) t.get(hashMap));
for (Object o : O) {
if (o != null) {
System.out.println(o);
fNode = clsHashMap$Node.getDeclaredField("next");
fNode.setAccessible(true);
while ((o = fNode.get(o)) != null) {
System.out.println(o);
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
备注:
hashMap hashtable 区别
HashMap 的put`get 方法都没有 使用 synchronized关键字,所以在并发编程的时候不太安全.但是速度快啊! 单线程/某个方法域里的new hashMap 不会影响安全问题, 所以我们最常用的还是 hashMap
HashTable的put`get方法都有使用 synchronized关键字 并发编程同一时刻只有一个线程可以获取该HashTable对象. 保证写入,读取时安全的. 但是效率不高. 同理 单线程/某个方法域里的new hashmap 不会影响安全问题, 用HashTable/HashMap 没区别.