扩容是元素还是数组_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…

客户端配置_交换机作为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…

实例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语言中的算术类型转换…

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的时…

第二次作业重交

一、项目简介 1、Gitee项目地址&#xff1a;https://gitee.com/xnsy/WC 2、开发语言&#xff1a;C#语言 3、解题思路 刚看完作业要求后&#xff0c;只知道这个程序要完成对文件的统计工作&#xff0c;但是对于程序设计仍然是一头雾水&#xff0c;而后百度了怎么编写wordcount程…

java学习(4):第一个java程序

1第一个java文件 编写一个.java后缀的文件 public class helloworld{ public static void main(String[] args){ System.out.println(“helloworld”); } } 2cmd 编译java javac helloworld 生成class文件使用 Java helloworld 输出helloworld结束 个人练习 public class test…

java学习(5):全局变量和局部变量

public class qulitity{ static int num125; public static void main(String[] args){ System.out.println(“全局变量的值为”num1); int num212; System.out.println(num2); Test(); } public static void Test(){ int num21000; System.out.println(num2); } }

C语言知识点笔记完全整理

这个大长篇相当于是自己对于c语言学习的一个总结&#xff0c;会持续更新完善。 后续会在寒假整理一些经典的例题附带题解&#xff0c;当然希望我学到的东西、总结的经验&#xff0c;能够给后来者提供一个更好的学习途径&#xff0c;从入门到精通而不再是放弃。 也欢迎读者提出…

[HAOI2016]食物链

题目描述 如图所示为某生态系统的食物网示意图&#xff0c;据图回答第1小题现在给你n个物种和m条能量流动关系&#xff0c;求其中的食物链条数。物种的名称为从1到n编号M条能量流动关系形如a1 b1a2 b2a3 b3......am-1 bm-1am bm其中ai bi表示能量从物种ai流向物种bi,注意单独的…

java学习(6):数据类型

public class Shortdata{ public static void main(String[] args){ byte by 45; short sho 32767; System.out.println(“sho的值是”sho); //获取最大值 System.out.println(Byte.MAX_VALUE); System.out.println(Short.MAX_VALUE); //获取最小值System.out.println(Byte.M…

Xcode添加pch文件

1.打开Xcode工程. 在Supporting Files目录下,选择 File > New > File > iOS > Other > PCH File 然后点击下一步&#xff1b; 2.如果项目名称为Demo, PCH 文件的名字为Test.pch,然后创建&#xff1b;3.选择 PCH 文件创建Test.pch文件4.找到 Project > Build …

java学习(7):巩固练习

//任务1 //使用记事本或其他文本编辑器编写一个java控制台程序&#xff0c;定义一个包含main方法的java类&#xff0c;在main方法中使用合适的数据类型定义如下局部变量&#xff0c;标识符要严格遵守java规范。 //学生姓名&#xff1b;学生年龄&#xff1b;学生身高&#xff0c…

java学习(8):巩固练习

//任务2 编写控制台程序将以下给定的整数常量用合适的变量接收并将其10进制值与二进制表示形式分别输出打印在控制台界面 //55&#xff1b;666&#xff1b;1080&#xff1b;2500&#xff1b;78451&#xff1b; public class test02{ public static void main(String[] args){ /…

wordpress安装_WordPress第三课:使用SOFTACULOUS安装WORDPRESS

在精简的过程中&#xff0c;你会发现你更加明确想要什么&#xff01;目标变得明确&#xff0c;生活也将变得清晰。安装WordPress最简单的方法是使用自动安装程序&#xff0c;这是一个特殊的工具&#xff0c;可以在你的网站上安装程序。大多数虚拟主机都会提供一个自动安装程序作…

keepalive日志_12.日志收集项目-数据流图以及nginx安装

数据流图nginx安装中文文档http://tengine.taobao.org/nginx_docs/cn/docs/基础依赖与安装yum -y install gcc gcc-c autoconf pcre pcre-devel make automakeyum -y install wget vim httpd-toolsyum源在官网拷贝vi /etc/yum.repos.d/nginx.repo[nginx-stable]namenginx stabl…

【算法】禁忌搜索算法(Tabu Search,TS)超详细通俗解析附C++代码实例

01 什么是禁忌搜索算法&#xff1f; 1.1 先从爬山算法说起 爬山算法从当前的节点开始&#xff0c;和周围的邻居节点的值进行比较。 如果当前节点是最大的&#xff0c;那么返回当前节点&#xff0c;作为最大值 (既山峰最高点)&#xff1b;反之就用最高的邻居节点来&#xff0c;替…