JDK 源码分析(4)—— HashMap/LinkedHashMap/Hashtable

HashMap

HashMap采用的是哈希算法+链表冲突解决,table的大小永远为2次幂,因为在初始化的时候,会保证给定的初始容量为2次幂,如下:

// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;

每一次扩展都为2的倍数,这样子的好处在于,计算HashCode之后,计算bucket index时就不需要进行求余运算,直接进行&运算即可(如2^3 - 1 = 0x0111,进行&运算就保证了在该值范围内,而不是2的次幂的话,就不行了,只能求余)

static int indexFor(int h, int length) {
return h & (length-1);
}

具体实现

put

put操作核心就在于先计算Hash找出对应的bucket,然后再看里面是否有对应的entry,如果没有,则新增,有则替换掉原来的值。在新增的过程中,如果判断出容量达到阈值,则要进行上述所讲的扩容。

public V put(K key, V value) {
if (key == null)
// 特殊处理,key为null的,放在bucket 0上
return putForNullKey(value);
// 计算hash code
int hash = hash(key);
// 计算bucket index
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加1
modCount++;
// 添加entry
addEntry(hash, key, value, i);
return null;
} void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
// 达到阈值,并且当前有冲突才会进行rehashing
// 2倍的速度增长
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
} // 添加新的entry
createEntry(hash, key, value, bucketIndex);
} void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}

resize

重新哈希(rehashing),条件是(size >= threshold) && (null != table[bucketIndex]),达到阈值并且产生冲突。如果新容量大于设定的Hash算法切换阈值,有可能会切换Hash算法,这时Hash Code就要进行重新计算。

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];
boolean oldAltHashing = useAltHashing;
// 这里当虚拟机启动后,并且新的容量大于设定切换Hash的阈值,则切换Hash
useAltHashing |= sun.misc.VM.isBooted() &&
(newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
/* 这里的rehash指示是否需要重新计算HashCode,只有当Hash算法进行切换的时候才需要重新计算,
如果已经切换过来了,则不需要了 */
boolean rehash = oldAltHashing ^ useAltHashing;
transfer(newTable, rehash);
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}

transfer

将entry转移到新的table表里面

void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
// 这里就是指示是否需要重新计算HashCode
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}

get

get相对比较简单,计算Hash,定位到对应的bucket,然后再从bucket的链表中查找。

```java`

public V get(Object key) {

if (key == null)

return getForNullKey();

Entry<K,V> entry = getEntry(key);

return null == entry ? null : entry.getValue();

}

final Entry<K,V> getEntry(Object key) {

// 计算Hash code

int hash = (key == null) ? 0 : hash(key);

// 定位到对应的bucket

for (Entry<K,V> e = table[indexFor(hash, table.length)];

e != null;

e = e.next) {

// 循环,依次查找

Object k;

if (e.hash == hash &&

((k = e.key) == key || (key != null && key.equals(k))))

return e;

}

return null;

}


