粉丝4.9万获赞54.1万

之前我分享过一期哈西 map 的面试题,然后呢,有个小伙伴来私信我,他说最近遇到了一个康 carrot 哈西 map 的一个问题,他不知道怎么回答,于是就有了这一期的内容。 我是麦克,一个工作了十四年的家务程序员,今天啊,我们来分享一下关于康卡润的哈西麦,他的底层实现原理这个问题,看看普通人和高手是如何回答的。普通人的回答,嗯,呃, 刚看到哈西迈普呢,他是用数组和练表的方式来实现的这样一个结构,也就是说我们的数据存储是 最终是存储在数组和链表这样一个里面,嗯,然后在 gdk 一点八里面呢,是引入一个红 红黑素的结构,嗯,红黑素和链表其实都是去解决,嗯, 解决哈西就是解决康康哈西 table 里面啊,康康哈西卖部里面他的呃数据的这个冲突的一个问题,然后就是就要要用红黑数,是因为, 呃,要用红黑素是因为他的这个链表啊,有可能会很长啊,就因为他的速度长度是有限嘛,所以就会比较长,比较长的情况下呢,就 就可能在做数据查询的时候效率会比较低一点啊,所以就是会采用对红黑素啊,这个是对是,嗯,高手的回答,嗯,关于这个问题呢,我从这三个方面来回答。 第一个,康卡润的哈西麦的整体架构,我们来看这个图,这个呢是康卡润哈西麦在 gdk 一点八里面的存储结构,它是由数组单项链表和红会数来构成的。 当我们去初始化一个康康润哈西卖部的实力的时候,默认会初始化一个长度等于十六的速度。由于康康哈西卖部,他的核心仍然是哈西表,所以必然会存在哈西冲突的问题。 所以呢,慷慨的哈西麦吧采用炼式寻子的方式来解决哈西表的冲突。当哈西冲突比较多的时候,会造成链表长度较长的问题, 所以这种情况下会使得康康润哈西麦中的一个数组元素的查询复杂度会增加,所以在 gdk 一点八里面引入了红会数这样一个机制, 当速度长度大于六十四,并且链表的长度大于等于八的时候,单项链表就会转化成红黑数。另外呢,随着康卡润的哈西麦部的一个动态扩容,一旦链表的长度小于八,红黑数会退化成单项链表。第二个,康卡润哈西麦部的一个基本功能, 康康的哈西麦普呢,本账是一个哈西麦,因此功能和哈西麦普是一样的。但是康康的哈西麦普在哈西麦普的基础上提供了并发安全的一个实现。并发安全的主要实现呢,主要是通过对于 no 的节点去枷锁来保证数据更新的安全性。我们来看这个图。 第三个,康康润哈西麦吧在性能方面做的一些优化,如何在并发性能和数据安全性之间去做好平衡? 在很多地方都有类似的设计,比如说像 cq 的三级缓存,买水口的八八破新款, nike 的一个锁升级等等。 康康润哈西麦粉呢,也做了类似的一个优化,主要体现在几个方面。第一个啊,在 gdk 一点八里面, 康康润的哈西麦吧它的锁的力度是数组中的某一个节点,而在 gdk 一点七里面,它锁定的是 segment, 锁的范围要更大,所以性能上它会更低。第二个,引入红黑素这样一个机制去降低了数据查询的时间复杂度, 红黑素的时间复杂度是 o logan。 第三个,我们看这个图,当数组的长度不够的时候,康康瑞哈奇迈普呢,他需要对数组进行扩容,而在扩容的时间上,康康瑞哈奇迈普引悟了多线层 并发扩容的一个实线,简单来说呢,就是多个县城对原始数组进行分片,分片之后每个县城去负责一个分片的数据迁移,从而去整体的提升了扩容过程中的数据迁移的一个效率。 第三,康康的哈西卖部呢,他有一个 size 方法来获取总的元素个数,而在多线层并发场景中啊,在保证原子性的前提下去实现元素个数的累加性能是非常低的。 所以康康的哈西卖部呢,在这个方面做了两个点的优化。第一个点是当现成竞争不激烈的时候,直接采用 cs 的方式来实现元素个数的一个原子递增。 第二个啊,如果县城竞争比较激烈的情况下,使用一个数组来维护元素个数,如果要增加 总的元素个数的时候,直接从数组中随机选择一个,再通过 cs 算法来实现原子低增。他的核心思想是引入了数组来实现对并发更新的一个复杂。以上呢,就是我对这个问题的理解, 从高速的回答中啊,我们可以看到,康康的哈西麦粉呢,里面有很多的设计思想是值得我们去学习和借鉴的,比如说锁的力度控制、分段锁的设计等等,他们都可以应用在实际的业务场景中。 很多时候啊,大家会认为这种面试题毫无价值,当你有足够的积累之后,你会发现从这些技术底层的设计思想中去获得很多的设计思路,他能够真正意义上去帮我们解决实际问题。好的,本期的普通人 vs 高手系列的视频呢,就要到结束了,喜欢的朋友记得点赞 和收藏。另外,我也陆续收到了很多小伙伴的面试题,我会在后续的内容中呢逐步去更新给了大家。我是麦克,一个工作了十四年的家务程序员,咱们下期再见。

