前言

点进来这篇文章的大概有两种人,一种是喜欢用Map的想看看自己是不是有可能也会踩雷,一种是不喜欢用Map的想进来看看那些喜欢用的人是怎么踩雷的。

那你要失望了,我只是单纯把公司最近代码审查时一个关于Map的小故事讲出来罢了。

如果这样用过的,可以收手了,没用过的,引以为鉴。

故事背景

我所在的是一个医疗行业互联网公司,有些地方比较严谨,比如架构和安全这块,有些地方又十分松散,就是没有好的编码规范,也没有代码审查,各自为之,自由发挥,所以工作中维护项目时经常能读到一些烂代码。

久而久之也就容易形成下面这张很有名的图描述的生态:

所以上个月开始,公司的主管决定开始做代码审查,而且每周五开一次审查会,热烈讨论和帮助成长(公开处刑)。

而上周开会有意思的就是一段Map用法相关的代码,被特别拎出来做了讨论,我就专门把里面的片段提取出来做了简化,给大家分享一下问题以及优化方法。

错误用法

因为源代码涉及的业务内容较多,直接拎出来不便于理解,所以我专门做了简化,把核心的那块拿出来展示。

大概的业务是这样:患者去挂号,我们要查询未缴费的挂号记录集合,然后拿到里面的患者ID和挂号编码,用这两个值再去查询每个挂号下面所开的处方列表,也就是检查、药品等等之类的。

好了,下面就是被处刑的一段使用Map的简化版代码:

首先,是查询未缴费挂号记录,然后用查到的值再去查询对应的处方。

看着还好,问题就是第一段返回Map那里:

获取了挂号列表,然后进行了一次遍历,把里面自己需要的两个值专门放到Map里面再返回。

OK,其实很好理解,可能有人看了就会有疑问了,为啥不直接返回列表就好了,还要用一下Map?

根据代码本人的解释是这样的:因为他要返回的这个对象里面属性实在太多了,大概是下面这样。

(PS:param是我为了核心字段的保密而修改的,源代码里该对象共有几十个业务字段。)

因为他嫌弃太多,就用Map只保留了自己需要的那两个,也算解释的过去……

问题解析

1、问题到底在哪里

案例摆出来了,那么问题是什么?

就是因为Map你放在了循环里,这是大忌啊!

众所周知,生产环境查询的数据往往是动态的,比如案例中的查询挂号列表,某个时刻也许是1个,也有可能是10个,流量洪峰期更有可能百多个,你想想Map里面一次性装了多少这鬼玩意儿。

2、Map最直接作用

Map网上有非常多的文章解析和原理解析,我就不专门解释了,我这里给你说其中一个最简单直接的特点,说白了你可以把它当成项目运行时的缓存。

比如项目启动后,你可以往里面存一些静态不会变的配置信息,这样项目运行时你直接可以GET到,而不必做多余的查询,这也是在服务商模式的支付场景中很常用的一个手段,PUT多个子商户的配置信息。

3、形象地看待Map

在Java中Map是一种特殊的工具,它一旦产生就很难清理,会在内存中直接开辟一块空间,你可以想象成建了一个小仓库,当你PUT键值对的时候,其实就是往这个小仓库中放杂物。

而怪就怪在当你想要把Map清空的时候,结果只是把仓库里的杂物给丢掉了,小仓库还在。

很多初学者甚至包括已经工作几年的Java工程师都不清楚这一点,要么以为会自动释放内存,要么以为调用empty()就可以清理掉占用内存,实则不然。

明白这一点以后,你再回头想一想这个案例,一个患者查询挂号信息的时候开辟了1个小仓库,这个小仓库里面又循环存放了N个杂物,算算一个三甲医院一天估计会有多少个患者看病,每个患者都开辟1个小仓库存放一堆杂物,你觉得内存的心理阴影面积有多大。

4、真会内存泄露吗

这也是为什么我们的代码评审会专门把这位道友的片段捞出来与民同乐的原因,当你无法预估一段代码中Map生产量的时候,就是埋下内存泄露隐患的时候。

