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去了解源码的结构,有助于提升大家对于源码的阅读力。 感谢阅读!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言函数调用栈

栈溢出&#xff08;stack overflow&#xff09;是最常见的二进制漏洞&#xff0c;在介绍栈溢出之前&#xff0c;我们首先需要了解函数调用栈。 函数调用栈是一块连续的用来保存函数运行状态的内存区域&#xff0c;调用函数&#xff08;caller&#xff09;和被调用函数&#xf…

Java8使用Lambda表达式(流式)快速实现List转map 、分组、过滤等操作

利用java8新特性&#xff0c;可以用简洁高效的代码来实现一些数据处理。1 数据准备1.1 定义1个Fruit对象package com.wkf.workrecord.work;import org.junit.Test;import java.math.BigDecimal; import java.util.ArrayList; import java.util.List;/*** author wuKeFan* date …

Stable Diffusion加chilloutmixni真人图片生成模型,AI绘图杀疯了

上期图文教程,我们分享过AI绘图大模型Stable Diffusion以及中文版本文心AI绘画大模型的基础知识以及代码实现,截至到目前为止。Stable Diffusion模型已经更新到了V2.1版本,其文生图大模型也越来越火,其在2022年底,由AI绘制的图片被荣为国际大奖,让大家对AI绘画大模型也越…

Node.js-----使用express写接口

使用express写接口 文章目录使用express写接口创建基本的服务器创建API路由模块编写GET接口编写POST接口CROS跨域资源共享1.接口的跨域问题2.使用cros中间件拒绝跨域问题3.什么是cros4.cros的注意事项5.cros请求的分类JSONP接口1.回顾jsonp的概念和特点2.创建jsonp接口的注意事…

请相信总有一扇门为你而开——社科院与杜兰大学金融管理硕士项目

考研人数每年都在递增&#xff0c;考研的竞争压力也逐年增长。考研话题也备受人们关注&#xff0c;初试&#xff0c;国家线&#xff0c;复试&#xff0c;考研的每一个关卡都会冲上热搜&#xff0c;引发热议。国家线公布后&#xff0c;有人欢喜有人忧。祝福成功上岸的学子们&…

【Leetcode——排序的循环链表】

&#x1f60a;&#x1f60a;&#x1f60a; 文章目录一、力扣题之排序循环链表二、解题思路1. 使用双指针法2、找出最大节点&#xff0c;最大节点的下一个节点是最小节点&#xff0c;由此展开讨论总结一、力扣题之排序循环链表 题目如下&#xff1a;航班直达&#xff01;&#…

有什么比较好的bug管理工具?5款热门工具推荐

工具再优秀&#xff0c;适合自己才最重要。 为尽量讲透这个问题&#xff0c;本文的行文结构我先整理如下&#xff1a; 1、为什么需要bug管理工具&#xff1f; 2、好的bug管理工具的标准是什么&#xff1f; 3、好的bug管理工具推荐&#xff08;5款&#xff09; 4、如何挑选适合…

雪花算法(SnowFlake)

简介现在的服务基本是分布式、微服务形式的&#xff0c;而且大数据量也导致分库分表的产生&#xff0c;对于水平分表就需要保证表中 id 的全局唯一性。对于 MySQL 而言&#xff0c;一个表中的主键 id 一般使用自增的方式&#xff0c;但是如果进行水平分表之后&#xff0c;多个表…

【python实操】用python写软件弹窗

文章目录前言组件label 与 多行文本复选框组件Radiobutton单选组件Frame框架组件labelframe标签框架列表框Listboxscrollbar滚动条组件scale刻度条组件spinbox组件Toplevel子窗体组件PanedWindow组件Menu下拉菜单弹出菜单总结针对组件前言 python学习之路任重而道远&#xff0…

chatgpt这么火?前端如何实现类似chatgpt的对话页面

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域优质创作者&#x1f61c;&#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;…

代码看不懂?ChatGPT 帮你解释,详细到爆!

