`
DiaoCow
  • 浏览: 244191 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

HashMap源码学习及总结

    博客分类:
  • Java
阅读更多
前言
平时经常使用HashMap,也大致了解它的数据结构,但是一直没有一窥究竟,今天正好有时间看了下源码(确实有很多自己不懂以及可学习的地方),现在简单做些笔记

1.HashMap的数据结构


上图大致描绘了HashMap的数据结构,其基本算法如下:
当我们把一个元素a放入HashMap时,首先获取a的hashCode,然后通过hash算法得到哈希值k,最后把k散列到数组位置i上,如果该位置为空则直接插入,否则遍历这个位置上的链表(若找到相同的节点(equals方法)则更新节点,否则插入)


2.HashMap的构造方法(挑选两个重要的看):
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);

     // capacity(数组容量)总是为2的幂次方,如果你传入的initialCapacity为17,capacity会被初始化为32
     int capacity = 1;
     while (capacity < initialCapacity)            
          capacity <<= 1;

     this.loadFactor = loadFactor;
     // 阀值 = 容量 * 负载因子(当map中元素个数超过阀值时,会自动做一次扩容以及rehash)
     threshold = (int)(capacity * loadFactor);
     table = new Entry[capacity];
     init();
}

public HashMap() {
     // 默认负载因子为0.75
     this.loadFactor = DEFAULT_LOAD_FACTOR;
     // 默认阀值为 12 (16*0.75) 
     threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); 
     // 默认容量为16
     table = new Entry[DEFAULT_INITIAL_CAPACITY]; 
     init();
}


3.HashMap中put方法的实现
public V put(K key, V value) {
     if (key == null)
          return putForNullKey(value);
     // 下面两步通过hash得到key,并散列到数组位置i上
     int hash = hash(key.hashCode());
     int i = indexFor(hash, table.length);
    
     // 循环遍历下标为i的链表
     for (Entry<K,V> e = table[i]; e != null; e = e.next) {
          Object k;
          // 若当前key在链表中存在,则update value
          if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
               V oldValue = e.value;
               e.value = value;
               e.recordAccess(this);
               return oldValue;  // return oldValue
          }
     }

     // 若当前key在链表中不存在,则插入
     modCount++;
     addEntry(hash, key, value, i);
     return null;
}

我们再看下addEntry方法:
void addEntry(int hash, K key, V value, int bucketIndex) {
     Entry<K,V> e = table[bucketIndex];
     // 添加新的节点
     table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
     // 如果此时元素个数达到map的阀值,则resize->rehash
     if (size++ >= threshold)
          resize(2 * table.length);
}

void resize(int newCapacity) {
     Entry[] oldTable = table;
     int oldCapacity = oldTable.length;
     // 若原来数组容量已经为MAXIMUM_CAPACITY,则此次仅仅扩大容量至Integer.MAX_VALUE,而不做rehash
     if (oldCapacity == MAXIMUM_CAPACITY) {
          threshold = Integer.MAX_VALUE;
          return;
     }
    
     Entry[] newTable = new Entry[newCapacity];
     // rehash
     transfer(newTable);
     table = newTable;
     // 重新计算map的阀值
     threshold = (int)(newCapacity * loadFactor);
}

最后看一下传说中的rehash
void transfer(Entry[] newTable) {
     // newTable是新的数组(扩容过),src是老的数组
     Entry[] src = table;
     int newCapacity = newTable.length;
     // 循环每个数组下表所对应的链表做rehash
     for (int j = 0; j < src.length; j++) {
          Entry<K,V> e = src[j];
          if (e != null) {
               src[j] = null;
               do {
                    Entry<K,V> next = e.next;
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i]; // 链表插入总是插在链表头
                    newTable[i] = e;
                    e = next;
               } while (e != null);
          }
     }
}

4.HashMap中的get方法
public V get(Object key) {
     if (key == null)
          return getForNullKey();
     // 通过hash得到key在数组中index,然后循环遍历
     int hash = hash(key.hashCode());
     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)))
               return e.value;
     }
     return null;
}

5.简单总结:
1.capacity:table容量,它的值总是2的幂次方,所以即使你设置初始化容量为17,它的也会自动扩容到32;
2.threshold:HashMap中元素个数阀值,当超过这个阀值的时候,HashMap会自动扩容一倍,然后对其中的元素做一次rehash,所以为了避免HashMap反复的做rehash(这个严重会影响hashmap的性能),我们应该在使用前给出一个合理的初始容量(通常这个又是很难做到的);
3.loadFactor:负载因子,刚才我说了阀值threshold的作用,但是这个值是如何确定的呢,它等于:capacity * loadFactor;
4.hashCode方法与equals方法:通常我们看到一些文章都会说,如果你重写了equals方法一定也要重写hashCode方法,反之亦然,但是为什么要这样呢? 其实我觉的这两个方法本身没有多大的关系,但是因为Set,Map这些集合的关系,这两个方法被紧密的绑定在一起;在Set和Map集合中,当我们判断两个key或者两个元素是否相同的时候,首先比较它们的hashcode值是否相同,若不相同,则判定这两个元素不相同,否则再进行equals方法的比较;若重写了equals方法而未重写hashcode方法,那么即使你认为是相同的元素对于Set和Map来讲也是不同的,并且当你设计一些公用类的时候,你无法预测别人是否会将这些类作为元素放入Set和Map中,所以建议最好重写hashCode和equals方法;
5. 遗留问题:代码中一些java关键字的学习;hash算法;一些内部类譬如EntrySet




  • 大小: 13.9 KB
分享到:
评论

相关推荐

    Java集合专题总结:HashMap 和 HashTable 源码学习和面试总结

    Java集合专题总结:HashMap和HashTable源码学习和面试总结 本文总结了Java集合专题中的HashMap和HashTable,涵盖了它们的源码学习和面试总结。HashMap是一种基于哈希表的集合类,它的存储结构是一个数组,每个元素...

    JAVA之hashmap源码分析-Mobile-Dev-Analysis:android或java的分析

    JAVA之hashmap源码分析 Mobile-Dev-Analysis Analysis of android or java 红岩网校工作站移动开发部学员分组学习 为了让大家学的更加坚固,采取小组学习的方式帮助大家学习,同时在学习研究的过程中需要不断的做...

    java7hashmap源码-learning-record:学习轨迹记录

    hashmap源码 learning-record - 学习轨迹记录 10月1号 7月18号 基础算法:反转单向链表 7月16号 7月11号 7月9号 复习 : HashTable和HashMap的区别详解 LeetCode 27. 删除元素(Remove Element) 7月8号 7月5号 复习...

    电话本管理系统HashMap实现

    总结一下,电话本管理系统使用HashMap实现的优势在于其快速的查找和操作性能。通过理解和运用HashMap的机制,我们可以构建出一个高效、易维护的电话本系统。在实际开发中,了解和掌握HashMap的内部原理对于优化代码...

    HashMap put方法的源码分析

    总结起来,理解HashMap的put方法源码对于优化Java程序中的数据处理至关重要,它揭示了HashMap如何高效地管理数据并处理冲突,这对于开发者来说是非常宝贵的编程知识。通过深入学习这些细节,我们可以更好地利用...

    JSON的学习总结(总结+源码)

    JSONObject是JSON数据结构中的一个核心概念,它代表了键值对的集合,类似于Java中的HashMap。在JSON中,键必须是字符串,而值可以是JSON支持的任何类型,如字符串、数字、布尔值、数组、对象或其他JSON对象。例如: ...

    struts源码学习.pdf

    ### Struts 源码学习知识点详解 #### 一、Struts框架简介 Struts是一个开源的MVC(Model-View-Controller)框架,用于Java Web应用开发。它简化了Web应用程序的开发流程,提供了清晰的结构化设计,使得开发者能够...

    HashMap.zip

    HashMap总结、面试资料!!(2020下半年) 包含:HashMap线程安全 + ConcurrentHashMap + HashMap + 源码分析 + jdk1.8的HashMap和ConcurrentHashMap。 欢迎下载学习

    java8源码-FiveYears:学习/总结/成长/记录

    源码学习 equals 编码规范 注解-未总结 :spider_web:前端 :revolving_hearts:数据结构和算法 CH LFU LRU :woman_biking:网络 :hollow_red_circle:操作系统 Linux :information:数据库 Oracle MYQL 事务 - 未学习 ...

    javajdk源码学习-JavaSourceLearn:JDK源码学习

    总结来说,JDK源码学习是提升Java开发技能的必经之路,"JavaSourceLearn"提供了这样的学习资源和途径。通过系统地学习和实践,开发者不仅能深化对Java语言的理解,还能培养解决问题和创新的能力,这对任何Java开发者...

    JAVA学习总结

    【JAVA学习总结】 Java是一种广泛使用的面向对象的编程语言,由Sun Microsystems(现已被Oracle公司收购)于1995年推出。它的设计目标是具有跨平台性、可移植性、安全性和高效性,使得“一次编写,到处运行”成为...

    免费开源-【Java学习+面试指南】部分内容大部分是Java程序员所需要掌握的核心知识

    ArrayList核心源码+扩容机制分析LinkedList核心源码分析HashMap核心源码+底层数据结构分析ConcurrentHashMap核心源码+底层数据结构分析LinkedHashMap核心源码分析CopyOnWriteArrayList核心源码分析...

    11.集合框架001-Collection接口21-23

    描述中提到的“21-23”可能是指三个视频教程的章节,分别深入分析了HashMap的源码、HashMap的内部结构总结和TreeMap。 首先,Collection接口是Java集合框架的根接口,它定义了对一组对象进行操作的基本方法,如添加...

    【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识

    项目相关 项目介绍 使用建议 贡献指南 常见问题 ...HashMap 核心源码+底层数据结构分析 ConcurrentHashMap 核心源码+底层数据结构分析 LinkedHashMap 核心源码分析 CopyOnWriteArrayList 核心源码分析

    (转)java学习总结

    【标题】:“(转)Java学习总结” 这篇“Java学习总结”显然是一位程序员在学习Java编程语言过程中的心得体会。Java是一种广泛使用的面向对象的编程语言,由Sun Microsystems(现为Oracle公司的一部分)于1995年...

    study1010_bursttpf_java学习_源码.zip

    源码学习对于理解编程语言的工作原理、提高编程技能以及学习最佳实践至关重要。通过阅读和分析源码,开发者可以深入理解类、对象、方法、接口等概念,学习如何组织代码结构,以及如何利用Java库和框架来解决问题。 ...

    java练习源码

    描述中提到“学习java 总结的代码”,这表明这些源码可能是作者在学习Java过程中编写的示例程序或练习项目,对于初学者来说极具参考价值。它们可以帮助初学者理解Java语法、类和对象的概念、异常处理、数据结构、...

    「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识

    HashMap(JDK1.8)源码+底层数据结构分析 ConcurrentHashMap 源码+底层数据结构分析 IO IO 基础知识总结 IO 设计模式总结 IO 模型详解 并发 知识点/面试题总结 : (必看 ) Java 并发常见知识点&面试题总结(上) Java ...

    「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识 准备 Java 面试,首选.zip

    HashMap(JDK1.8)源码+底层数据结构分析 ConcurrentHashMap 源码+底层数据结构分析 IO IO 基础知识总结 IO 设计模式总结 IO 模型详解 并发 知识点/面试题总结 : (必看 ) Java 并发常见知识点&面试题总结(上) Java ...

    Java相关技术总结,包括redis,MySQL,RabbitMq,面试题总结,源码解读

    面试题总结部分,Java开发者应该掌握的基本概念包括:多线程(线程安全、同步机制)、集合框架(List、Set、Map的区别与选择)、异常处理、设计模式(单例、工厂、装饰器等)、JVM内存模型以及垃圾回收机制。...

Global site tag (gtag.js) - Google Analytics