前言:第一次写学习贴,看了很多的帖子,然后也去看了hashMap的源码,想尝试自己去写一下自己对于这个东西的理解,并不一定是对的,可能也就是写出来让自己加深一下基础印象,如果有些的不好的地方请指出。
此博客应该是以JDK1.7为基础的,但是我是以1.8来说的不要混淆了(只因为我懒得打开1.8的源码
)
1.HashMap是什么:让我们重新认识一下这个小哥儿
HashMap是java中AbstractMap的子类,是键值对集合java.util.Map的实现类,是作为Map家族中被使用频率最高的小哥哥![]()
HashMap是java面试时基础中被询问频率极高的你不得不懂得面试题1.2.3
2.HashMap基础了解:HashMap家中拥有着好几个兄弟姐妹,但是咱们只是来了解它的,就不多介绍了,先说的感觉最基础的
HashMap是一个无序的键值对集合,是以Key-Value的形式存储数据的,它只允许一个Key为null值,允许多个value为null值
HashMap是一个非线程安全的键值对集合,即当你使用多线程去操作它时,他可能会出现数据不一致,但是如果你需要它达到线程安全的条件时他也是可以做到的,可以用 Collections(肯来克辛斯
)的synchronizedMap(神奎耐日德Map
)方法使HashMap具有线程安全的能力,或是选择使用ConcurrentHashMap(肯卡韧特HashMap
)这样一个线程安全的Map集合达到目的。
最开始HashMap底层是由数组加链表来实现的,但是在JDK1.8以后在原有的基础数引入了红黑树这样一个二叉树结构的树来优化HashMap
3.让我们去源码层参观一下HashMap

HashMap的默认长度
/** * The default initial capacity - MUST be a power of two. */ static final int DEFAULT_INITIAL_CAPACITY = 16;
HashMap的默认负载因子
/** * The load factor used when none specified in constructor. */ static final float DEFAULT_LOAD_FACTOR = 0.75f;
HashMap的最大长度
/** * The maximum capacity, used if a higher value is implicitly specified * by either of the constructors with arguments. * MUST be a power of two <= 1<<30. */ static final int MAXIMUM_CAPACITY = 1 << 30;
HashMap中修改map长度和负载因子的构造函数(中文注释是我自己加的,个人理解)
--initialCapacity:长度
--loadFactor :负载因子
public HashMap(int initialCapacity, float loadFactor) {
/*如果初始化的长度小于0则抛出异常*/
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
/*如果初始化的长度大于最大值常量那么就以最大值常量为默认长度*/
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
/*如果初始化负载因子小于等于0或则不是一个数字那么抛出异常*/
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// Find a power of 2 >= initialCapacity
/*这里的算法我开始没看懂,然后我把代码拿出来测试了一下,我觉得应该是求比你初始化长度更大的一个2的平方根的值
比如我initialCapacity初始化值是15那么capacity的值就是16,如果我的值>16且小于16X2 那么他的值就一定是32(推测的,未经 测试)之所以这么做这么个算法的原因是因为它本身对于Hash冲突的算法的原因,如果后面有聊到这个我们在解释它本身的hash算法
*/
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
/*赋值这个就不需要我多解释了吧*/
this.loadFactor = loadFactor;
/*我觉得这里应该是求一个阈值,就是达到这个值以后map就会进行扩容,threshold是一个全局的变量*/
threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
/*看到new关键字就不提了吧,初始化一个Entry的素组长度为capacity*/
table = new Entry[capacity];
useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
init();
}
到此这个构造函数就结束了,去看看其他的
发现一个get方法,让我们来看看
public V get(Object key) {
/*这行代码证明HashMap是可以有null的Key的*/
if (key == null)
return getForNullKey();
/*对象获取,源码往下*/
Entry<K,V> entry = getEntry(key);
/*这个对象不为空就返回这个对象得value值*/
return null == entry ? null : entry.getValue();
}
--获取HashMap中key值为空的value源码
private V getForNullKey() {
/*循环取HashMap*/
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
/*如果这个key值==null就返回这个key值得value值,最后则返回null*/
if (e.key == null)
return e.value;
}
return null;
}
--获取HashMap中key值对应得Value源码
final Entry<K,V> getEntry(Object key) {
/*运用三目表达式获取到这个key值得hash码*/
int hash = (key == null) ? 0 : hash(key);
/*根据hash码进行运算得到key值对应的对象在数组中的坐标,最后返回对象*/
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;
}
--hash码获取的源码(这里的算法对我来说太高级了不解释)
final int hash(Object k) {
int h = 0;
if (useAltHashing) {
if (k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h = hashSeed;
}
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);
}
再看下put方法
public V put(K key, V value) {
/*同理于上面得get空值的key,HashMap是允许一个主键为空的*/
if (key == null)
return putForNullKey(value);
/*获取hash码*/
int hash = hash(key);
/*通过算法获取坐标*/
int i = indexFor(hash, table.length);
/*这个Entry我觉得有必要解释一波,这是一个实现了Map.Entrty的链表结构对象,上文也说了Hash底层是数组+链表+红黑树实现的(1.8 才有的红黑树,我这里是1.7的源码)*/
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
/*如果put的key值存在*/
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
/*进行覆盖,然后返回旧的Value*/
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
/*如果key值不存在走这里*/
modCount++;
addEntry(hash, key, value, i);
return null;
}
--源码addEntry
void addEntry(int hash, K key, V value, int bucketIndex) {
/*判断是否需要扩容*/
if ((size >= threshold) && (null != table[bucketIndex])) {
/*每次扩容都会是之前长度X2*/
resize(2 * table.length);
/*获取key值hash码*/
hash = (null != key) ? hash(key) : 0;
/*对hash码进行算法运算以后得到坐标*/
bucketIndex = indexFor(hash, table.length);
}
/*新增*/
createEntry(hash, key, value, bucketIndex);
}
/*这里的代码就是一个新的对象添加到了HashMap的数组中,然后数组长度++*/
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++;
}
今天就到这里吧,有什么问题欢迎指出
外贴上我看的两篇对于HashMap写的比较好的(1.8)帖子以供大家共同学习
大家回见
本文主要探讨了HashMap在Java中的基础概念,包括其无序性、键值对存储方式以及非线程安全性。作者指出,虽然HashMap在JDK1.7中以数组+链表实现,但JDK1.8后引入了红黑树优化。通过分析HashMap的构造函数、get和put方法的源码,展示了HashMap的工作原理。同时,提到了在多线程环境下如何确保HashMap的安全性。
3974

被折叠的 条评论
为什么被折叠?