哈西卖宝,啥时候扩容?为什么要扩容?这是一个针对一到三年左右 java 开发人员的一个面试题,问题本身呢,并不是很难,但是对于这个阶段的粉丝来说,由于不怎么去关注这些底层的实现,所以呢会难住一部分的同学。哈喽,大家好,我是麦克, 一个工作了十四年的 java 程序员。下面呢,我们来分享一下这个问题的底层逻辑和专业的回答。 在任何语言里面呢,我们希望在内存里面去临时存放一段数据,那么可以使用官方封装好的一些集合框架,比如说像 list、 哈士 map 和 set 等等,作为数据存储的一个容器。 当我们创建一个集合对象的时候呢,实际上就是在内存里面一次性的申请了一块内存空间,而这个内存空间的大小啊,是在创建集合对象的时候去指定的,比如 list 的默认大小是十 十,哈士 max 的默认大小是十六。在实际开发过程中啊,我们需要去存储的数据量往往是大于存储容器的大小,那么昨天这个情况呢,通常做法就是扩容,当集合存储容量达到某个预值的时候啊, 集合就会进行一个动态扩容,从而去更好的满足更多的数据存储的一个需求。而 list 和哈奇麦姆本质上是一个数组的结构,所以基本上只需要去新建一个更长的数组,然后把原来数组里面的数据拷贝到新数组里面就可以了。以哈奇麦姆为例啊,他是什么时候触发扩容,以及扩容的原理是什么呢? 当哈西 map 里面的元素个数超过临界值的时候,会自动触发扩容,这个临界值啊,有一个计算公式,它等于负载因子乘以它的一个容量大小,负载因子的默认值呢,是零点七五,而 而容量大小默认是十六,也说元素个数达到十二的时候会触发扩容,扩容后的大小呢,是原来的两倍。由于动态扩容这个基设存在,所以我们在使劲用里面需要注意集合初始化的时候明确去指定集合的大小, 避免频繁扩容带来性能上的影响。假设啊,我们像哈西卖部里面存幺零二四个元素,如果按照默认的值是十六的情况下, 随着元素的不断增加,会造成至少七次扩容,而这七次扩容需要重新去创建新的哈士表,并且进行数据的一个迁移,对性能的影响呢,是非常大的。最后啊,可能有些面试官呢,会继续去沿着这个方向来问你,哎,为什么扩容一直是零点七五啊? 扩容因子啊表示哈西表中的元素的填充程度。扩容因子的值越大,那么意味着 触发扩容的元素个数会更多,虽然它的整体空间利用率比较高,但是哈西冲突的概率也会增加。反过来说,扩容因子的值越小,那么触发扩容元素的个数也就越小,那么意味着 垃圾充足的概率也会减少,但是对于内存空间的浪费就比较多了,而且呢,还会增加扩容的一个频率。因此啊, 扩容因子的值的设置本质上就是一个冲突的概率以及空间利用率之间的一个平衡。零点七五这个值的来源啊,和统计学里面的松柏分布有关系,我们知道哈西 map 里面采用的是链式寻指的方式来去解决哈西冲突的问题, 为了避免链表过长带来的一个时间复杂度增加的一个情况,所以呢,链表长度大于等于七的时候,就会转化为红黑素,从而去提升解锁的效率。当扩容因子在零点七五的时候,链表 长度达到八的可能性机会为零,也就是说比较好的达到了一个空间成本和时间成本的一个平衡。以上就是我对这个问题的完整理解, 如果在面试的时候遇到这个问题呢,我们可以这样去回答,当哈士 map 的元素个数达到扩容预值默认是十二的情况下,会自动触发扩容,默认扩容大小呢是原来速度长度的两倍。 巴西 map 的最大容量是英的酒店 max value 也就是二的三十一次方减一。以上就是我对这个问题的回答。