我工作至今,只有在广州工作期间遇到过一次,而且不是互联网项目,是电力行业的系统,用户量也就几万人,就因为Map使用不当导致某一天系统忽然跑不动了,一直转啊转啊转,跟着我左手右手一个慢动作……

以2014年当时的一些技术底蕴,很多中小企业并没有可靠的监控措施,纯粹靠人力巡检,所以出了这种生产环境的问题大家都傻了,然后各种分析各种讨论,好在经过大家的同心协力,最后重启一下好了

后来知道,就是内存泄露了,并且找到了核心业务中动态产生Map的代码,当时给我惊呆了,还有这种写法,还好不是我。

其实这些年换了几家公司,和不同的同事合作过,不分经验长短,写这种代码的程序员在中小企业比你想象的多,但是我遇到的出现内存泄露的就那一次,哪怕这次代码审查捞出来的也是没有在线上出现问题的代码,只是他被审查了而已。

所以,内存泄露没有你想的那么容易出现,服务器水平也和以前大不相同,动辄8核16G什么的,但不能因为这样就抱有侥幸的心态,以后没把握还是别用什么Map了,万一你走了把别人坑了呢,就当乐于助人吧。

正确用法

说下正确的用法吧,其实很简单,遇事不决List,围绕List来解决一般都没啥问题。

1、直接List

上面的案例说了,虽然字段多,但其实省事一点就直接List列表全部返回也没啥,总比你对着Map疯狂输出要强。

就直接返回整个List列表,别整些奇奇怪怪的,在实际工作中,安全比方便重要,亘古不变。

2、拼接字符串

如果你实在不想要那么多字段,毕竟有大几十个,我就只想要我的两个小可爱。

那也简单,直接把两个小可爱做成人体蜈蚣,然后放List返回就行。

获取挂号列表的时候,直接用一个分隔符比如@,把两个你要的值拼接起来放入List返回就行。这里只是抛砖引玉,只要思路是这样方法有很多,比如你不嫌麻烦的话还可以单独创建一个DTO就传输你想要的几个字段也行。

然后查处方时再把两个小可爱拆开即可

总结

Map原理解析的文章多如牛毛,你可能看过就忘了,也可能看的很烦,没关系,那些都是造核弹用的,我们就拧螺丝不干别的,工作时就注意一下我总结的几点即可高枕无忧。

1、Map最直接的特点是可以作为项目运行时的缓存,多用于项目启动后存放静态数据,比如配置信息;

2、Map的产生会开辟一块内存空间,而这块内存空间的数据很好清理,但占用的内存很难清理,往往要重启后才会彻底释放;

3、在循环中使用Map是大忌,因为线上环境它的生产量难以预估;

4、查询时一旦牵扯到动态数据,千万不要使用Map,围绕List来做替代。

最后就好奇问下,您也是神仙转世吗?


原创文章纯手打,一个一个字敲出来的,如果觉得有帮助麻烦点个推荐吧~

本人致力于分享工作中的经验及趣事,喜欢的话可以进主页关注一下哦~

