java ArrayList源码分析(深度讲解)
ArrayList类的底层实现
ArrayList类的断点调试
空参构造的分步骤演示(重要)
带参构造的分步骤演示
一、前言
大家好,本篇博文是对单列集合List的实现类ArrayList的内容补充。之前在List集合的万字详解篇,我们只是拿ArrayList演示了List接口中的常用方法,并没有对它进行深究。但这正是我们今天要做的内容。
up会利用断点调试(Debug)来一步一步地给大家剖析ArrayList底层的扩容机制到底是如何实现的。空参构造和带参构造初始化ArrayList对象up都会演示到。但是,重点是空参构造器构造ArrayList对象后,底层扩容机制的详细实现。
注意 : ①解读源码需要扎实的基础,比较适合希望深究的同学;②不要眼高手低,看会了不代表你会了,自己能全程Debug下来才算有收获;③点击文章的侧边栏目录或者前面的目录可以进行跳转。④本篇博文对ArrayList源码的解读基于JDK17.0的版本,虽然不是主流的JDK8.0,但是经过对比不难发现其底层原理大同小异,所以,就算你用的不是高版本的JDK,本文也对打牢你的基础有一定帮助。良工不示人以朴。感谢阅读!
二、ArrayList类的底层实现
1.ArrayList类在底层是由数组来实现的,ArrayList类源码中维护了一个Object类型的数组elementData,用于存储ArrayList集合中的元素。
关于transient关键字,transient本身是转瞬即逝的意思,如下 :
被transient关键字修饰的程序元素不可被序列化。
2.当我们使用空参构造来创建ArrayList类对象时,则elementData数组的初始容量为0,第一次添加元素时,将该数组扩容为10,如需再次扩容,则将elementData数组的当前容量扩容为1.5倍。
3.如果使用指定数组初始容量大小的带参构造来创建ArrayList类对象,则elementData数组的初始容量即为传入形参的指定容量,如果需要扩容,则直接将该数组当前容量扩容至1.5倍。
三、ArrayList类的断点调试
0.准备工作 :
up以一下代码为演示,来进行Debug操作,(分别在第13行和第20行设置断点),代码如下 :
package csdn.knowledge.api_tools.gather.list;import java.util.ArrayList;/*** @author : Cyan_RA9* @version : 21.0*/
public class ArrayList_Demo {public static void main(String[] args) {//演示 : Debug ArrayList空参构造,以及数组扩容的全流程。//1.通过空参构造创建一个ArrayList类对象ArrayList arrayList = new ArrayList();System.out.println("使用空参构造创建的集合对象 = " + arrayList);//2.利用for循环向集合对象中添加10个元素。(0~9)for (int i = 0; i < 10; i++) {arrayList.add(i); //此处有自动装箱}System.out.println("添加十个元素后,当前集合 = " + arrayList);//3.如果按照我们的理论,在向集合对象中添加第11个元素时,底层的数组需要扩容。(1.5倍)arrayList.add("这是集合的第十一个元素捏.");arrayList.add("这是集合的第十二个元素捏.");arrayList.add("这是集合的第十三个元素捏.");arrayList.add("这是集合的第十四个元素捏.");arrayList.add("这是集合的第十五个元素捏.");//4.再次测试ArrayList类底层数组的扩容机制。 (10 ---> 15 ---> 22)arrayList.add("这是集合的第十六个元素捏.");System.out.println("添加十六个元素后,当前集合 = " + arrayList);}
}
1.空参构造——分步骤Debug(详细阐释)(重要)
0°开始Debug。
首先,我们进入Debug界面,并在无参构造调用行(此处为第13行),跳入ArrayList类无参构造,如下GIF图所示 :
1°初始化底层elementData数组为空数组。
跳入ArrayList无参构造,我们可以看到,它将用于存储集合元素的elementData数组进行了初始化。等号后面的这一大堆直译过来就是 "默认容量空的elementData数组" ,可以根据Ctrl + b/B查看ArrayList中该常量的源码,如下 :
可以看到,这个所谓的 "默认容量空的elementData数组" 确实名副其实,真是一个空数组,而且与ArrayList中用于存储集合元素的elementData数组一样都是Object类型。
接下来,我们跳出这个无参构造,进入for循环,并跳入第一个元素的add方法。
2°对add方法中的实参进行自动装箱。
第一次跳入add方法,会跳到valueOf方法中,对要添加的int类型进行装箱操作。如下图所示 :
我们不用管他,直接选择跳出,并准备第二次跳入add方法。
第一次跳出add方法后,该行代码仍然标注着高亮,表示此行代码还未执行完毕。我们第二次跳入add方法。
3°进入add方法底层。
第二次跳入add方法,我们来到了真的"add"方法,如下 :
其中,形参列表的"e"代表了你要添加的元素,根据我们上面给出的代码,for循环要向集合对象中添加0~9这十个元素,这是for循环第一次循环,要添加元素0,因此可以看到,此时 e = 0。
modCount属性用于保存你修改集合的次数,用来防止有多个线程修改它,多个线程修改时会抛出异常,这里我们不用管它。
可以看到,跳入的add方法中,还调用了一个形参列表不一样的"add"方法,我们暂时将这个"add方法中的add方法"称为内层add方法,内层add方法传入了三个形参,分别是"当前要添加的元素(此时e = 0),"初始化后的空数组elementData", 以及"当前集合中的元素个数size"。显然,内层add方法更加底层,当然,我们要追进去看看。
4°进入grow方法。
更底层的add方法(内层add方法),如下 :
可以看到,内层add方法中首先是一个if条件语句的判断,if条件语句结束后,才将e(当前e = 0)添加到了elementData数组中,并且为size属性 + 1(集合中的元素个数从0 --> 1,多了1个)。
但是,要知道我们的elementData数组之前可是初始化为了一个空数组阿,是啥都放不下的。显然,它在if条件语句中被动了手脚。我们来仔细看看这个if条件语句,判断条件是"当前集合中的元素个数是否等于数组的大小(长度)", 啥意思呢?就是说咱们现在不是正要向集合中添加元素么——(前面我们也看了,向ArrayList集合中添加元素,底层其实就是向ArrayList类中的elementData数组中添加元素)——如果判断条件成立,说明当前集合的元素个数与底层elementData数组的大小相等,也就是说数组已经满了,再想添加元素就要扩容🌶!
我们再回到实际,因为我们正在添加第一个元素,所以目前集合中元素的个数 = 0;底层数组为空,所以目前数组的大小 = 0,0 = 0,满足判断条件。所以,肯定要进入这个grow方法。grow方法,见名知意,就是对数组进行扩容的方法。"elementData = grow();",显然grow方法最终是返回一个Object类型的数组,这样才能赋值给elementData数组(其实就是更改了elementData的引用)。我们也是一路高歌,继续跳入grow方法中看看是咋回事。如下图所示 :
果然,grow方法返回了一个Object类型的数组。不过比较操蛋的是grow方法与前面的add方法类似,都™长了层包皮,没关系,我们继续追进去看看。(注意 : 既然外层的grow方法是返回Object类型的数组,说明return后面的grow方法肯定要是返回一个Object类型的数组。不过,内层的grow方法传入了一个形参是size + 1,size表示当前集合中元素的个数,第一个元素还没加进去,所以size + 1 = 0 + 1 = 1.)
5°进入grow方法底层。
内层的grow方法如下图所示 :
是不是傻眼了😂?什么玩意儿!别急,不要因为走得太远而忘记了我们为什么出发。我们一路追追追,只有一个目的——elementData数组目前为空,要添加第一个元素0进入,必须先扩容,而内层grow方法要返回一个Object类型的数组。所以,现在我们就找内层grow方法中有扩容操作的代码就行,别的暂时不看!
可以看到,首先第一条语句,是把当前数组的长度(0)赋值给了oldCapacity变量,这条语句有啥用呢,我们暂时不管。继续往下看,有一个if-else的多重条件语句,判断条件是"当前数组的长度是否大于0,或当前数组是否不为空",显然,elementData数组目前还是个空呢,两个条件均不满足,于是if语句中的内容现在不执行,直接跳到else语句。
else语句中,注意观察,出现了return语句!显然,这里很可能就是返回Object类型数组的地方。我们来看看是不是。
返回的Object数组的长度,是调用Math类的max函数的返回值,我们之前在常用类中讲过Math类,max函数可以返回两个数中的较大值。第一个数"DEFAULT_CAPACITY",直译过来就是"默认的容量",其实不用我说你们也能猜出来,和我们一开始初始化elementData数组时遇到的那"一大堆"一个拉撒,一丘之貉,这里也不追进去看了,但是注意,当我们鼠标悬停在这个常量上面是,会显式它的值 = 10;第二个数是minCapacity(最小容量),这个变量是哪里来的呢?欸,你往上翻翻看,不就是内层grow函数的形参么!那这个形参是哪儿来的呢?欸,瞧你这记性,翻到上面外层grow函数,可以看到当时我们传入的是(size + 1),即0 + 1, 等于 1。minCapacity表示——要把该元素添加进去,所需数组的最小长度,我们这才添加第一个元素阿,那minCapacity当然是1了。那两个数中,显然10 > 1,内层grow函数最终返回的就是"new Object[10]"。
6°逐层返回,第一次扩容elementData数组完毕(0 ——> 10)。
然后,内层grow函数执行完毕,返回外层grow函数,如下 :
然后,再返回内层add函数,如下 :
接着,内层add函数中,if条件语句中的方法将要执行完毕。
可以看到,右上角的提示信息中,已经由原来的"Object[0]" 变成了"Object[10]",这也可以验证我们上文"ArrayList类的底层实现"中提到的——当我们使用空参构造来创建ArrayList类对象时,则elementData数组的初始容量为0,第一次添加元素时,将该数组扩容为10。
接着往下执行,可以看到第一个元素0已经被添加进了elementData数组,如下图所示 :
内层add函数执行完毕,返回外层add函数,如下 :
可以看到,size(当前集合中元素的个数)已经由0变成了1,说明我们添加成功了。接着返回到我们的测试类中,如下 :
7°向集合添加第二个元素(不需要扩容)。
第一次扩容完成后,elementData数组的长度由0扩到了10,因此,for循环中后续几个元素的添加都不再需要扩容。以第二个元素的添加为例,up再来带大家过一遍,加深印象,当然,这次主要是体验一下流程,不会像第一次一样那么细了。
如下图所示,随着循环变量i的自增,for的第一次循环顺利结束,第二次循环开始,向集合中添加第二个元素1 :
可以看到,首先还是老规矩,先将int类型的数据1做了装箱处理。
接着,我们再次跳入外层add方法,如下图所示 :
注意看右上角,e(即我们要添加的元素)已经是1了,因为代码里的for循环中,就是要向集合添加0~9这十个元素,现在是第二个元素1。继续往下执行,我们跳入内层add方法,如下图所示 :
首先注意看右上角,s变量(当前集合中的元素个数)变成了1,因为我们之前已经添加过1个元素了么。执行内层add方法,if条件判断语句中,当前集合中元素的个数显然小于elementData数组的长度(1 < 10),因此不进入grow方法,而是直接将第二个元素放入elementData数组中索引为1的位置,同时再对size变量进行加1操作。如下图所示 :
可以看到,第二个元素也已经成功添加到了elementData数组中,而且size变量也变成了2。
之后,依然是先跳出内层add方法,再跳出外层add方法,一直跳回去,如下GIF所示 :
8°将集合中的元素添加到10个元素。(第一个临界点)
之后的8次循环均与第二次循环相同,我们直接一笔带过,如下GIF图所示 :
9°集合的第二次扩容开始。
因为第一次对elementData数组扩容,默认只从0扩到了10。而for循环结束后,我们已经向集合对象中添加了十个元素,即ArrayList底层的elementData数组已被装满。现在我们想添加第十一个元素,就需要对elementData数组进行第二次扩容了。
我们还是先跳入add方法,看看会发生什么,如下图所示 :
可以看到,由于从第十一个元素开始,均为String类型,因此不需要"装箱"的操作,而是直接跳到了外层add方法。同样地,我们继续跳入内层add方法,如下图所示 :
注意看内层add方法中的if条件语句,IDEA已经给出了提示" = true",没错,当前集合中已有的元素个数s = 10,而当前elementData数组的长度 = 10,10 = 10,满足判断条件。因此这时候要第二次进入grow方法对数组进行扩容了。好,我们跳入外层grow方法,如下图所示 :
注意看这时内层grow方法的实参,minCapacity : size + 1,即要想把第十一个元素放入集合中,所需集合的最小容量是size + 1 = 11,即所需elementData数组的最小长度是11。好的,接下来我们跳入内层grow方法,如下 :
仍然是先将当前elementData数组的长度(10)赋值给了oldCapacity变量;接着,if条件语句,判断条件是"当前数组的长度是否大于0,或当前数组是否不为空",与我们第一次扩容不同,第一次是从0 ——> 10,现在elementData数组已经不为空了。因此,可以看到IDEA也是再次给出了提示" = true"。
接着往下走,if语句体中,我们发现它由两部分构成。
首先,第一部分,是一个newCapacity局部变量的定义,见名知意,显然它代表了我们内层grow方法要返回的新数组的长度。而为newCapacity赋值的是另一个底层方法newLength,我们先不管它,继续往下看。
第二部分,是一个return语句,显然,这个return语句中要返回扩容后的Object类型的新数组。
好的,框架清晰后,我们再看细节。既然第一部分要获取扩容后新数组的长度,那么显然我们是要追进去这个底层方法看看的。但是,进去之前,我们不妨先来看看它的形参列表,一共传入了三个数——①当前数组的长度;②所需新数组的最小长度 - 就数组长度(= 数组的最小增长量);③旧数组右移1位。这里稍微说一下这个"oldCapacity >> 1"是什么意思,其实涉及到了C语言的一些基础——位运算。这里面涉及到了二进制的知识,当然我们也就废话少说,直接告诉你结论:"<<"表示左移,每左移1位相当于*2;">>"表示右移,每右移一位相当于/2。那么此处的"oldCapacity >> 1"就表示将旧数组的长度的值(10)右移一位,相当于10 / 2,= 5。因此,此处调用的newLength方法的实参就分别为①10;②1;③5。
OK,搞清楚这些后,我们追进去newLength方法一探究竟,如下图所示 :
可以看到,newLength方法内部的结构还是比较清晰的 : 一个变量的赋值语句,一个if-else的复合条件语句。我们一个一个来看。
首先,"prefLength",直译过来就是"预设长度"的意思,见名知意,显然它和我们新数组的长度关系密切。继续看代码,为prefLength赋值的是"旧数组的长度 + 数组最小增长量和prefGrowth之间的最大值"。这个prefGrowth不知道大家能不能想到,前面我们传入三个实参的时候,第三个实参不是"oldCapacity >> 1"吗?欸,就是它!我们也可以将鼠标悬停在它上面,IDEA会显示出它的值,如下图所示 :
可以看到,确实 = 5,和我们前面推理的一致。而minGrowth和prefGrowth一个1,一个5,肯定5大呀。所以,最终赋值给prefLength的值 = 10 + 5 = 15。欸,有没有发现,正好验证了我们前文中所提到的——当我们使用空参构造来创建ArrayList类对象时,如需再次扩容,则将elementData数组的当前容量扩容为1.5倍。估计到这里大家也能理解为什么是1.5倍了,就和前面那个位运算">> 1"有关,本身再加上它的一半,可不是1.5倍么。
继续往下执行,if条件语句中,要判断新数组的长度是否合法,要满足大于0并且小于一个"一大堆"的玩意儿。我们还是将鼠标悬停在那"一大堆"上,看看它的值是多少,如下所示 :
哎呀我去,行了,不用看了,咱就一个测试的数组,能跑那么大吗?
🆗,newLength方法结束,返回内层grow方法。如下图所示 :
还是那句话,不要因为走得太远而忘记了我们为什么出发。newLength方法只是为了获取新数组的长度,我们也进去看了,也知道是个啥了,就是旧数组的1.5倍。最后返回新数组,还是内层grow方法来完成。
此处用到了Arrays的copyOf方法。copyOf方法大家不用深究,只需要知道它的功能是将原数组中指定长度的内容拷贝到新数组中,并且,若指定的长度大于原数组长度,则多出来的部分以默认值填充,最终返回的是新数组。使用该方法扩容数组,可以在保留原数组中内容的同时,又达到扩容的目的。此时,我们新数组的长度15显然大于原数组长度10,因此我们猜测,最后返回的数组的效果就是[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, null, null, null, null, null]。(第11个元素未加入前)
我们接着Debug,跳出内层grow方法,如下所示 :
接着再跳出外层grow方法,如下 :
在内层add方法中,我们继续执行,如下如所示 :
可以看到,扩容后elementData数组的情况与我们预测的一模一样。多出的4个元素均以默认值null取代了。而且,当前集合中元素的个数也从10变成了11。
10°集合的第二次扩容结束。
接着,我们跳出内层add方法,一直跳到演示类的代码中,如下GIF所示 :
11°将集合中的元素添加到15个。(第二个临界点)
之后的第12到第15个元素的添加,与第十一个元素的添加大同小异,只不过在内层的add方法中,我们不再进入grow方法(即数组不需要扩容),而是直接将元素添加到elementData数组中。因此,接下来4个元素的添加,我们一笔带过(有兴趣的小伙伴儿可以自己下去Debug一下),如下GIF演示图 :
12°集合的第三次扩容开始。
第二次扩容结束后,底层的elementData数组由10扩容到了15。而经过我们的一通操作过后,elementData数组又满了。现在我们想向集合中添加第16个元素,就要进行集合的第三次扩容。从第二次扩容开始,之后的每次扩容在底层都与第二次扩容原理一样,并且每次都扩容到当前集合容量的1.5倍。
好的,同样地,我们先跳入外层add方法。如下 :
接着,再跳入内层add方法。如下 :
可以看到,if条件语句的判断中,又提示" = true"了,因此,我们还要再跳入外层grow方法中。如下 :
显式当前集合中元素的个数为size = 15。继续跳入内层grow方法 :
内层for循环中if语句的判断条件也为true。继续跳入newLength方法 :
可以看到,新数组的预设长度是22,恰好等于15 + 15 / 2 = 22,也再次印证我们之前的结论——扩容到1.5倍。
13°集合的第三次扩容结束。
好滴,接着我们再逐层返回,一直跳回到内层add方法中,如下GIF图所示 :
继续执行内层add方法中的代码,如下图所示 :
可以看到,elementData数组的长度成功地由15扩容到了22。
接下来,我们逐层返回,一直返回到演示类中,如下GIF图所示 :
🆗,无参构造的分步骤Debug演示,就到这里结束了。相信只要你把这一套流程吃透,自己可以Debug下来。那么其他情况下的扩容流程你也可以轻松举一反三了。
3.带参构造——分步骤Debug(详细阐释)
0°前言 :
如果利用带参构造来初始化ArrayList对象,那么它底层的扩容机制,其实与无参构造初始化ArrayList对象时大同小异。唯一不同的一点在于,使用带参构造初始化ArrayList对象,底层的elementData数组在一开始不会置空,而是将其初始化为调用带参构造时中实参指定的长度。之后的扩容流程与空参构造初始化对象时无异。因此,up这里就不会像之前空参构造时演示得那么细了。
up以ArrayList_Demo2为例,代码如下 : (13行和22行设置断点)
package csdn.knowledge.api_tools.gather.list;import java.util.ArrayList;/*** @author : Cyan_RA9* @version : 21.0*/
public class ArrayList_Demo2 {public static void main(String[] args) {//演示 : 测试通过带参构造初始化ArrayList集合时,其底层的数组扩容机制。//1.创建ArrayList集合对象ArrayList arrayList = new ArrayList(4);System.out.println("刚创建的集合 = " + arrayList);//2.向集合中添加元素(先来4个)for (int i = 0; i < 4; i++) {arrayList.add(i); //此处涉及到了自动装箱。}//3.再次向集合中添加元素。(扩容:4 ——> 6)arrayList.add("这是第五个元素捏");arrayList.add("这是第六个元素捏");System.out.println("添加六个元素后,集合 = " + arrayList);//4.再次向集合中添加元素。(扩容:6 ——> 9)arrayList.add("这是第七个元素捏");System.out.println("添加七个元素后,集合 = " + arrayList);}
}
1°开始Debug。
如下图所示,进入Debug界面:
2°集合的第一次扩容(初始化)。
接着,我们跳入ArrayList的带参构造,如下图所示 :
可以看到,elementData数组被初始化为了长度为4的数组,4正是我们调用带参构造时指定的长度。其实,可以看到ArrayList类的该带参构造是一个if-else if-else的符合条件语句。因为我们指定的长度4 > 0,所以它直接进入if控制的语句中,即将一个长度为4的新数组赋值给了elementData数组(其实就是改变了elementData引用的指向)。如果带参构造传入的实参为0,它就会当作空参构造来执行。如果 < 0,就会抛出一个异常对象。
接着,我们跳出带参构造。如下图所示 :
注意,我们可以看到底层的elementData数组已经被初始化为4的长度。
3°向集合中添加第一个元素。
继续Debug,跳入for循环的add方法。同之前一样,这里我们要添加int类型的数据进入几何,底层会有装箱的过程,我们直接跳出即可,如下GIF图所示 :
接着,我们再次跳入add方法中,如下图所示 :
可以看到,这不就是上面演示的外层add方法吗?是的,也就是说,从elementData数组的初始化后,带参构造集合在扩容时,底层所有的操作都同无参构造一致。
因此,下面的演示up就多以GIF图来呈现了。其实只要上面那个无参的你会了,这个过一遍就🆗了。
接下来,我们完成集合中第一个元素的添加,如下GIF图所示 :
4°将集合中的元素添加到4个。(第一个临界点)
由于底层的elementData数组初始化时长度为4。因此前四个元素的添加均无二致。我们一笔带过就好,如下GIF图演示 :
5°集合的第二次扩容。
当前集合的元素已达4个,要想向集合中添加第五个元素,需要再次进行扩容。扩容机制同上面的演示一样,这里不再赘述。如下GIF图演示 :
6°将集合中的元素添加到6个。(第二个临界点)
如下GIF图所示 :
7°集合的第三次扩容。
经过两次扩容,集合的容量从0 ——> 4 ——> 6,那么第三次扩容,就应该是6的1.5倍 = 9了。我们来看看是不是,如下GIF图所示 :
可以看到,第三次扩容后,elementData数组的长度确实从6扩到了9。如下图所示 :
四、总结
🆗,以上就是我们ArrayLIst源码分析的全部内容了。其实,如果你看过JDK8.0的源码,不难发现两个在底层的实现上有着异曲同工之妙。当然,就up个人主观来看,两者的差异主要体现在了方法的形参和方法的位置上。话说回来,非常建议大家跟着up一起Debug一下,看源码,通过Debug去了解源码的结构,有助于提升大家对于源码的阅读力。感谢阅读!
相关文章:
java ArrayList源码分析(深度讲解)
ArrayList类的底层实现ArrayList类的断点调试空参构造的分步骤演示(重要)带参构造的分步骤演示一、前言大家好,本篇博文是对单列集合List的实现类ArrayList的内容补充。之前在List集合的万字详解篇,我们只是拿ArrayList演示了List…...
【网络编程】零基础到精通——NIO基础三大组件和ByteBuffer
一. NIO 基础 non-blocking io 非阻塞 IO 1. 三大组件 1.1 Channel & Buffer channel 有一点类似于 stream,它就是读写数据的双向通道,可以从 channel 将数据读入 buffer,也可以将 buffer 的数据写入 channel,而之前的 st…...
操作系统 - 1. 绪论
目录操作系统基本概念概念特征功能操作系统的分类与发展手工操作单道批处理系统多道批处理系统分时系统实时系统操作系统的运行环境CPU 运行模式中断和异常的处理系统调用程序的链接与装入程序运行时内存映像和地址空间操作系统的体系结构操作系统的引导操作系统基本概念 概念…...
详谈parameterType与resultType的用法
resultMap 表示查询结果集与java对象之间的一种关系,处理查询结果集,映射到java对象。 resultMap 是一种“查询结果集---Bean对象”属性名称映射关系,使用resultMap关系可将将查询结果集中的列一一映射到bean对象的各个属性&#…...
【Linux】进程概念、fork() 函数 (干货满满)
文章目录📕 前言📕 进程概念📕 Linux下查看进程的两种方法方法一方法二📕 pid() 、ppid() 函数📕 fork() 函数、父子进程初识再理解📕 fork做了什么📕 如何理解 fork 有两个返回值📕…...
【动态规划】最长上升子序列、最大子数组和题解及代码实现
Halo,这里是Ppeua。平时主要更新C语言,C,数据结构算法......感兴趣就关注我吧!你定不会失望。 🌈个人主页:主页链接 🌈算法专栏:专栏链接 我会一直往里填充内容哒! &…...
Ajax进阶篇02---跨域与JSONP
前言❤️ 不管前方的路多么崎岖不平,只要走的方向正确,都比站在原地更接近幸福 ❤️Ajax进阶篇02---跨域与JSONP一、Ajax进阶篇02---跨域与JSONP(1)同源策略1.1 什么是同源1.2 什么是同源策略(2)跨域2.1 什…...
C 语言编程 — 线程池设计与实现
目录 文章目录目录线程池(Thread Pool)tiny-threadpool数据结构设计Task / JobTask / Job QueueWorker / ThreadThread Pool ManagerPublic APIsPrivate Functions运行示例线程池(Thread Pool) 线程池(Thread Pool&am…...
并发编程要点
Java并发编程中的三大特性分别是原子性、可见性和有序性,它们分别靠以下机制实现: 原子性:原子性指的是对于一个操作,要么全部执行,要么全部不执行。Java提供了一些原子性操作,例如AtomicInteger等…...
HDFS黑名单退役服务器
黑名单:表示在黑名单的主机IP地址不可以,用来存储数据。 企业中:配置黑名单,用来退役服务器。 黑名单配置步骤如下: 1)编辑/opt/module/hadoop-3.1.3/etc/hadoop目录下的blacklist文件 添加如下主机名称&…...
基于stm32智能语音电梯消毒系统
这次来分享个最近做的项目,stm32智能语音电梯消毒系统功能说明:在电梯,房间,客道区域内,检测到人,则执行相关动作!例如继电器开关灯,喷洒酒精等行为。手机app/微信小程序可以控制需要…...
FreeRTOS系列第1篇---为什么选择FreeRTOS?
1.为什么学习RTOS? 作为基于ARM7、Cortex-M3硬件开发的嵌入式工程师,我一直反对使用RTOS。不仅因为不恰当的使用RTOS会给项目带来额外的稳定性风险,更重要的是我认为绝大多数基于ARM7、Cortex-M3硬件的项目,还没复杂到使用RTOS的地…...
基于.NET Core内置浏览器窗体应用程序界面框架
更多开源项目请查看:一个专注推荐.Net开源项目的榜单 平常我们在做项目过程中,桌面软件具备操作高效、利用本地计算机做一些复杂运算、或者设定快捷操作等优势,但是桌面软件也有很多缺点,比如升级问题、系统兼容问题、系统bug排查…...
【数据结构初阶】一文带你学会归并排序(递归非递归)
目录 前言 递归实现 代码实现 非递归实现 代码实现 总结 前言 归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。 作为一种典型的分而治之思想…...
Simulink壁咚(一)——What and How
目录 一、前言 二、Simulink 知多少 三、滤波算法 四、Model Verification 五、Model Coverage 六、Simulink测试实例 七、Simulink Test 八、Test Manager 九、Test Harness 十、 学习 一、前言 Simulink从2017b以后更加工程化和实用化,基于MBD的功能日趋…...
【PyTorch】Pytorch基础第0章
本文参加新星计划人工智能(Pytorch)赛道:https://bbs.csdn.net/topics/613989052 这是目录PyTorch的简介PyTorch 构建深度学习模型的步骤搭建pytorch使用环境PyTorch的简介 PyTorch 是一个开源的机器学习框架,由 Facebook 的人工智能研究院(…...
Android学习总结
积累熟练掌握 Java 语言,面向对象分析设计能力,反射原理,自定义注解及泛型,多次采用设计模式重构公司项目;熟练掌握 IVM 原理,反射,动态代理以及对 ClassLoader 热修复有比较深的理解࿱…...
虚拟机ubuntu安装samba服务
安装samba apt-get install samba 新建一个共享目录 mkdir /home/l/work chmod 777 /home/l/work 配置服务 配置 /etc/samba/smb.confsudo smbpasswd -a l(添加用户名名称) 防火墙关闭 Ubuntu中 我们使用命令查看当前防火墙状态; sudo ufw status inactive状态是防火墙关闭…...
开发板中的内存压力测试,你了解多少?
1. 测试目的内存压力测试的目的是评估开发板中的内存子系统性能和稳定性,以确保它能够满足特定的应用需求。开发板通常用于嵌入式系统、物联网设备、嵌入式智能家居等场景,这些场景对内存的要求通常比较高。其内存压力测试的主要目的有:1.对确…...
MATLAB | 这些花里胡哨的热图怎么画
好早之前写过一个绘制相关系数矩阵的代码,但是会自动求相关系数,而且画出来的热图只能是方形,这里写一款允许nan值出现,任意形状的热图绘制代码,绘制效果如下: 如遇到bug请后台提出,并去gitee下…...
Java开发的一些编码建议
1、无论是类、方法、字段、变量,尽可能的限制他们的作用范围,可以避免出现不必要的错误;同时虚拟机也能有更大的优化空间。 2、错误越早发现越好,编译时发生错误比在运行时发生错误好。而且编译时错误能更好的定位问题所在。 这…...
【YOLOv8/YOLOv7/YOLOv5/YOLOv4/Faster-rcnn系列算法改进NO.59】引入ASPP模块
前言作为当前先进的深度学习目标检测算法YOLOv8,已经集合了大量的trick,但是还是有提高和改进的空间,针对具体应用场景下的检测难点,可以不同的改进方法。此后的系列文章,将重点对YOLOv8的如何改进进行详细的介绍&…...
C++STL set/multiset容器 构造和赋值 大小和交换 插入和删除 查找和统计
文章目录set/multiset容器1 set容器 基本概念2 set容器 构造和赋值3 set容器 大小和交换4 set容器 插入和删除5 set容器 查找和统计set/multiset容器 1 set容器 基本概念 简介: 所有元素都会在插入时会被自动排序,例如,在set容器放入元素1、…...
产品研发项目进度管理软件工具有哪些推荐?整理10款最佳进度管理软件
项目进度管理是确保项目按时完成的关键过程,使用合适的项目进度管理工具能确保帮助项目管理者实时了解和控制项目的进展情况,及时发现和解决问题,减少项目风险,提高项目效率和管理水平。这里将整理出国内外最受欢迎的10款项目进度…...
「ML 实践篇」分类系统:图片数字识别
目的:使用 MNIST 数据集,建立数字图像识别模型,识别任意图像中的数字; 文章目录1. 数据准备(MNIST)2. 二元分类器(SGD)3. 性能测试1. 交叉验证2. 混淆矩阵3. 查准率与查全率4. P-R 曲…...
从大专到测开,上海某字母站大厂的面试题,岗位是测开(25K*16)
简单介绍一句,大专出身,三年经验。跳了四次槽,面试了无数次,现在把自己的面试经验整理出来分享给大家,堪称必杀技! 1,一切从实际出发,对实际工作进行适当修饰 2,不会的简…...
【面试题】Python软件工程师能力评估试题(一)
文章目录前言应试者需知(一)Python 语言基础能力评估1、理解问题并完成代码:2、阅读理解代码,并在空白处补充完整代码:3、编写一个装饰器:exposer4、阅读代码并在空白处补充完整代码:5、自行用P…...
Java八股文(Java多线程面试题)
并行和并发的区别?(1)并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生;(2)并行是在不同实体上的多个事件,并发是在同一实体上的多个事件&#…...
小程序当前页面如何分享别的页面内容呢?
需求分析 因为功能的需要分为两点 他需要调转转发,并且有首页转发点击button按钮进行转发邀请好友帮忙助力,如何做到一个页面多种转发 如何区分,是button转发还剩右上角三个点转发呢? 通过onShareAppMessage()这个函数的事件…...
编写Java哪个编译器好
现在能够编写Java代码的工具简直不要太多,各种各样五花八门,但目前效率最高的还是Intellij Idea。但这个工具对于完全零基础的小白来说,第一次用起来是比较复杂的,因为它的功能太多了。这就好比你要学开车,如果上来就给…...
哈尔滨免费建站模板/个人网站注册平台
一.角色以及入口: 角色:系统管理员,开发人员 入口:服务管理--修改服务 二.页面元素检查: 对页面初始化的检查,即页面打开后,对页面不做任何操作时的元素检查。(破页;js错;demo对比等…...
wordpress禁用修订/免费友情链接交换平台
云计算职业转型之一:企业架构师 企业架构师在技术和各平台上是一个颇为具有代表性的角色,但那些准备转移至云的企业寻找的是有那些特定技术的员工。 请看下面的职业图表。有两条非常好的途径可供选择:公共云解决方案架构师和安全架构师。大…...
重庆大渡口营销型网站建设公司推荐/怎么自己做个网站
PMP(Project Management Professional)是项目管理专业人士资格认证,是由美国国际认证协会(PMI)发起的严格评估项目管理人员知识技能是否具有高品质的资格认证考试。目前已在全球190多个国家和地区得到了认可࿰…...
内容网站/技能培训有哪些
项目背景提要最近公司经常有测试,产品,开发人员需要我帮忙查看服务器上面发送的短信验证码来完成工作上的一些需求。我们的短信验证码由我们后台程序发出,调用第三方短信平台发送,这中间我们会将短信发送内容记录到日志文件&#…...
高校网站一般采用什么网页布局/互联网怎么赚钱
电脑A ip :192.168.1.186 电脑B ip :192.168.1.1 用电脑A 去链接电脑B的本地数据库 1.互相 ping 看能否访问 如图 (判断是否在一个局域网) 2.给电脑A授权创建用户 电脑B操作 允许用户myuser从ip为 192.168.1.186 的主机连接到mysql服务器&a…...
营口市网站建设/做搜索引擎优化的企业
1 centos 6.x 安装 MySQL 注意:需要root权限。 yum install -y mysql yum install -y mysql-server yum install -y mysql-devel 2 centos 7.x 安装 MySQL 注意:需要root权限。 1)安装前工作 // 1)检测系统是否已有MySQL rp…...