说说你对哈西卖部的理解。那么这是我们面试的一个开场题。那么这个问题呢,如果我们只是简单的迅速一两句,那肯定是不够的。但是如果我们迅速的很混沌的话呢,也不能达到很好的效果。 所以我们可以梳理一下,从几个维度来去叙述这个问题。首先我们看一下还是迈步的内的继承结构,那么这个呢?就是我们还是迈步的内的继承结构,还是迈步在这个位置它继承这个接口。 那么他下面还有立刻的哈西麦普,然后右边还有这个哈西太伯,他是一个限制人群的人脉普,他下面还有个婆婆提示。那么这个类有的时候我们经常用来读取这个配置文件。 那左边还有康康的哈西迈普,他是现成安全的。还有一个呢,这个区迈普他是可以做排序的啊。这是第一个维度,我们描述了哈西迈普的内的继承结构。然后第二个 维度我们可以描述一下哈西迈步的一些左右的特征。那么主要特征啊,第一个就是哈西迈步的 k 是不能重复的,如果你重复的话,就会把之前的数据覆盖掉。那么第二点就是哈西迈步里面的数据是没有顺序的,他不是按照你放入的顺序来存放的 好。那么第三个呢,我们哈西半部不是县城安全的,所以在多县城条件下会有县城安全问题。那么另外一个的话呢,就是我们哈西半部啊,它里面的剑和直都可以是空的,允许是空值。那么这就是我们哈西半部的一个主要特征。 然后呢,我们可以描述一下哈西麦部的这个底层数据存储结构,那么哈西麦部的底层存储结构啊,就是我们这个图,如果是揭利开一点七的哈西卖部,他是由这个速度,然后加上这个列表所构成的。如果我们是揭开一点八的话,他除了速度和列表以外,他还有一种 红黑树的这个结构。那么当我们这个列表的长度啊,达到八以后,他会转成红黑树啊,进行这个数据存储。这时呢,含西麦卜底层的数据结构 好。另外呢,我们可以看一下哈西麦普这个类的,它里面有几个哎,与性能相关的参数。一个是呢,我们这个玉植 是零点七五,他是一个横流的尺度,当我们还是迈步里边速度的容量已经使用了啊,百分之七十五了,哎,那这个时候呢,这个速度需要扩容。那目的情况下呢,我们速度的长度啊,这个容量他是一十六,哎,一十六的零点七五,那就是一十二。 当速度已经使用了十二个元素以后,那这个时候呢,他就需要扩容。那以上呢,我们通过几个维度叙述了一下我们对海西面部的理解和认识。

不要光讲原理,不讲使用场景,今天来讲一下 earliest 和哈西 map 的一个非常常用的使用场景,就是我有一个用户表和一个地区表,用户表有一个地区 id, 我们的需求是根据这个地区 id 查询这个地区名称, 最终返回给前面的结果是这样的,我们有两种解决方案,一种是将用户数据和地区数据都放入 list, 然后通过双重循环 比较用户的地区 id 和地区的地区 id, 如果相等的话,则将地区的地区名称复制给该用户,这样做呢是大大的增加了循环的次数,从而降低了性能。第二种方案就是将存储地区的这个例子转换成卖便利用户的时候, 直接通过 mac 的 k 来直接访问到这个地区信息,将地区的名称复制给该用户,这样做的好处就是大大减少了循环的次数,从而提升了性能。

