一.HashMap的存储结构
二.HashMap成员变量
- //默认初始容量,总为2的次方值
- static final int DEFAULT_INITIAL_CAPACITY = 16;
- //最大容量
- static final int MAXIMUM_CAPACITY = 1 << 30;
- //默认加载因子
- static final float DEFAULT_LOAD_FACTOR = 0.75f;
- //Entry数组,每一个Entry是一个键值对实体
- transient Entry[] table;
- //实际存的Entry个数
- transient int size;
- //数组扩容的阀值,当size+1 > threshold时,扩充到以前容量的两倍
- //threshold = table.length * loadFactor
- int threshold;
- //负载比率
- final float loadFactor;
- //Map结构一旦变化,如put remove clear等操作的时候,modCount随之变化
- transient volatile int modCount;
三.Entry对象
- //很简单的一个键值对实体而已
- static class Entry<K,V> implements Map.Entry<K,V> {
- final K key; //key
- V value; //value
- Entry<K,V> next; //next Entry
- final int hash; //计算出key的hash值
- /**
- * Creates new entry.
- */
- Entry(int h, K k, V v, Entry<K,V> n) {
- value = v;
- next = n;
- key = k;
- hash = h;
- }
- .....
- }
四.构造函数
- // 构造函数
- public HashMap(int initialCapacity, float loadFactor) {
- if (initialCapacity < 0)
- throw new IllegalArgumentException("Illegal initial capacity: "
- + initialCapacity);
- if (initialCapacity > MAXIMUM_CAPACITY)
- initialCapacity = MAXIMUM_CAPACITY;
- if (loadFactor <= 0 || Float.isNaN(loadFactor))
- throw new IllegalArgumentException("Illegal load factor: "
- + loadFactor);
- // 将传入的initialCapacity值,转变成2的次方值capacity去实例化hashmap的属性
- // 比喻传入initialCapacity = 100,则算出来capacity = 2 << 7 = 128,
- // 最终threshold = 128 * 0.75 = 96,table = new Entry[128]
- int capacity = 1;
- while (capacity < initialCapacity)
- capacity <<= 1; //左移,相当于capacity*2
- this.loadFactor = loadFactor;
- threshold = (int) (capacity * loadFactor);
- table = new Entry[capacity];
- // 模板方法模式,子类想在init里面做点什么重写init就好了
- init();
- }
五.hash算法和index算法
(帮助理解hash算法:http://692088846.iteye.com/admin/blogs/2075638)
- /**
- * 让hashMap里面的元素尽量分布均需,方便查找
- * @param h entry中key的hash值
- * @return 打散后的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);
- }
- /**
- * 类似求模运算, 将hash值同length-1(必定是01111...1)相与,运算的结果处于1到length-1间,0刚好用来保存key为null和0的元素
- * @param h 打散后的hash值
- * @param length 数组的长度
- * @return 数组下标 1到lenght-1
- */
- static int indexFor(int h, int length) {
- return h & (length-1);
- }
六.取数据
- public V get(Object key) {
- if (key == null)
- return getForNullKey();
- //key != null时
- //1.根据key计算出hash和index
- int hash = hash(key.hashCode());
- //2.从index处的链表头Entry e开始遍历链表,
- //如果e满足hash(key.hashCode()) == e.hash && (key == e.key || key.equals(e.key)),就是我们要找的entry
- 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.equals(k))) //注释3里的逻辑更好理解
- return e.value;
- }
- return null;
- }
- //如果key为空,直接从table[0]链表里面遍历寻找value
- private V getForNullKey() {
- for (Entry<K,V> e = table[0]; e != null; e = e.next) {
- if (e.key == null)
- return e.value;
- }
- return null;
- }
七.存数据,扩容
- public V put(K key, V value) {
- //1.key为空时候,调用putForNullKey方法
- if (key == null)
- return putForNullKey(value);
- //2.key不为空
- //2-1.计算hash和index
- int hash = hash(key.hashCode());
- int i = indexFor(hash, table.length);
- //2-2.根据index得到当前链表头e=table[i]不为空
- for (Entry<K,V> e = table[i]; e != null; e = e.next) {
- Object k;
- //2-3.如果e满足hash(key.hashCode())=e.hash && key==e.key || key.equals(e.key),value覆盖oldValue
- if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
- V oldValue = e.value;
- e.value = value;
- e.recordAccess(this);
- return oldValue;
- }
- }
- //2-4.table[i]为空,直接插入entry
- modCount++;
- addEntry(hash, key, value, i);
- return null;
- }
- private V putForNullKey(V value) {
- //1.从0位置的链表头开始,遍历
- for (Entry<K,V> e = table[0]; e != null; e = e.next) {
- //1-1.e.key==null时,直接替代value
- if (e.key == null) {
- V oldValue = e.value;
- e.value = value;
- e.recordAccess(this);
- return oldValue;
- }
- }
- //2.头Entry不存在时,直接插入entry
- modCount++;
- addEntry(0, null, value, 0);
- return null;
- }
- /**
- * @param hash 计算后key的hash值
- * @param key
- * @param value
- * @param bucketIndex 应该插入到Entry[]的哪个位置
- */
- void addEntry(int hash, K key, V value, int bucketIndex) {
- //1.当前bucketIndex位置的头Entry e
- Entry<K,V> e = table[bucketIndex];
- //2.在bucketIndex位置新建一个Entry,新建Entry.next=原头Entry,也就是addEntry的Entry都被加到了链表头
- table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
- //hashmap在每次addEntry完后检查是否扩容,而list是在每次加入之前检查:ensureCapacity(size + 1);elementData[size++] = e;
- if (size++ >= threshold)
- resize(2 * table.length);
- }
- //扩容,把hashmap的容量设置为新容量newCapacity
- void resize(int newCapacity) {
- Entry[] oldTable = table;
- int oldCapacity = oldTable.length;
- //每次扩到到原来的两倍,总有一个时候到MAXIMUM_CAPACITY
- if (oldCapacity == MAXIMUM_CAPACITY) {
- //到了MAXIMUM_CAPACITY设置threshold后直接return
- threshold = Integer.MAX_VALUE;
- return;
- }
- //1.新建newCapacity大小的Entry数组
- Entry[] newTable = new Entry[newCapacity];
- //2.把当前Entry的数据全部移到新Entry数组中
- transfer(newTable);
- //3.用已经就绪的新Entry数组重新给table赋值
- table = newTable;
- //4.设置新的threshold
- threshold = (int)(newCapacity * loadFactor);
- }
- void transfer(Entry[] newTable) {
- Entry[] src = table;
- int newCapacity = newTable.length;
- //1.从0开始遍历原Entry数组
- for (int j = 0; j < src.length; j++) {
- //2.拿到下标为j的链表头Entry e
- Entry<K,V> e = src[j];
- //3.遍历链表
- if (e != null) {
- src[j] = null;
- do {
- //3-1.e的下一个Entry,为了继续遍历做准备
- Entry<K,V> next = e.next;
- //3-2.计算e应该存放在新Entry数组的位置下标
- int i = indexFor(e.hash, newCapacity);
- //3-3.将e插入到新Entry[i]链表的表头
- e.next = newTable[i];
- //3-4.将Entry[i]表头的Entry重新定位为e,这样就完成了一个元素的重新hash
- newTable[i] = e;
- //3-5.继续链表的下一个Entry
- e = next;
- } while (e != null);
- }
- }
- }
八.删数据
- public V remove(Object key) {
- Entry<K, V> e = removeEntryForKey(key);
- return (e == null ? null : e.value);
- }
- /**
- * Removes and returns the entry associated with the specified key in the
- * HashMap. Returns null if the HashMap contains no mapping for this key.
- */
- final Entry<K, V> removeEntryForKey(Object key) {
- // 1.计算hash和index
- 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;
- // 2.遍历i位置的Entry链表
- while (e != null) {
- Entry<K, V> next = e.next;
- Object k;
- // 2-1.e满足hash(key.hashCode()) == e.hash && (key == e.key ||
- // key.equals(e.key)),找到了要移除的Entry
- if (e.hash == hash
- && ((k = e.key) == key || (key != null && key.equals(k)))) {
- // 2-2.移除操作modCount++, size--
- modCount++;
- size--;
- // 2-3-1.如果prev和e相等,说明要删除的Entry是链表头,直接将table[i]位置的Entry设置为next,就删除了e
- if (prev == e)
- table[i] = next;
- else
- // 2-3-2.如果不相等e =
- // prev.next,说明要删除的Entry是除链表头外的其他Entry,将prev.next设置为next,就删除了e
- prev.next = next;
- e.recordRemoval(this);
- return e;
- }
- // 把prev和e到移到下一个
- prev = e;
- e = next;
- }
- return e;
- }
- /**
- * 还是将o的key拿到,然后和removeEntryForKey(Object key)一样了
- *
- * @param o
- * must be Map.Entry
- * @return
- */
- final Entry<K, V> removeMapping(Object o) {
- if (!(o instanceof Map.Entry))
- return null;
- Map.Entry<K, V> entry = (Map.Entry<K, V>) o;
- Object key = entry.getKey();
- ......
- }
九.对table[0]位置存放数据的理解
- package com.zzy.collection2;
- import java.util.Map;
- public class TestHashMapEntry0 {
- public static void main(String[] args) {
- Map<Integer, Integer> map = new HashMap<Integer, Integer>();
- map.put(null, 1);
- map.put(null, 2);
- map.put(0, 3);
- map.put(0, 4);
- }
- }
- put(null, value)其本质是addEntry(0, null, value, 0);
- put(0, value)其本质是addEntry(0, 0, value, 0);
- put(null, value1)后put(0, value2)都是向table[0]链表中处添加Entry,但 0 != null,所有value2不会覆盖value1。
- put(null, value1)后再次put(null, value2),value2覆盖value1。
相关推荐
在Java编程语言中,集合框架是开发者日常工作中不可或缺的一部分,HashMap作为其中的重要成员,它的实现原理对于理解Java性能优化和数据结构有深远的意义。HashMap是一个基于哈希表的数据结构,它实现了Map接口,...
#### 二、HashMap线程安全问题分析 在多线程环境中,`HashMap`的主要线程安全问题包括但不限于: 1. **链表死循环问题**:在JDK 1.7中,当多个线程同时进行`put`操作时,可能会出现链表死循环的情况,这是一个严重...
### Java集合容器知识点详解 #### 一、集合容器概述 - **定义**:集合容器是Java平台提供的标准组件,主要用于存储对象。...通过本篇文章的学习,希望读者能够更好地理解和应用Java集合容器,提升编程技能。
JDK 8.0中,HashMap的底层结构仍然是一个数组,默认长度为16,数组的每个元素可以看作是一个链表的头节点。当链表长度达到8,并且数组长度不小于64时,链表会转换成红黑树,以提高搜索效率。 3. HashMap内部实现...
Java学习笔记,内容包括JVM,spring,hashMap实现源码分析,多线程,剑指offer题解,设计模式。然后根据面试的重点,又将很多从里面抽出,专门整了个面试的分类,如果是看面试的东西的话,可以重点看这个。 | 书籍 |...
Java集合专题总结:HashMap和HashTable源码...本文总结了HashMap和HashTable的源码学习和面试总结,涵盖了它们的存储结构、构造方法、get和put方法的源码分析、Hash表的特点和缺点、HashTable和HashMap的区别等内容。
HashMap采用的存储结构是“数组+链表”的形式,其底层以一个固定长度的数组实现,数组的每一个元素是一个链表的头结点。这样的设计既结合了数组易于寻址的优点,又发挥了链表插入和删除操作方便的长处。 数组长度的...
"学习笔记:三数组实现的HashMap"这篇博客文章可能讨论了一种非标准但有趣的HashMap实现,使用了三个数组来代替标准的哈希表结构。这里我们将深入探讨这种三数组实现HashMap的原理和可能的优势。 1. **基本概念**:...
HashMap 是 Java 中最常用的集合类之一,它是基于哈希表数据结构实现的,提供快速的存取操作。在深入理解 HashMap 的实现原理之前,我们先要明白哈希表的基本概念。哈希表是一种通过哈希函数将键(Key)映射到数组...
如果多个线程同时访问一个 `HashMap` 实例,而其中至少一个线程修改了该 `HashMap` 结构,则必须保持外部同步。 - **HashTable**:是线程安全的,即同步的。它的所有公共方法都是 `synchronized` 的,这意味着可以...
### 一、集合容器概述 #### 1. 什么是集合? 集合(Collection)是指在Java中用来存储、检索及操作一组对象的一种容器。它是一种高级的数据结构,主要用于管理一组对象,而不仅仅是简单地存储这些对象。 #### 2. ...
Java集合框架是Java编程中不可或缺的一部分,它提供了多种数据结构来存储和管理对象。面试中常常会涉及到关于集合的深入理解,包括其接口、实现、数据结构以及相关算法。以下是对这些知识点的详细解释: 1. **集合...
总的来说,学习HashMap的源码能够帮助我们深入理解Java的数据结构和算法,以及如何在实际应用中高效地使用它们。通过分析源码,我们可以了解Java 7 HashMap的设计思想,为解决并发问题提供思路,并为升级到更高级的...
- **集合框架**:Java的ArrayList, LinkedList, HashMap等容器类的使用会在源码中体现,帮助理解数据存储和操作。 - **泛型**:源码将展示如何使用泛型编写类型安全的代码,减少类型转换的麻烦。 - **枚举**:...
2. **HashMap**:`HashMap` 则是在 Java 1.2 版本中引入的,它是 `Map` 接口的一个具体实现类。相比于 `Hashtable`,`HashMap` 在设计上更为灵活,并且支持更高的性能。 #### 二、同步性 - **Hashtable**:所有的...
- **HashMap**:允许一个键为空,多个值为空。 3. **遍历方式**: - **HashTable**:使用 `Enumeration`。 - **HashMap**:使用 `Iterator`。 4. **容量扩展**: - **HashTable**:初始容量为 11,扩容时采用 ...
JAVA之hashmap源码分析 Mobile-Dev-Analysis Analysis of android or java 红岩网校工作站移动开发部学员分组学习 为了让大家学的更加坚固,采取小组学习的方式帮助大家学习,同时在学习研究的过程中需要不断的做...
通过视频资源“20-集合框架020-HashMap-1080P 高清-AVC.mp4”,你可以更直观地学习和掌握HashMap的详细知识。这个高清视频可能包含了HashMap的实例演示、源码分析以及常见问题解答,有助于深入理解和应用HashMap。
通过以上分析,我们可以了解到实现一个自定义的HashMap涉及许多细节和技巧,包括哈希函数的设计、冲突解决策略的选择、以及性能优化等。在实际编程中,理解这些概念有助于我们更好地使用和定制HashMap,满足特定需求...