什么是HashMap
HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。
public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); // bucket位置 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; // 如果该Key已经存在,会返回之前保存的值 } } modCount++; addEntry(hash, key, value, i); return null; } public V get(Object key) { if (key == null) return getForNullKey(); 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; }
当两个对象的hashcode相同会发生什么?
它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。
如果HashMap的大小超过了负载因子定义容量,怎么办?
默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。
addEntry(hash, key, value, i); 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); if (size++ >= threshold) resize(2 * table.length); } 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]; transfer(newTable); table = newTable; threshold = (int)(newCapacity * loadFactor); }
如果使用的是默认,即直接new HashMap<String, Object>(),那么在put第12个元素时,就会进行resize操作。
static final int DEFAULT_INITIAL_CAPACITY = 16; static final float DEFAULT_LOAD_FACTOR = 0.75f; public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); table = new Entry[DEFAULT_INITIAL_CAPACITY]; init(); }
所以你应该评估自己可能缓存的数量,使用HashMap(int initialCapacity, float loadFactor)构造一个带指定初始容量和加载因子的空 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); // Find a power of 2 >= initialCapacity int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; this.loadFactor = loadFactor; threshold = (int)(capacity * loadFactor); table = new Entry[capacity]; init(); }
重新调整HashMap大小存在什么问题
当重新调整HashMap大小的时候,存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。
上面看到resize会操作table,而操作方法本身和table都是非线程安全。
为什么String, Interger这样的wrapper类适合作为键
String,Interger这样的wrapper类作为HashMap的键是再适合不过了,而且String最为常用。因为String是不可变的,也是final的,而且已经重写了equals()和hashCode()方法了。其他的wrapper类也有这个特点。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。不可变性还有其他的优点如线程安全。如果你可以仅仅通过将某个field声明成final就能保证hashCode是不变的,那么请这么做吧。因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的。如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这样就能提高HashMap的性能。
我们可以使用自定义的对象作为键吗
当然你可能使用任何对象作为键,只要它遵守了equals()和hashCode()方法的定义规则,并且当对象插入到Map中之后将不会再改变了。如果这个自定义对象时不可变的,那么它已经满足了作为键的条件,因为当它创建之后就已经不能改变了。
请您到ITEYE网站看 java小强 原创,谢谢!
http://cuisuqiang.iteye.com/ !
自建博客地址:http://www.javacui.com/ ,内容与ITEYE同步!
相关推荐
在Java编程语言中,`HashMap`是`java.util`包中的一个核心类,它属于集合框架的一部分,主要用于存储键值对的数据结构。`HashMap`基于哈希表(散列表)实现,提供了快速的插入、删除和查找操作,平均时间复杂度为O(1...
在Java编程语言中,集合框架是开发者日常工作中不可或缺的一部分,HashMap作为其中的重要成员,它的实现原理对于理解Java性能优化和数据结构有深远的意义。HashMap是一个基于哈希表的数据结构,它实现了Map接口,...
总的来说,马士兵老师的HashMap学习笔记不仅涵盖了HashMap的基础知识,还深入探讨了其在不同JDK版本中的实现细节,特别是并发环境下的问题和解决方案。理解这些内容对于提升Java编程技能和优化代码性能具有重要价值...
java代码-使用java解决手写hashMap的源代码 ——学习参考资料:仅用于个人学习使用!
Java 中的 HashMap 是一种常用的数据结构,一般用来做数据字典或者 Hash 查找的容器。在初始化并赋初值时,我们通常使用 `HashMap, Object> map = new HashMap();` 的方式初始化,然后使用 `put` 方法添加键值对。...
在Java编程语言中,HashMap是Java集合框架的重要组成部分,它提供了高效的存储和检索对象的能力,实现了"键-值"对的映射。本教程将深入分析2022...因此,深入学习HashMap的内部实现对于Java开发者来说是非常有价值的。
java网络编程学习 简介 用于存放学习java网络编程过程遇见的重难点笔记和相关代码 附带代码大部分是《java网络编程 第四版》的课程代码 对运算字符串进行识别的作业 @since2020.11.26 IO流 - 多线程 @since2020.11....
本文总结了Java集合专题中的HashMap和HashTable,涵盖了它们的源码学习和面试总结。HashMap是一种基于哈希表的集合类,它的存储结构是一个数组,每个元素是一个单链表的头节点,链表用来解决hash地址冲突问题。...
这个压缩包中的示例工程包含了Java和C++的代码,可以作为学习和实践JNI处理不同类型数据的参考。你可以直接运行这些代码,观察其工作原理。同时,提供的参考资料也会帮助你深入理解JNI的工作机制和最佳实践。 总之...
HashMap是Java编程语言中最常用的集合类之一,尤其在面试中,HashMap的相关知识是考察候选人对数据结构和算法理解的重要部分。本套学习资料全面涵盖了HashMap的深入解析,旨在帮助求职者掌握大厂面试中的核心知识点...
Java8之后新增挺多新东西,接下来通过本文给大家介绍Java8 HashMap的实现原理分析,对java8 hashmap实现原理相关知识感兴趣的朋友一起学习吧
在本资源中,我们将学习如何使用 Java 语言实现一个简单的购物车系统,其中使用 HashMap 来存放用户想买的商品信息。下面是该资源中的知识点总结: ConnDB.java ConnDB.java 是一个用于连接数据库的类,该类中...
随着Java学习的不断深入,发现很多知识在脑海里都是一个个碎片,建此仓库的目的希望把零碎的知识点都整合起来,提高自己的学习效率。欢迎志同道合的朋友,一起来维护该仓库 目录 网络基础 WEB TCP协议 HTTP/HTTPS ...
"Java HashMap 详解" Java HashMap 是一种常用的数据结构,它实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度。HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。 HashMap 的...
2. 集合:在13集合.md中,详细讲解了Java集合框架,包括ArrayList、LinkedList、HashSet、HashMap等基本集合类的使用,以及List、Set、Map接口的特性。此外,还可能涉及泛型的概念,泛型(14泛型.md)提高了代码的...
hashmap源码 Excellent-Blog-share Welcome to share excellent blogs . 当你的能力还驾驭不了你的目标时,就应该沉下心来历练 当你的才华还撑不起你的野心时,那你就应该静下心来学习 #目录 ###附录 java系列 java8...
作者目录Java基础Java基础学习(1)——引用Java基础学习(2)——注解Java基础学习(3)——泛型Java基础学习(4)——动态代理《Java多线程核心技术》读书笔记JDK源Java集合框架源码解读(1)——ArrayList、LinkedList和...
在深入学习Java集合时,我们需要特别关注HashMap和HashTable这两个重要的类。虽然它们都是用于存储键值对的数据结构,但它们在设计和使用上有显著的区别。 HashMap是Java 1.2引入的,它是Map接口的一个实现,提供了...
总的来说,学习HashMap的源码能够帮助我们深入理解Java的数据结构和算法,以及如何在实际应用中高效地使用它们。通过分析源码,我们可以了解Java 7 HashMap的设计思想,为解决并发问题提供思路,并为升级到更高级的...