深入理解哈西卖铺与哈西算法,首先我们来讲一下哈西卖铺的数据结构,在 java 七以及 java 七以及以前,哈西卖铺的底层是数组加列表,像这样的 默认情况下面的元素放在数组上,如果说他们中间发生了哈西冲突,也就是说在一个数组上面,我要插多个元素,他就会形成这样的一个单列表,这就是哈西麦普 gdk 一点七以及以前他的数字结构。那么到了扎瓦八中间之后的话呢,除了数组和列表之外,他还加入了鸿飞数,也就是说 如果链表的长度过长,他在一定条件下面可能会转换成一棵红黑树。那什么情况下面链表会转换成红黑树呢?他要满足的条件就是第一个链表的长度必须要大于八,就是这个长度必须大于八。然后与此同时还要注意一个点,就是他还需要满足第二 条件,就是数组的长度必须要大于等于六十四,也就是说默认情况下面,你又出来的,又出来的这个哈西迈普,他的数组是六十四,是一十六个长度,对不对?这个地方你要经过两次扩容变成六十四之后,当列表的长度大于等于八了之后, 那么这个链表才会转成红黑树,才会转成这个样子,否则的话呢,他只会触发哈西麦普当中数组的扩容,也就是说他仅仅只会把这个数组呢进行乘以两倍, ok, 所以呢,这就是链表 什么情况下面会转成同回数?要注意了,是有两个条件的,不单纯是列表大于八,还要满足数组的长度呢,他一定是要大于等于六十四, ok, 好,我们再来讲一下哈西卖谱当中的哈西算法,其实要讲清楚哈西算法的话,我们必须要去讲他的 pow 的方法和 get 方法,但是不 管是哈西卖部当中的扑特还是他的 get, 他的流程是类似的,比如说第一步先根据 t 值来计算出哈西值,然后第二步根据这个哈西值来算出这个数组的下标, ok, 然后的话呢再进行对应的,比如说插入,插在数字上面,或者插在列表上面,或者插在横汇数上面, 或者是进行对应的查询,所以呢,这就是哈西卖部当中的一个库的 get 方法,他的一个流程。那么我们来看一下,在这里面呢,他就会涉及到两个算法,第一个的话呢,就根据 k 来计算哈西,第二个话呢,根据哈西来算出数字的下标。 首先在原码当中,我们来找一下根据 k 计算出哈西的这种方法,那么这个 k 呢,是个入餐对不对?然后的话呢,你会发现它里面进行了一个三目运算,如果这个 k 等于空,直接返回一个零, ok, 如果说他不为空,那么首先拿到这一个 他的哈西克的值,拿到之后再做一个处理,把这个值呢付给 h, 然后呢再用 h 这个本身对不对?和这个 h 向右无符号位一十六位做一个优惠运算,最终得到一个改进型的哈西阔的, ok, 然后这就是根据 t 怎么去算哈西?这是第一个算法,然后第二个算法的话,你会发现啊,他在根据这个哈西值再来去算出对应数字的下标,也就是说我得到这一个哈西值,然后的话呢,再去 跟这个 n 减一,进行一个雨计算,也就是说我们得到的哈西值再跟这个 n 减一,进行一个雨计算, ok, 这样呢得到一个对应的数组下标,那么这个 n 是什么呢?这个 n 是为数组的一个长度,那么为什么他可以算呢?我要讲一下,因为 这么去运算,他的效果其实跟哈西值去个 n 曲模他是一样的,但是他满足的条件就是必须这个 n 是二的次方,当然在哈西卖部当中 他是二的四方,所以呢这样的运算他的效果是一样的。与此同时的话,因为他是个雨计算,看到没有,在计算机里面雨计算他的运算速度一定比我们的这种曲模运算他的效率要高,所以这种算法效率更高。好,这是第一个点, 第二个点就是在 gdk 一点八当中,为什么要采用这么复杂的哈吸值的计算方法呢?明明你通过一个 k 直接调用哈吸扣的方法可以拿到一个哈吸值,为什么还要采用这样的运算呢? 其实原因很简单,就是为什么哈西卖普要把哈西扣的做一个改进,就是让他算出的数组下标更加分散,就是让我们算出了这个哈西值,然后再经过数 下边的运算,他得出的结果会更加的分散。原因是什么呢?我们来看一下。如果说我们就不进行这种运算,我们拿到一个哈西扣的,然后拿到这个哈西扣的之后,我们再跟 n 减一,进行一个语计算, n 是什么? n 是 数字的长度对不对?比如说数字的长度是一十六,那么这个情况下面呢,他 n 减一,他就是一十五,然后用二进去表示就是四个幺, ok, 然后高位全是零,你们进行一个语句算的时候,大家发现没有, 这个哈西值得出的数组下标,他只跟低位有关,就是你这个哈西值的高位,其实他不会去参与到我们数组下标的运算,也就是说如果你不对这个哈西扣的进行改进,那么这种 n 减一与上哈西的这种计算数组下标的 方式,他始终会导致很大的哈西通途,因为你有可能计算出来了哈西值,对不对?明明他的低位都是相同的,只是高位不同,于是高位很多的东西不同,那么低位只要第四位,第五位相同,那么他们算出的数组下标一定相同,所以这样的情况呢,就不利于哈西分散, 所以的话呢,在哈西卖普里面就用这样的方式进行了一个改进。怎么改进的?首先把这个哈西扣的复制给 h, 好,那么我们得到一个 h, 那么大家知道啊,这一个哈西扣的他是一个三十二位的,那当然有低一十六位和高一十六位,对不对?这个时候呢,我们再把这个 h 向右无符号位于一十六位,得到一个另外的结果,那么你可以发现这个 h 向右边无符号位于一十六位,就相当于是往右边推进,那相当于是把这个高一十六位,把它放到这 第一十六位,对不对? ok, 然后的话呢,这个高位就补零,然后他们中间再来完成一个预后运算,也就是说通过这种计算比较均匀的预后运算,最终会得到一部分影响数组下标的这样的一个哈西值, 所以你发现这样的一个算法的改进的话呢,他就会会让这种高一十六位也会去参与到影响数组下标运算的这些低位的这个哈西值里面来。 如果说你不采用这样的运算,那么你的数组下标了可能会更加的什么有哈气冲突。如果我采用了这样的运算,那么高一十六位他也可以去参与我们的这个数组下标的计算的一个影响,所以他会让我们的下标计算 更加的分散,所以这就是哈西卖部当中他对哈西 coat 做的一个算法改进。