## LinkedHashMap
LinkedHashMap,一般用于在使用HashMap的时候,需要记录每个entry的插入顺序或者访问顺序(如LRU,FIFO)。在HashMap的基础上实现还是比较简单的,只需要自定义一个entry,继承HashMap的entry,在里面添加链表的特性,然后再覆盖对应的addEntry,使用自定义的entry即可。
关键字段:
accessOrder:用于控制节点排序为访问顺序还是插入的顺序,accessOrder=false可以实现FIFO的淘汰策略,accessOrder=true实现LRU,默认为false。 ### 具体实现 #### put
put操作,通过对addEntry以及createEntry的方法来进行追加链表特性。
```java
// put操作当需要添加entry时,会调用此addEntry方法
void addEntry(int hash, K key, V value, int bucketIndex) { // 调用父类的实现,不作更改
super.addEntry(hash, key, value, bucketIndex); // 当有必要的时候,会移除一些键值(通过覆盖removeEldestEntry方法来实现)
Entry<K,V> eldest = header.after;
if (removeEldestEntry(eldest)) {
/* 移除最老的键值对,这里最老的键值对判断方式由子类实现,
但是顺序由accessOrder实现,可以为LRU或者FIFO */
removeEntryForKey(eldest.key);
}
} // 父类的entry会调用到此方法(因为覆盖了)
void createEntry(int hash, K key, V value, int bucketIndex) {
HashMap.Entry<K,V> old = table[bucketIndex];
// 覆盖父类的createEntry是为了使用当前类定义的entry,具有链表的特性
Entry<K,V> e = new Entry<>(hash, key, value, old);
table[bucketIndex] = e;
e.addBefore(header);
size++;
}

get

Get操作也是覆盖了父类的实现,方便访问后调用子类的recordAccess。

public V get(Object key) {
Entry<K,V> e = (Entry<K,V>)getEntry(key);
if (e == null)
return null;
// 访问回调
e.recordAccess(this);
return e.value;
} private static class Entry<K,V> extends HashMap.Entry<K,V> {
Entry<K,V> before, after; void recordAccess(HashMap<K,V> m) {
LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
// 这里accessOrder为false则不调整顺序
if (lm.accessOrder) {
// true则将当前访问的节点移动到的链表头
lm.modCount++;
remove();
addBefore(lm.header);
}
}
}

Hashtable

Hashtable和HashMap的区别:

  1. Hashtable的基本操作采用同步实现,加了synchronized关键字
  2. 扩容策略,可以指定初始大小,后续以这个基础进行扩容,每次均为当前数值两倍(不一定是2次幂,采用求余运算)
  3. 取所有的key或者values时,返回的是Enumeration(也有keySet操作)

JDK 源码分析(4)—— HashMap/LinkedHashMap/Hashtable的更多相关文章

  1. 【集合框架】JDK1.8源码分析之HashMap & LinkedHashMap迭代器(三)

    一.前言 在遍历HashMap与LinkedHashMap时,我们通常都会使用到迭代器,而HashMap的迭代器与LinkedHashMap迭代器是如何工作的呢?下面我们来一起分析分析. 二.迭代器继 ...

  2. JDK源码分析之hashmap就这么简单理解

    一.HashMap概述 HashMap是基于哈希表的Map接口实现,此实现提供所有可选的映射操作,并允许使用null值和null键.HashMap与HashTable的作用大致相同,但是它不是线程安全 ...

  3. JDK源码分析(三)——HashMap 下(基于JDK8)

    目录 概述 内部字段及构造方法 哈希值与索引计算 存储元素 扩容 删除元素 查找元素 总结 概述   在上文我们基于JDK7分析了HashMap的实现源码,介绍了HashMap的加载因子loadFac ...

  4. JDK源码分析(三)——HashMap 上(基于JDK7)

    目录 HashMap概述 内部字段及构造方法 存储元素 扩容 取出元素 删除元素 判断 总结 HashMap概述   前面我们分析了基于数组实现的ArrayList和基于双向链表实现的LinkedLi ...

  5. 源码分析四(HashMap与HashTable的区别 )

    这一节看一下HashMap与HashTable这两个类的区别,工作一段时间的程序员都知道, hashmap是非线程安全的,而且key值和value值允许为null,而hashtable是非线程安全的, ...

  6. 【JDK】JDK源码分析-LinkedHashMap

    概述 前文「JDK源码分析-HashMap(1)」分析了 HashMap 主要方法的实现原理(其他问题以后分析),本文分析下 LinkedHashMap. 先看一下 LinkedHashMap 的类继 ...

  7. 【JDK】JDK源码分析-HashMap(1)

    概述 HashMap 是 Java 开发中最常用的容器类之一,也是面试的常客.它其实就是前文「数据结构与算法笔记(二)」中「散列表」的实现,处理散列冲突用的是“链表法”,并且在 JDK 1.8 做了优 ...

  8. 【JDK】JDK源码分析-HashMap(2)

    前文「JDK源码分析-HashMap(1)」分析了 HashMap 的内部结构和主要方法的实现原理.但是,面试中通常还会问到很多其他的问题,本文简要分析下常见的一些问题. 这里再贴一下 HashMap ...

  9. 【集合框架】JDK1.8源码分析之HashMap(一) 转载

    [集合框架]JDK1.8源码分析之HashMap(一)   一.前言 在分析jdk1.8后的HashMap源码时,发现网上好多分析都是基于之前的jdk,而Java8的HashMap对之前做了较大的优化 ...

随机推荐

  1. linux 编译c程序与动态链接库

    linux 下编译c程序与动态链接库 1 动态库h文件和c文件 1.1 h 文件: kaflog4c.h /** * kaflog4c.h */ #include <stdio.h> #i ...

  2. web多语言url的设计

    因为项目要支持国际化,最近跟一个同事在讨论多语言版本下面url如何设计,假如我们需要支持en和cn的版本. 他倾向于支持如下的url格式,后续以格式1指代: /en/group/abc.html /c ...

  3. (NO.00002)iOS游戏精灵战争雏形(四)

    在Xcode打开MainScene,添加初始化代码如下: -(void)didLoadFromCCB{ NSAssert(_sprite1, @"_sprite1 must not nil& ...

  4. 小强的HTML5移动开发之路(8)——坦克大战游戏2

    来自:http://blog.csdn.net/cai_xingyun/article/details/48629015 在上一篇文章中我们已经画出了自己的坦克,并且可以控制自己的坦克移动,我们继续接 ...

  5. OpenCV特征点检测------Surf(特征点篇)

    Surf(Speed Up Robust Feature) Surf算法的原理                                                              ...

  6. 【Android 应用开发】Android 数据存储 之 SQLite数据库详解

    . 作者 :万境绝尘 转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/19028665 . SQLiteDataBase示例程序下 ...

  7. mysql进阶(十一)外键在数据库中的作用

    MySQL外键在数据库中的作用 MySQL外键的目的是控制存储在外键表中的数据,使两张表形成关联,是MySQL数据库中非常重要的组成部分,值得我们去深入了解.那么,MySQL外键究竟起到哪些作用呢?下 ...

  8. 网站论坛同步用户,整合api,实现…

    在网上参考了很多资料后,终于完美实现了网站和discuz!nt论坛的双向整合,整合后网站和论坛之间可以同步注册.登录.退出和修改登录密码操作. 本系统的实现形式是新云CMS网站(ASP)和Discuz ...

  9. STL常用排序算法介绍

    merge()  以下是排序和通用算法:提供元素排序策略  merge: 合并两个有序序列,存放到另一个序列. #include <iostream> #include <cstdi ...

  10. OS X中app store正在下载的文件在哪里

    很多朋友遇到过app下载一半卡死的情况,点击停止和继续又会无限期等待中. 其实可以将还未下完的app删除来再次尝试下. 打开终端,输入以下命令: open $(getconf DARWIN_USER_ ...