扩容是元素还是数组_Map扩容源码

首先我们运行一段代码:

7f119e79d40d41891e863d7c787f7d0e.png

此时运行,程序正常,接下来我们将注释放开:

75bd4691dd443b68454c1b3172f7471b.png

此时运行发现,OOM了:

6f1d5dfdaa0a733063d82febbcd6562a.png

为什么new出来HashMap的时候并没有报OOM,而是在第一次进行put操作的时候才报的OOM?我们来看下map的源码。

设置数组长度、扩容阈值

创建Map时可以指定元素个数,也可以不指定。先来看下不指定元素个数的构造方法:

public HashMap() {    this.loadFactor = DEFAULT_LOAD_FACTOR; // 设置加载因子为0.75}static final float DEFAULT_LOAD_FACTOR = 0.75f;

当进行put操作时,会进行resize扩容操作:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,               boolean evict) {    Node[] tab; Node p; int n, i;    if ((tab = table) == null || (n = tab.length) == 0) //初始table为空        n = (tab = resize()).length;    //触发resize操作    //省略...    }    //省略...}transient Node[] table;

看下resize扩容的核心代码:

final Node[] resize() {    Node[] oldTab = table;    //初始table为空    int oldCap = (oldTab == null) ? 0 : oldTab.length;    //所以oldCap为0    int oldThr = threshold;    //初始threshold为0 所以oldThr为0    int newCap, newThr = 0;    if (oldCap > 0) {        //省略...    }    else if (oldThr > 0)         //省略...    else {                       newCap = DEFAULT_INITIAL_CAPACITY;    //设置新数组长度为16        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);  //设置新map可容纳元素个数为16*0.75    }    if (newThr == 0) {        //省略...    }    threshold = newThr;    //为threshold赋值为newThr 代表扩容后的map的可容纳元素个数上限 当map中元素个数超过threshold会触发扩容操作    @SuppressWarnings({"rawtypes","unchecked"})        Node[] newTab = (Node[])new Node[newCap];    table = newTab;    //设置table为扩容后的新数组    //省略...    return newTab;}static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

如果指定元素个数,例如:

Map<String,String> map = new HashMap<String,String>(17);
public HashMap(int initialCapacity) {    this(initialCapacity, DEFAULT_LOAD_FACTOR);    //加载因子0.75}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);    this.loadFactor = loadFactor;    //加载因子0.75    this.threshold = tableSizeFor(initialCapacity);    //设置threshold为最接近initialCapacity的2次幂的值}static final float DEFAULT_LOAD_FACTOR = 0.75f;

调用tableSizeFor方法为threshold赋值,看下tableSizeFor的代码:

static final int tableSizeFor(int cap) {    int n = cap - 1;    n |= n >>> 1;    n |= n >>> 2;    n |= n >>> 4;    n |= n >>> 8;    n |= n >>> 16;    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;}

这个函数是用来对你申请的容量进行处理让他变成最接近你申请的容量的2次幂的大小,这里注意:假如你申请的容量为0,最后处理的结果会变成1,代表着你最小的容量为1。

我们自己测试一下,tableSizeFor(17)=32:

public static void main(String[] args) {    int number = 17;    System.out.println(tableSizeFor(number)); //32}public static int tableSizeFor(int number) {    int n = number - 1;    n |= n >>> 1;    n |= n >>> 2;    n |= n >>> 4;    n |= n >>> 8;    n |= n >>> 16;    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;}

补充:

算术右移(>>)

右移是按最左边(高位)来补的(即如果是1就补1,如果是0就补0,不改变该位的值)。另一种说法,正数右移高位补0,负数右移高位补1。

逻辑右移(>>>)

不管最左边一位是0还是1,都补0。另一种说法,无论是正数还是负数,高位通通补0。

同样的,当put元素时会触发map的扩容操作。

接下来看下指定元素个数时,Map扩容的核心源码。

final Node[] resize() {    Node[] oldTab = table;    int oldCap = (oldTab == null) ? 0 : oldTab.length; //初始table为空 oldCap=0    int oldThr = threshold;     //此时threshold已经有值(2的n次幂)    int newCap, newThr = 0;    if (oldCap > 0) {        //省略...    }    else if (oldThr > 0)         newCap = oldThr;    //扩容后map的数组长度    else {                       省略...    }    if (newThr == 0) {        float ft = (float)newCap * loadFactor;  //设置扩容后map的threshold        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?                  (int)ft : Integer.MAX_VALUE);    }    threshold = newThr;     @SuppressWarnings({"rawtypes","unchecked"})    Node[] newTab = (Node[])new Node[newCap];    table = newTab;    //省略...    return newTab;}

总结一下,如果没有指定元素个数,则扩容后的数组长度默认为16,如果指定元素个数,则扩容后的数组长度为与指定的元素个数最接近的2次幂的值,比如指定元素个数为17,则数组长度为32。threshold的取值都是固定数组长度*加载因子(0.75,0.75并不是一个绝对的,只是在时间和空间上可能map的效率最高)。

再回到一开始的问题,new的时候没有OOM但是在put的时候却OOM了,因为在new的时候只是设置threshold值,而且设置的值还是比最大整数大的2次幂,在扩容的时候,需要分配数组内存,所以OOM了。

数据迁移

上面其实只是分析了resize操作关于设置数组长度、扩容阈值的代码,真正扩容后数据迁移都省略了,接下来看下数据迁移部分:

final Node[] resize() {    //省略...    if (oldTab != null) {        for (int j = 0; j < oldCap; ++j) {    //遍历老数组            Node e;            if ((e = oldTab[j]) != null) {                oldTab[j] = null;                if (e.next == null)                    newTab[e.hash & (newCap - 1)] = e;    //如果老数组的某一索引位置没有链表,则将计算该索引位置的元素在新数组的索引位置                else if (e instanceof TreeNode)                    ((TreeNode)e).split(this, newTab, j, oldCap);                    else {     //如果索引位置有链表                    Node loHead = null, loTail = null;                    Node hiHead = null, hiTail = null;                    Node next;                    do {                        next = e.next;                        if ((e.hash & oldCap) == 0) {    //将索引位置的链表拆分成loHead->loTail和hiHead->hiTail两个链表,顺序保持不变                            if (loTail == null)                                loHead = e;                            else                                loTail.next = e;    //尾插法                            loTail = e;                        }                        else {                            if (hiTail == null)                                hiHead = e;                            else                                hiTail.next = e;                            hiTail = e;                        }                    } while ((e = next) != null);                    if (loTail != null) {                        loTail.next = null;                        newTab[j] = loHead;    //新数组索引位置放入loHead链表                    }                    if (hiTail != null) {                        hiTail.next = null;                        newTab[j + oldCap] = hiHead;    //新数组索引位置+原始数组长度位置放入hiHead链表                    }                }            }        }    }    return newTab;}

举例说明下e.hash&oldCap==0来区分该放到loHead还是hiHead链表,下面是我的理解:

0 1 0 0 0  8    //假设原数组长度8 即oldCap=80 0 0 1 1  3  j=0    //假设原数组0索引位置存在3 6 12 三个数组成的链表0 0 1 1 0  6  j=0 3->6    //经e.hash&oldCap计算 3和6结果均为0 所以组成loHead:3-60 1 1 0 0  12 j=8 [0+8]    //而12与8与操作结果不等于0 所以组成hiHead:12在扩容时 将3->6放入新数组的0索引位置,将12放入新数组的8索引位置

这样的好处是不用计算链表的每一个元素在新数组对应的索引位置了,同时也保持了元素在链表中的顺序。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/426730.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实例63:python

#题目&#xff1a;输入数组&#xff0c;最大的与第一个元素交换&#xff0c;最小的与最后一个元素交换&#xff0c;输出数组 #!/usr/bin/python -- coding: UTF-8 -- a[1,2,3,7,9,8] for i in range(len(a)): if a[i]max(a): a[0],a[i]a[i],a[0] for i in range(len(a)): i…

css关于position的几个值

1. staitic:该值符合文档的初始排版&#xff0c;其中设置的与位置有关的值不起作用。2.relative 该值的偏移量&#xff0c;是在文档初始排版的基础上进行排版&#xff0c;并且覆盖顺序是最新输出的在最上面3.absolute该值元素的定位是以网页文档左上角位基准&#xff0c;并且不…

C语言笔记:格式化输入输出(fprintf、fscanf、sscanf...)

C语言笔记&#xff1a;格式化输入输出&#xff08;fprintf、fscanf、sscanf…) 包含以下函数的基本库&#xff1a;stdlib.h fprintf int fprintf(FILE *stream, const char *format,...) fprintf函数按照format说明的格式对输出进行转换&#xff0c;并写到stream流中。返回值是…

客户端配置_交换机作为STelnet客户端登录其他设备配置示例

交换机作为STelnet客户端登录其他设备配置示例1、组网需求图1 设备通过STelnet登录其他设备组网图如上图1所示&#xff0c;用户希望在服务器端和客户端进行安全的数据交互&#xff0c;配置两个登录用户为client001和client002&#xff0c;分别使用password认证方式和RSA认证方式…

实例64:python

#题目&#xff1a;有 n 个整数&#xff0c;使其前面各数顺序向后移 m 个位置&#xff0c;最后 m 个数变成最前面的 m 个数 #!/usr/bin/python -- coding: UTF-8 -- if name ‘main’: n int(input(‘整数 n 为:\n’)) m int(input(‘向后移 m 个位置为:\n’)) def move(a…

canvas 动画库 CreateJs 之 EaselJS(上篇)

本文来自网易云社区作者&#xff1a;田亚楠须知本文主要是根据 createjs 中的 EaselJS 在 github 上的 tutorials 目录下的文章整理而来 &#xff08;原文链接&#xff09;&#xff0c;同时也包含了很多本人的理解&#xff0c;如过有叙述不当的地方&#xff0c;请联系我 :-D 本…

细说fgetc

fgetc int fgetc(FILE *stream) 注意到参数类型FILE *&#xff0c;因为这个函数是我们在对文件进行读写操作时常用到的&#xff0c;文件流&#xff08;即我们所定义的指向文件的指针&#xff09;。同时还要注意到函数的返回类型int,参考了其他博主一些文章后总结出来&#xf…

boot分布式计算 spring_腾讯T4大佬剖析SpringBoot2 :从搭建小系统到架构分布式大系统...

写在前面SpringBoot是目前Spring技术体系中炙手可热的框架之一&#xff0c;既可用于构建业务复杂的企业应用系统&#xff0c;也可以开发高性能和高吞吐量的互联网应用。Spring Boot框架降低了Spring 技术体系的使用门槛&#xff0c;简化了Spring 应用的搭建和开发过程&#xff…

实例65:python

#题目&#xff1a;有n个人围成一圈&#xff0c;顺序排号。从第一个人开始报数&#xff08;从1到3报数&#xff09;&#xff0c; #凡报到3的人退出圈子&#xff0c;问最后留下的是原来第几号的那位。 coding:utf-8 nint(input(“输入人数:”)) List[] for i in range(1,n1): L…

算术类型转换、整型提升

分享一个很有意思的小tip 有人在编写代码时运行出了一个让人摸不着头脑的结果: -20>0U 怎么会是真值呢&#xff1f;&#xff1f; 这位朋友还特意检验了一下0U的值&#xff0c;当然是0没错。可是出现这样的结果到底是为什么呢&#xff1f; 这就涉及到c语言中的算术类型转换…

python博客编程_python编程

一.程序是什么&#xff1f; 程序&#xff0c;就是让机器按我们需求运行的一套代码。二.编程是什么&#xff1f; 编程&#xff0c;就是编写程序。三.编程语言是什么&#xff1f; 编程语言&#xff0c;就是编写程序的语言。四.编程语言的发展&#xff1f; 机器语言(二进制语言) →…

java学习(1):学生管理系统1

最近又想开始接触java了&#xff0c;写了一个简单的学生管理系统找找感觉&#xff0c;分为三部分&#xff0c;适合萌新使用。 实现初步功能 1建立一个student的java类 import java.util.*; public class student { public static void main(String[] args){ //输入人数 Scanner…

Mysql解压版配置

mysql安装包可到官网下载&#xff0c;地址&#xff1a;https://dev.mysql.com/downloads/mysql 1、首先解压文件包&#xff0c;我这解压到E:\install_work\mysql目录下&#xff1a; 2、发现mysql根目录下没有data目录和my.ini文件&#xff0c;不要紧&#xff0c;初始化mysql的时…

文件的输入和输出:流和缓冲区的概念和文件操作函数总结

流和缓冲区的概念理解 流&#xff08;stream&#xff09; A stream is a source or destination of data that may be associated with a disk or other peripheral. 流&#xff08;stream&#xff09;是与磁盘或其它外围设备关联的数据的源或目的地。 Streams are a portable …

python利用有道词典翻译_使用Python从有道词典网页获取单词翻译

从有道词典网页获取某单词的中文解释。import reimport urllibwordraw_input(input a word\n)urlhttp://dict.youdao.com/search?q%s%wordcontenturllib.urlopen(url)patternre.compile("",re.DOTALL)resultpattern.search(content.read()).group()pattern2re.compi…

java学习(2):学生管理系统2

这是继续修改后的代码&#xff0c;亲测可用 增加一个二位数组存储数据 import java.util.; public class student { public static void main(String[] args){ //存储学生人数 Scanner in new Scanner(System.in); System.out.println(“请输入学生人数&#xff1a;”); //存储…

[BZOJ1444]有趣的游戏(AC自动机+矩阵乘法)

n个等长字符串&#xff0c;机器会随机输出一个字符串&#xff08;每个字母出现的概率为p[i]&#xff09;&#xff0c;问每个字符串第一个出现的概率是多少。 显然建出AC自动机&#xff0c;套路地f[i][j]表示i时刻位于节点j的概率。 构建转移矩阵&#xff0c;当i为某个子串结束节…

web前端开发——HTML学习

WEB前端开发 W3C学习网站 MDN学习网站 HTML 从语义角度&#xff0c;描述页面结构 语言不区分大小写&#xff0c;特殊字符要求全小写 html5文件结构 快速编辑&#xff1a;Tab键 <!DOCTYPE html> 文档类型&#xff1a;符合HTML5标准 <htmml lang"en"&…

python群发短信脚本_python实现zabbix发送短信脚本

本文实例为大家分享了zabbix发送短信的具体代码&#xff0c;供大家参考&#xff0c;具体内容如下使用方法./sendSMS.py PHONE_NUMBER args_2 SMS_MSG接收参数输入参数一: 接收手机号(zabbix传来的第1个参数&#xff0c;报警接收手机号)&#xff0c;第一个参数可以对比发送邮件的…

java学习(3):学生管理系统3

总计分为六次修改&#xff0c;代码可直接拿出来用&#xff0c;建立一个类即可&#xff0c;注意类名同步 解决总分平均分问题 import java.util.*; public class student { public static void main(String[] args){ //存储学生人数 Scanner in new Scanner(System.in); System…