接下来我们来做一个案例,哈西迈步集合存储学生对象并便利。首先我们来看一下详细的需求,创建一个哈西迈步集合,建设学号,直视学生对象,存储三个建制的元素并便利。 看完这个需求之后,我们发现他还是比较简单的,所以接下来我们一起来说一下思路。第一步,定义学生类。第二步,创建哈西迈普集合对象,注意他的箭是死去类型,直是死丢状态类型。 第三步,创建学生对象。第四步,把学生添加到集合,在这里要注意学生是这个集合的值,而他的箭是死翠类型,死翠类型我们直接用双引号扩起来就是一个字母串对象,所以这里我们并没有创建 字符串的对象。第五步,便利集合,便利集合。我们有两种方式,方式一,根据箭找直,方式二,根据箭直段对象找箭和直。 说完了这个案例的需求和思路之后,接下来我们到程序中去实现一下来看这里我新建好了一个包,在这个包下建好了两个类,在这个测试类里面有这个案例的需求和思路的注视信息。 接下来我们按照思路的步骤来完成我们这个案例。第一步,定义学生内,看这里学生内我已经准备好了。第二步,创建哈西迈普集合对象,接下来我们一起来写代码实现。那么在这里我们要创建一个 哈西迈普,这个哈西迈普的箭是死春一类型的,只是死丢的类型的,起个名字 xm, 你有一个哈西迈普箭是死春一类型,只是死丢的类型。 在接下来我们来创建学生对象,死丢蛋挞 s 一,又一个死丢蛋挞。来姓名给一个林青霞,年龄是三十, 选中他, ctrl c ctrlv, 首先改这个名字, s 二 s 三。接着在这里我们改一下名字,张曼玉,这个是王祖贤,那么张曼玉年龄是三十五岁, 王祖贤三十三岁。在接下来我们把学生添加到集合 hm 点 toot, 认真看,他的箭是死去类型学号, it 黑马零零一 s 一,存出了一个 hm 点铺特 it 黑马零零二 s 二, hm 点 pro i t 黑马零零三 s 三。好嘞,现在我存储了三个建制的元素, 那么最后我们便利集合,便利集合有两种方式,我们先来写方式一的箭找直。首先我们要获取到所有箭的集合,那就是 hm 点 rk 赛他 col alt v 生成左边内容,起个名字就叫 kict。 接下来我们要便利这个键的集合,放 k 冒号可以,赛特进来之后,我们要根据剑找直 hm 点 get 这个 k, 然后 ctrl altv 得到了一个直。 在接下来我们 s o ut 一回车,输出这个键,加上这个值,注意这个值 w, 他是个学生对象,他要 get 内幕, 再加上逗号,加上 w 点 get 好了,到这方式一的便利我们就做完了。 s o ut 一回车一二三四五 六七八,我们用一个分隔线给隔开。接下来我们来写方式二的便秘, 来一起说一下啊。首先我们要得到兼职段对象的集合, hm 点 angelat, ctrl alt v 生成左边的内容,起个名字就叫 angelat。 接下来我们要便利见值得对象的集合,得到每一个见值的对象,注意这个增强货里面这个元素类型就写这个东西就可以了,因为他是色的集合中元素的类型,我们直接把它拿过来给他起个名字,密, 然后面跟上 nchats, 那么进来之后密点 get k, ctrl alt v, 生成左边的内容,密点 get yel, ctrl altv, 生成左边的内容。 在接下来这个输出语句我们直接从这里拿过来就 ok 了。最后 ctrl alt l 格式化一下。好了,到这这两种便利方式我们就写完了,右键执行,看一下控制台的输出 好了,这个是没有问题的,那么到这关于这个案例我们就讲完了。

