热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

javaHashMap源码简析(1.8)

1、HashMap的key、value都可以为null,映射不是有序的,并且不保证该序列恒久不变。2、HashMap是线程不安全的——
1、HashMap的 key、value都可以为null, 映射不是有序的,并且不保证该序列恒久不变。

2、HashMap是线程不安全的——
HashMap不是同步的,通过Collections类的静态方法synchronizedMap——
public static Map synchronizedMap ( Map  m)
返回由指定映射支持的同步(线程安全的)映射。

3、HashMap数据结构
HashMap底层是基于 数组和链表 来实现的。
查询速度快的原因:通过计算散列码来决定存储位置。
计算hash值 :通过 key的HashCode 来计算。(只要HashCode相同,hash值就相同)
hash冲突:不同对象所算出的hash值可能是相同的。
HashMap 底层如何解决hash冲突:链表
图解:


4、初始化容量为什么为16?
首先,假设HashMap的长度是10
hashcode : 101110001110101110 1001
length - 1 : 1001
index : 1001

再换一个hashcode 101110001110101110 1111 试试:
hashcode : 101110001110101110 1111
length - 1 : 1001
index : 1001

从结果可以看出,虽然 hashcode变化了,但是运算的结果都是一样 ,为1001,也就是说,当HashMap长度为10的时候,有些index结果的出现几率会更大,而有些index结果永远不会出现(比如0111),这样就 不符合hash均匀分布的原则

反观 长度16或者其他2的幂,length - 1的值是所有二进制位全为1 ,这种情况下, index的结果等同于hashcode后几位的值,只要输入的hashcode本身分布均匀,hash算法的结果就是均匀的

所以,HashMap的默认长度为16,是为了降低hash碰撞的几率


5、加载因子:
static final float  DEFAULT_LOAD_FACTOR  = 0.75f; 
为什么是0.75?
若:加载因子越大,填满的元素越多,好处是空间利用率高了,但冲突的机会加大了.链表长度会越来越长,查找效率降低。
反之,加载因子越小,填满的元素越少,好处是冲突的机会减小了,但空间浪费多了.表中的数据将过于稀疏(很多空间还没用,就开始扩容了)

冲突的机会越大,则查找的成本越高
因此——必须在"冲突的机会"与]空间利用率"之间寻找一种平衡。
如果机器内存足够,并且想要提高查询速度的话可以将加载因子设置小一点;相反如果机器内存紧张,并且对查询速度没有什么要求的话可以将加载因子设置大一点。
一般我们都不用去设置它,用默认值0.75即可。


为什么加载因子的默认值为0.75?
泊松分布—— 在理想情况下,使用随机哈希码,节点出现的频率在hash桶中遵循泊松分布。
用0.75作为加载因子的值的时候,桶内元素的个数和概率对照表:
  • 0: 0.60653066
  • 1: 0.30326533
  • 2: 0.07581633
  • 3: 0.01263606
  • 4: 0.00157952
  • 5: 0.00015795
  • 6: 0.00001316
  • 7: 0.00000094
  • 8: 0.00000006

上面的对照表显示:当加载因子为0.75时,桶中的元素个数达到8个的概率已经很小了;
即表明—— 0.75作为hashmap的加载因子的时候,每个碰撞位置的链表长度几乎都在8个
以下了


6、HashMap 中关于红黑树的三个关键参数
HashMap 中有三个关于红黑树的重要参数:
1、 static final int TREEIFY_THRESHOLD = 8;
一个桶的树化阈值:当桶中元素个数超过8的时候,需要使用红黑树节点替换链表节点

2、 static final int UNTREEIFY_THRESHOLD = 6; 一个树的链表还原阈值:当扩容时,桶中元素个数小于等于6的时候,就会把树形的桶元素 还原(切分)为链表结构 3、 static final int MIN_TREEIFY_CAPACITY = 64; 哈希表的最小树形化容量:当哈希表中的容量大于这个值时,表中的桶才能进行树形化

注:桶内元素太多,且容量小于64时,会扩容,而不是树形化