喜欢用Map却从未遭遇内存泄露的Java程序员上辈子都是神仙的更多相关文章

  1. java程序员--小心你代码中的内存泄漏

    当你从c&c++转到一门具有垃圾回收功能的语言时,程序员的工作就会变得更加容易,因为你用完对象,他们会被自动回收,但是,java程序员真的不需要考虑内存泄露吗? 其实不然 1.举个例子-看你能 ...

  2. Java中基本数据类型的存储方式和相关内存的处理方式(java程序员必读经典)

    1.java是如何管理内存的 java的内存管理就是对象的分配和释放问题.(其中包括两部分) 分配:内存的分配是由程序完成的,程序员需要通过关键字new为每个对象申请内存空间(基本类型除外),所有的对 ...

  3. top命令经常用来监控linux的系统状况,比如cpu、内存的使用,程序员基本都知道这个命令。 按 q 退出

    top命令经常用来监控linux的系统状况,比如cpu.内存的使用,程序员基本都知道这个命令. 按 q 退出

  4. 对《java程序员上班那点事》笔者对数组占用内存质疑

    1.<java程序员上班那点事>笔者对数组占用内存的描述 2.实际测试情况: /** * 测试一维数组占用内存 */ public static void testOneArray() { ...

  5. Go map 竟然也会发生内存泄露?

    Go 程序运行时,有些场景下会导致进程进入某个"高点",然后就再也下不来了. 比如,多年前曹大写过的一篇文章讲过,在做活动时线上涌入的大流量把 goroutine 数抬升了不少,流 ...

  6. 读书笔记一 Java程序员的基本修养(数组及其内存管理)

    1.1 数组初始化 1.1.1 java数组是静态的 java数组被初始化之后,该数组所占的内存空间.数组长度都是不可变的. java程序中的数组必须经过初始化才可使用. 数组的初始化有两种方式: 1 ...

  7. Java 程序员最喜欢的 11 款免费 IDE 编辑器

    Java开发人员需要花费大量的时间埋头于Java代码中,使用各种不同的IDE(Intergrated Development Environment)来开发Java代码,所以下面我将为大家介绍11个不 ...

  8. Java 程序员最喜欢使用的日常工具

    多年来,Java 始终是企业应用程序的支柱.最近几年,Java 也是 Android 开发的首选编程语言.不过开发人员如何使用这种语言呢?一项新的研究阐明了主要使用 Java 的开发人员的工作类型,以 ...

  9. 99.9%的Java程序员都说不清的问题:JVM中的对象内存布局?

    本文转载自公众号:石彬的架构笔记,阅读大约需要8分钟. 作者:李瑞杰 目前就职于阿里巴巴,资深 JVM 研究人员 在 Java 程序中,我们拥有多种新建对象的方式.除了最为常见的 new 语句之外,我 ...

  10. Java程序员也应该知道的系统知识系列之(网卡,cpu,内存,硬盘,虚拟化)

    https://yq.aliyun.com/articles/1718?spm=5176.100240.searchblog.16.UaGd04 https://yq.aliyun.com/artic ...

随机推荐

  1. 使用 EFKLK 搭建 Kubernetes 日志收集工具栈

    转载自:https://mp.weixin.qq.com/s?__biz=MzU4MjQ0MTU4Ng==&mid=2247491992&idx=1&sn=a770252759 ...

  2. Linux病毒扫描工具ClamAV(Clam AntiVirus)安装使用

    在线检测木马病毒的网址:https://www.virustotal.com/gui/home/upload 一.简介 ClamAV(Clam AntiVirus)是Linux平台上的开源病毒扫描程序 ...

  3. 3.使用nexus3配置maven私有仓库

    配置之前,我们先来看看系统默认创建的都有哪些 其中圈起来的都是系统原有的,用不到,就全删掉,重新创建. 1,创建blob存储 2,创建hosted类型的maven 点击 Repository下面的 R ...

  4. Map中定义的方法:

    添加.删除.修改操作: Object put(Object key,Object value):将指定key-value添加到(或修改)当前map对象中void putAll(Map m):将m中的所 ...

  5. 死锁与Lock锁

    死锁1.死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁 2.说明: 1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞 ...

  6. CURL提交--POST/GET-带header信息

    function https_request($url, $param, $data = '', $method = 'GET', $headers = '') { $opts = array( CU ...

  7. Linux基础_1_简介

    Linux是什么 一款优秀的操作系统软件,特性是一切皆文件:一切设备皆文件!一切设备的设置皆修改配置文件!一切服务的搭建皆修改配置文件!(庞大的树形结构文件系统) 根据FHS标准,Linux目录有以下 ...

  8. MySQL开发

    常用数据类型 整数:tinyint.int.bigint小数:decimal.字符串:char.varchar.text 增 insert into 表名(列名,列名)values(值,值): 删 d ...

  9. JDBC数据库编程(java实训报告)

    文章目录 一.实验要求: 二.实验环境: 三.实验内容: 1.建立数据库连接 2.查询数据 2.1 测试结果 3.添加数据 3.1.测试结果 4.删除数据 4.1.测试结果 5.修改数据 5.1 测试 ...

  10. 二、python基本数据类型

    一. 字面量 代码中,被写在代码中的固定的值,称之为字面量 Python常用6种值(数据)类型 字符串(string) :又称文本,是由任意数量的字符如中文.英文.各类符号.数字等组成.所以叫做字符的 ...