cash map 知多少?首先,哈西 map 是基于哈系表的 map 接口的非同步实现,该实现提供所有可选的映射操作,并允许使用空作为 k 和 y 留的值。此类不保证 更奢的顺序,特别是他不保证该顺序很久不变。其次呢,哈西麦婆实际上是一个链表散裂的数据结构及数组加链表的结合体。当我们往哈西麦婆当中 铺的元素的时候,首先根据 t 的哈西扣的重新计算哈西值,根据哈西值呢得到这个元素在数组当中的位置,那么如果该数组在该位置已经存放了其他元素,那么在这个位置上的元素将以链表的形式存放,新加入的呢?放在链头,最后加入的放入链尾。如果数组中该位置没有元素,就直接将该 元素放到数组的位置上。但是 gdk 一点八当中对哈西麦的实现是做了优化的,当列表当中的节点数超过八个之后,该列表就会转为红黑数了,来提高查询效率。从原来的 on 到 ologan, 欢迎大家在评论区补充其他的内容吧。

扎勒集合核心误区深度剖析,从理论根源规避实践陷阱在扎勒开发中,集合框架的正确使用是构建健状应用程序的基石。然而, 一些深层次的误区往往导致难以察觉的逻辑错误和性能损耗。文本将深入剖析 equals hash 器约、自动装箱集合、快照与不可变集合这四大核心误区, 从理论机制层面澄清概念,提示风险。一、 eqo 与 hashcode 哈希集合的基石契约 在 java 集合框架中,尤其是哈希集合,如 hashmap hashset 对 象的识别机制完全依赖于 eqo 和 hashcode 方法的协通工作,二者之间必须遵守一个严格的契约 contract, 破坏这一契约将导致集合行为异常且错误,往往具有隐蔽性。 契约的核心原则是,如果两个对象通过 euclidean 方法比较相等,那么他们的 hash 扣必须返回相同的值,反之则不一定成立。哈希碰撞是允许的,此契约是哈希表数据结构能够正确工作的理论基础,破坏契约的后果是严重的。 在 hash 赛中,如果两个对象 euclidean 相等,但 hash 扣不同,它们将被视为不同的对象并存放在哈希表的不同位置。同, 导致 set 中出现重复元素,这根本违背了 set 集合的唯一性承诺。在 hashmap 中,则会表现为查找失败。使用一个键 key 成功存入之后, 若使用另一个 eq 相等的键 t 二进行查找,可能会返回 no。 这是因为 hashmap 首先根据 t 二的 hashcode 定位到特定的哈希桶,如果 hashcode 不 一致,即使 hashcode 返回处查找也会在错误的桶中进行,从而失败。更隐蔽的风险在于使用可变对象作为键, 若键对象的 hashcode 计算依赖于可变字段,在对象存入 hashmap 后,修改该字段将导致其哈希值改变, 这使得对象失踪,无法通过原件或新件找到对应的值,因为对象仍留在旧的哈希桶中,而查找操作在新桶中进行。 因此,最佳实践要求必须同时重写一括和 hash 扣的方法,并使用完全相同的一组字段进行计算。优先使用不可变字段参与计算,避免因对象状态改变而破坏其约。 使用 objects euclidean 和 objects hash 等工具方法进行实现,可以有效确保空安全、 no safety 和一致性。二、自动装箱隐形成本与空指真风险 自动装箱 auto boxing 和拆箱 boxing 是 java 的 语法堂,但其背后的影视转换带来了性能开销和运行式风险,理解其机制直观重要。 自动装箱的本质是编辑器在编辑时自动插入了 into 这儿 file 物等调用,而拆箱则是调用了 in file 物等方法。 每次装箱都可能创建新的包装类对象。在循环或大规模数据操作中,频繁的装箱会导致大量短期存在的对象被创建,显著增加堆内存压力和垃圾回收 g c 的 负担。虽然 java 为常用范围,如 into 是 一百二十八到一百二十七的包装类提供了缓存优化, 但超出此范围的数值每次装箱都会创建新对象,无法享受缓存优化。包装类对象是可以为 null 的, 而基本类型不能当。对一个值为 null 的 包装类引用进行自动拆箱时,虚拟机会尝试调用七 x x x file u 方法,如 in file u, 从而必然抛出 no pointer exception。 这不是一个偶然的 bug, 而是机制决定的必然结果。例如,从 map string integer 中获取一个不存在的键,返回 no, 若直接赋给 int 变量 mp 一 立即发生 在性能敏感场景,优先选择基本类型数组,如雁荡飞。包装类型集合,如 listintigator。 对 可能返回 no 的 包装类型,在拆箱前必须进行显示检查。在循环体内特别需要注意避免重复的装箱拆箱操作。三、集合快照误区试图与副本的语义辨析 集合 a p i 中提供了获取子集或试图的方法,但他们返回的往往是试图育儿,非独立副本 copy。 对 二者语义的误解会引发数据一致性问题。 类似色不类似。返回的是原列表的一个动态试图,而非数据的静态快照。对子试图的修改,如设置元素排序,会直接反映到原列表上,反之亦然。更关键的是,在子试图存在期间,如果直接修改了原列表的结构,如添加或删除元素, 那么子试图的后续操作甚至是简单的便利,可能会立即抛出 concurrent modification exception。 这是因为子试图的内部状态依赖于原列表的结构。原列表的结构性修改破坏了这种依赖。 raise as list 返回的列表是基于原始数组的固定大小 fix size。 试图 虽然可以修改已存在元素的值 set 操作,但不能进行改变列表大小的操作,如 i 核认木,否则会抛出 unsupported operation exception, 这源于其内部实现是适配数组的,而数组长度不可变。 使用这类 a p i 时,核心在于明确其返回的是试图还是副本。需要独立操作时,应立即创建副本 list, original list, sublist from to。 在 a p i 设计时,应在文档中清晰说明方法返回的是仕图还是副本,避免误导使用者。 四、不可变集合不同层次的不可变性不可变集合一词在不同语境下含义不同,其不可变性的强度和保证存在显著差异。理解这些层次是进行防御性编程的关键。 collections and modifiable list, set up 等方法返回的是原集合的指读式图, 它本身不能通过其引用进行修改操作,但原集合的修改会直接反映到该视图中。这是一种浅不可变,其不可变性依赖于原集合引用不被恶意修改。 java 九家引入的 list, set up, map 等工厂方法创建的是深度不可变的集合实力, 他们不依赖任何外部集合。内部实现经过优化并拒绝一切修改操作,是最高级别的不可变。任何修改尝试都会抛出 unsupported operation exception。 基于此,健壮的 a p i 设计需要遵循防御性编程原则。在接收外部集合参数时,如果需要在内部存储,不应直接存储外部引用, 而应创建其防御性副本。例如, this internal list 等于 new ray list, external list, 以隔绝外部修改的影响。 在向外部暴露内部集合时,如果不希望调用者修改内部状态,应返回其不可修改的仕途 collections and modifiable list, internal list 或真正不可变集合的副本 总结构建健状集合操作的理论基时,要编辑健壮高效的扎了集合代码,必须深刻理解其背后的理论机制。 企约优先将遵守 euclos hash 扣企约视为不可动摇的铁律。明确语意,透彻理解每个集合 api 的 精确行为,清晰区分,试图与副本真正不可辨与止读,试图 防御性编程,对外部输入保持警惕,对内部状态进行保护。性能意识认识到自动装箱等语法堂背后的隐形成本,最危险的错误往往隐藏在最熟悉的代码中。对集合 api 背后理论的深入理解是规避这些陷阱,构建可靠应用程序的坚实基础。