7 、为什么哈希表的容量一定要是2的整数次幂?
首先,h&(length-1)就相当于对length取模,这样便保证了散列的均匀,同时也提升了效率;
其次,length为 2的整数次幂的话,为偶数 ,这样length-1为奇数,奇数的最后一位是1,这样便 保证了h&(length-1)的最后一位可能为0,也可能为1 (这取决于h的值), 即与后的结果可能为偶数,也可能为奇数, 这样便可以 保证散列的均匀性
(如果length为 奇数 的话,很明显length-1为偶数,它的最后一位是0,这样 h&(length-1)的最后一位肯定为0,即只能为偶数 ,这样任何hash值都只会被散列到数组的偶数下标位置上,这便 浪费了近一半的空间

所以,length取2的整数次幂,是为了使不同hash值发生碰撞的概率较小,这样就能使元素在哈希表中均匀地散列。


8、HashMap的扩容(resize)机制:
为什么要扩容(重新计算容量): 在向HashMap对象里不停的添加元素,而HashMap对象内部的数组无法装载更多的元素时———对象就需要扩大数组的长度,以便能装入更多的元素。当然 Java里的数组是无法自动扩容的 ,方法是 使用一个新的数组代替已有的容量小的数组
代码解析:
//传入新的容量
void resize(int newCapacity) {
//引用扩容前的Entry数组 Entry[] oldTable = table; int oldCapacity = oldTable.length;
//扩容前的数组大小如果已经达到最大(2^30)了 if (oldCapacity == MAXIMUM_CAPACITY) {
//修改阈值为int的最大值(2^31-1),这样以后就不会扩容了 threshold = Integer.MAX_VALUE; return; }
//初始化一个新的Entry数组 Entry[] newTable = new Entry[newCapacity];
//将数据转移到新的Entry数组里 transfer (newTable);
//HashMap的table属性引用新的Entry数组 table = newTable;
//修改阈值 threshold = (int)(newCapacity * loadFactor);//修改阈值 }

transfer()方法: 将原有Entry数组的元素拷贝到新的Entry数组里。
注意: 要重新计算每个元素在数组中的位置(hash值)
遍历顺序:先遍历到哈希表的第一个元素,然后依次遍历以这个元
素为头结点的单链表中的每个元素。接着遍历哈希表中第二个元素……
重复该过程。

9、HashMap存取时,要计算当前key应该对应Entry[]数组哪个元素,即计算
数组下标,代码如下:
     static   int   indexFor ( int  h,  int  length) {
         return  h & (length-1);
    }

我们一般对 哈希表的散列 很自然地会想到用 hash值对length取模 (即除法散列法) Hashtable 中也是这样实现的 这种方法基本能保证 元素在哈希表中散列的比较均匀 ,但取模会用到 除法运算,效率很低 HashMap中则通过h&(length-1)的方法来代替取模 ,同样实现了均匀的散列,但效率要高很多 ,这也是HashMap对Hashtable的一个改进。

效率更高的原因 位运算 直接 对内存数据进行操作,不需要转化为十进制,处理速度更快。
 

10、HashMap和HashTable中对于hash的实现的总结——
HashMap默认的初始化大小为16 ,之后每次扩充为原来的 2倍
HashTable默认的初始大小为11 ,之后每次扩充为原来的 2n+1倍

原因:
HashTable选择取模运算: 当哈希表的大小为素数时,简单的取模哈希的结果会更加
均匀,hash结果越分散效果越好。
HashMap选择取模运算: 在取模计算时,如果模数是2的幂,那么我们可以直接使用
位运算来得到结果,运算效率要大大高于做取模。
但是,HashMap为了提高效率使用位运算代替哈希——引入了哈希分布不均匀的问题,所以HashMap为解决这问题,又对hash算法做了一些改进,进行了扰动计算。


11、扰动运算
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);return h ^ (h >>> 7) ^ (h >>> 4);
对key的hashCode进行扰动计算,防止不同hashCode的高位不同但低位相同导致的hash冲突。
简单来说, 就是为了把高位的特征和低位的特征组合起来,降低哈希冲突的概率,也就是说,尽量做到任何一位的变化都能对最终得到的结果产生影响。


12、HashMap共有4个构造函数:
①HashMap()
构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。
②HashMap(int initialCapacity)
构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。

③HashMap(int initialCapacity, float loadFactor)
构造一个带指定初始容量和加载因子的空HashMap。

④HashMap(Map m)
构造一个映射关系与指定 Map 相同的新 HashMap。


9、put方法 将“key-value”添加到HashMap中)
    public V put(K key, V value) {
// 若“key为null”,则将该键值对添加到哈希表的下标0的位置中。
        if (key == null)
            return putForNullKey(value);
//若“key不为null”,计算该key的哈希值,然后将其添加到该哈希值对应的链表中。
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry e = table[i]; e != null; e = e.next) {
            Object k;
// 若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
// 若“该key”对应的键值对不存在,则将“key-value”添加到哈希表中
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

10、get方法 获取key对应的value)
    public V get(Object key) {
        if (key == null)
            return getForNullKey();
// 获取key的hash值
        int hash = hash(key.hashCode());
// 在“该hash值对应的链表”上查找“键值等于key”的元素
        for (Entry 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;
    }

11、fail-fast
fail-fast: java.util.HashMap不是线程安全的,因此如果 在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException ,这就是所谓fail-fast策略。

实现及原理: 这一策略在源码中的实现是通过 modCount域 ,modCount顾名思义就是 修改次数 对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。

 由所有HashMap类的“collection 视图方法”所返回的迭代器都是快速失败的:在迭代器创建之后,如果从结构上对映射进行修改, 除非通过迭代器本身的 remove 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。








推荐阅读
  • HashTable与ConcurrentHashMap均可实现HashMap的功能,对外提供了键值对存储的数据结构。但是在内部结构及实现上有何区别,性能上的差异到底在哪里又是如何导致的 ... [详细]
  • 缓存这个东西就是为了提高运行速度的,由于缓存是在寸土寸金的内存里面,不是在硬盘里面,所以容量是很有限的。LRU这个算法就是把最近一次使用时间离现在时间最远的数据删除掉。先说说List:每 ... [详细]
  • 本文探讨了 Java 中 Pair 类的历史与现状。虽然 Java 标准库中没有内置的 Pair 类,但社区和第三方库提供了多种实现方式,如 Apache Commons 的 Pair 类和 JavaFX 的 javafx.util.Pair 类。这些实现为需要处理成对数据的开发者提供了便利。此外,文章还讨论了为何标准库未包含 Pair 类的原因,以及在现代 Java 开发中使用 Pair 类的最佳实践。 ... [详细]
  • 本文介绍了如何利用ObjectMapper实现JSON与JavaBean之间的高效转换。ObjectMapper是Jackson库的核心组件,能够便捷地将Java对象序列化为JSON格式,并支持从JSON、XML以及文件等多种数据源反序列化为Java对象。此外,还探讨了在实际应用中如何优化转换性能,以提升系统整体效率。 ... [详细]
  • 转载自:http:www.blogjava.netCarpenterLeearchive20160427430268.html总体介绍之所以把HashSet和HashMa ... [详细]
  • 01Map集合概述A:Map集合概述:我们通过查看Map接口描述,发现Map接口下的集合与Collection接口下的集合,它们存储数据的形式不同a:Collection中的集合 ... [详细]
  • 手写HashMap,快手面试官直呼内行
    手写HashMap,快手面试官直呼内行-手写HashMap?这么狠,面试都卷到这种程度了?第一次见到这个面试题,是在某个不方便透露姓名的Offer收割机大佬的文章:这……我当 ... [详细]
  • 使用Maven JAR插件将单个或多个文件及其依赖项合并为一个可引用的JAR包
    本文介绍了如何利用Maven中的maven-assembly-plugin插件将单个或多个Java文件及其依赖项打包成一个可引用的JAR文件。首先,需要创建一个新的Maven项目,并将待打包的Java文件复制到该项目中。通过配置maven-assembly-plugin,可以实现将所有文件及其依赖项合并为一个独立的JAR包,方便在其他项目中引用和使用。此外,该方法还支持自定义装配描述符,以满足不同场景下的需求。 ... [详细]
  • Java中不同类型的常量池(字符串常量池、Class常量池和运行时常量池)的对比与关联分析
    在研究Java虚拟机的过程中,笔者发现存在多种类型的常量池,包括字符串常量池、Class常量池和运行时常量池。通过查阅CSDN、博客园等相关资料,对这些常量池的特性、用途及其相互关系进行了详细探讨。本文将深入分析这三种常量池的差异与联系,帮助读者更好地理解Java虚拟机的内部机制。 ... [详细]
  • 本指南从零开始介绍Scala编程语言的基础知识,重点讲解了Scala解释器REPL(读取-求值-打印-循环)的使用方法。REPL是Scala开发中的重要工具,能够帮助初学者快速理解和实践Scala的基本语法和特性。通过详细的示例和练习,读者将能够熟练掌握Scala的基础概念和编程技巧。 ... [详细]
  • 本篇文章给大家分享的是有关Java中怎么对HashMap按键值排序,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话 ... [详细]
  • 将学生对象和学生的归属地通过键与值存储到map集合中。importjava.util.HashMap;importjava.util.Iterator;importjava.uti ... [详细]
  • 本文将继续探讨 JavaScript 函数式编程的高级技巧及其实际应用。通过一个具体的寻路算法示例,我们将深入分析如何利用函数式编程的思想解决复杂问题。示例中,节点之间的连线代表路径,连线上的数字表示两点间的距离。我们将详细讲解如何通过递归和高阶函数等技术实现高效的寻路算法。 ... [详细]
  • 在Java编程中,`String`对象既可以用作对象,也可以用作基本类型。本文深入解析了`String`对象中`equals`方法与`==`运算符的区别及其应用场景。`equals`方法用于比较两个字符串的内容是否相同,而`==`运算符则用于比较两个字符串对象的引用是否相同。通过具体示例和代码片段,文章详细阐述了这两种比较方式的内在机制和适用场景,帮助开发者更好地理解和使用`String`对象的比较操作。 ... [详细]
  • ***功能:排序*privatestaticvoidoutputRegionStatistics(HashMap<String,Integer>regionMap){ ... [详细]
author-avatar
月夜清风XL
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有