偷个懒&#xff0c;用ChatGPT 帮我写段生物信息代码如果 ChatGPT 给出的的代码不太完善&#xff0c;如何请他一步步改好&#xff1f;网上看到一段代码&#xff0c;不知道是什么含义&#xff1f;输入 ChatGPT 帮我们解释下。生信宝典 1: 下面是一段 Linux 代码&#xff0c;请帮…

Linux命令之nano命令

一、nano命令简介 nano是一个小型、免费、友好的编辑器&#xff0c;旨在取代非免费Pine包中的默认编辑器Pico。nano不仅复制了Pico的外观&#xff0c;还实现了Pico中一些缺失&#xff08;或默认禁用&#xff09;的功能&#xff0c;例如“搜索和替换”和“转到行号和列号”。nan…

【面试题】如何避免使用过多的 if else?

大厂面试题分享 面试题库前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★地址&#xff1a;前端面试题库一、引言相信大家听说过回调地狱——回调函数层层嵌套&#xff0c;极大降低代码可读性。其实&#xff0c;if-else层层嵌套&#xff0c;如下图…

iOS-砸壳篇(两种砸壳方式)

CrackerXI砸壳呢&#xff0c;当时你要是使用 frida-ios-dump 也是可以的&#xff1b; https://github.com/AloneMonkey/frida-ios-dump frida-ios-dump: 代码中需要更改的&#xff1a;手机中的内网ip 密码 等 最后放到我的砸壳路径里&#xff1a; python dump.py -l查看应用…

【答疑现场】我一个搞嵌入式的,有必要学习Python吗?

【答疑现场】我一个搞嵌入式的&#xff0c;有必要学习Python吗&#xff1f; 文章目录1 写在前面2 一个结论3 Python在嵌入式领域能干啥事4 Python是用来干大事的5 友情推荐6 福利活动大家好&#xff0c;我是架构师李肯&#xff0c;一个专注于嵌入式物联网系统架构设计的攻城狮。…

【蓝桥杯嵌入式】ADC模数转换的原理图解析与代码实现(以第十一届省赛为例)——STM32G4

&#x1f38a;【蓝桥杯嵌入式】专题正在持续更新中&#xff0c;原理图解析✨&#xff0c;各模块分析✨以及历年真题讲解✨都在这儿哦&#xff0c;欢迎大家前往订阅本专题&#xff0c;获取更多详细信息哦&#x1f38f;&#x1f38f;&#x1f38f; &#x1fa94;本系列专栏 - 蓝…

Linux--多线程(1)

目录 一、概念 二、理解 三、创建、退出、合并进程 //man pthread_create //Compile and link with -pthread. //1.为什么没有fun函数&#xff1f; //2.加上sleep来改进 //3.线程结束会不会影响主线程运行&#xff1f; //4.那如果主线程比较少呢&#xff1f; 四、如何…

IP协议+以太网协议

在计算机网络体系结构的五层协议中&#xff0c;第三层就是负责建立网络连接&#xff0c;同时为上层提供服务的一层&#xff0c;网络层协议主要负责两件事&#xff1a;即地址管理和路由选择&#xff0c;下面就网络层的重点协议做简单介绍~~ IP协议 网际协议IP是TCP/IP体系中两…

RecyclerView流程学习

RecyclerView流程学习模块划分绘制流程onMeasuremLayout为nullmLayout开启自动测量未开启自动测量onLayoutonDrawonLayoutChildren缓存预加载滚动和fling模块划分 RecyclerView中根据其功能可以分为以下几个模块&#xff1a; Recycler mRecycler // 缓存管理者&#xff0c;fi…

yolov5的基本配置

yolov5的基本配置train.pydata.yaml数据集标签文件格式:总结train.py def parse_opt(knownFalse):parser argparse.ArgumentParser()parser.add_argument(--weights, typestr, defaultROOT / yolov5s.pt, helpinitial weights path)parser.add_argument(--cfg, typestr, defau…