【Java集合】ArrayList源码深度分析
参考笔记:
java ArrayList源码分析(深度讲解)-CSDN博客
【源码篇】ArrayList源码解析-CSDN博客
目录
1.前言
2. ArrayList简介
3. ArrayList类的底层实现
4. ArrayList的源码Debug
4.1 使用空参构造
(1)开始Debug
(2)初始化底层elementData数组为空数组
(3) 对add方法中的实参进行自动装箱
(4) 进入add方法
(5)进入ensureCapcityInternal 方法
(6) 进入grow方法
(7)逐层返回,第一次扩容elementData数组完毕(0 ——> 10)
(8)向集合中添加第二个元素(不需要扩容)
(9)将集合中的元素添加到10个(达到第一个临界点)
(10)集合的第二次扩容开始
(11)集合的第二次扩容结束
(12)将集合中的元素添加到15个(达到第二个临界点)
(13)集合的第三次扩容开始
(14)集合的第三次扩容结束
4.2 使用有参构造
(1)开始Debug
(2)集合的第一次扩容(初始化)
(3)向集合中添加第一个元素
....
5.add (E e) 方法源码
1.前言
本篇博文是对单列集合 List 的实现类 ArrayList 的内容补充。之前在 List 集合的详解篇,只是拿 ArrayList 演示了 List 接口中的常用方法,并没有对它进行深究。但这正是本文要做的内容
up会利用 Debug 来一步一步地剖析 ArrayList 底层的扩容机制到底是如何实现的。 重点是空参构造器构造ArrayList对象后,底层扩容机制的详细实现
注意 : ①解读源码需要扎实的基础,比较适合希望深究的同学; ② 不要眼高手低,看会了不代表你会了,自己能全程 Debug下来才算有收获; ③ 本篇博文对 ArrayList 源码的解读基于主流的 JDK 8.0 的版本
2. ArrayList简介
ArrayList 类是单列集合 List 接口的一个实现类,它的本质是一个可以动态修改的数组。 ArrayList 位于 java.util.ArrayList 包下,其类的定义如下:
3. ArrayList类的底层实现
① ArrayList 类在底层是由数组来实现的,ArrayList 类源码中维护了一个 Object 类型的数组 elementData,用于存储 ArrayList 集合中的元素
关于 transient 关键字,transient 本身有 "转瞬即逝、短暂的" 的意思,被 transient 关键字修饰的程序元素不可被序列化
② 使用空参构造来创建 ArrayList 类对象时,elementData 数组的初始容量为 0 。第一次添加元素时,将该数组容量扩容为 10 。如需再次扩容,则将 elementData 数组的当前容量扩大 1.5 倍
③ 使用指定数组初始容量大小的带参构造来创建 ArrayList 类对象时,elementData 数组的初始容量即为传入形参的指定容量。如果需要扩容,则直接将该数组当前容量扩大 1.5 倍
4. ArrayList的源码Debug
4.1 使用空参构造
用以下代码为演示,来进行 Debug 操作,代码如下 :
import java.util.ArrayList;
public class 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); //此处有自动装箱,即 int -> Integer}System.out.println("添加十个元素后,当前集合 = " + arrayList);//3.如果按照理论,在向集合对象中添加第11个元素时,底层的数组需要扩容 10 -> 15(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
首先,进入 Debug 界面,并在第 6 行的无参构造调用行,跳入 ArrayList 类无参构造,如下 GIF 图所示 :
(2)初始化底层elementData数组为空数组
跳入 ArrayList 无参构造,可以看到,它将用于存储集合元素的 elementData 数组进行了初始化。等号右边的 "DEFAULTCAPCITY_EMPTY_ELEMENTDATA" 直译过来就是 "默认容量空的elementData数组" ,可以根据 Ctrl + b/B 查看 ArrayList 中该常量的源码,如下 :
可以看到,这个所谓的 "默认容量空的elementData数组" 名副其实,确实是一个空的数组,而且与 ArrayLis t中用于存储集合元素的 elementData 数组一样都是 Object 类型
接下来,我们跳出这个无参构造,进入 for 循环,并跳入第一个元素的 add 方法
(3) 对add方法中的实参进行自动装箱
第一次跳入 add 方法,会先跳到 valueOf 方法中,对要添加的 int 类型进行装箱操作。如下图所示 :
这里不用管它,直接选择跳出,并准备第二次跳入 add 方法
第一次跳出 add 方法后,该行代码仍然标注着高亮,表示此行代码还未执行完毕。我们第二次跳入 add 方法
(4) 进入add方法
第二次跳入 add 方法,我们来到了真的"add"方法,如下 :
形参列表的 "e" 代表了当前要添加的元素,根据我们上面给出的代码,for循环要向集合对象中添加 0~9 这 10 个元素,这是 for 循环的第一次循环,要添加元素 0,因此可以看到,此时 e = 0
第一行调用的函数是 ensureCapcityInternal (minCapacity:size + 1) ,"size" 表示当前集合中的元素个数,此时的 size = 0 。该函数的作用是:确定是否需要对 elementData 数组进行扩容。因为我们当前正在执行添加操作嘛,所以这里传入的参数为 minCapacity: "size + 1 " 就表示要把该元素添加进去,所需的数组的最小长度
第二行就是简单的赋值操作了,把当前元素 e 添加到数组中,然后 size++
下面我们进入 ensureCapcityInternal (minCapacity:size + 1) 方法看看到底这个扩容机制到底是怎么个事
(5)进入ensureCapcityInternal 方法
进入 ensureCapcityInternal 方法,其代码如下:
可以看到,该方法中又调用了两个方法 ensureExplicitCapacity、calculateCapacity,真是跟长了包皮一样,套来套去真的烦,但是不要急躁,这里涉及到的代码都不是很难
我们先进入内层调用的函数 calculateCapacity(elementData,minCapacity)看到底做了啥:
可以看到第一条语句是 if(elementData == DEFAULTCAPCITY_EMPTY_ELEMENTDATA)
,判断数组是否为空数组,如果是的话就 return Math.max(DEFAULT_CAPACITY,minCapacity),DEFAULT_CAPACITY = 10,见名知意,它表示的意思就是:默认的数组长度为 10,源码中也有注释,如下:
而 minCapicity(数组的最小容量)= 1,代表的是要把该元素添加进去,所需的数组的最小长度。所以这个 if 语句很明显的就是用于第一次扩容的代码。我们这里的返回值就是 10
回到 ensureCapcityInternal 方法,下面就是进入 ensureExplicitCapacity 方法了:
modCount 属性用于保存你修改集合的次数,用来防止有多个线程修改它,多个线程修改时会抛出异常,这里我们不用管它
下面的 if 判断语句就是利用前面得到的所需的最小数组容量 minCapacity 和当前数组的容量elementData.length 作一个比较,如果发现当前数组的容量无法满足所需的最小数组容量 minCapacity ,那就要调用 grow 方法开始真正意义上的扩容了
这里 minCapacity = 10,elementData.length = 0,所以需要调用 grow 方法完成数组扩容
(6) 进入grow方法
private void grow(int minCapacity) {//minCapacity:10//记录当前数组的容量,由于此时还没有存储元素,所以为0int oldCapacity = elementData.length;/*核心扩容算法:原来容量的 + 原来容量的一半(即原容量的1.5倍),但第一次扩容的时候这里的newCapacity = 0因为第一次扩容的时候 oldCapacity = 0 嘛。但不用担心,后面紧跟着的if语句就是用来应对这种情况的*/int newCapacity = oldCapacity + (oldCapacity >> 1);//确保新容量newCapacity大于等于最小容量minCapacity,所以第一次扩容时 newCapacity = 10if (newCapacity - minCapacity < 0)newCapacity = minCapacity;//下面这个if语句不用管也没事,一般都不会搞这么大容量的列表//判断新容量是否超过了Integer.MAX_VALUE - 8if (newCapacity - MAX_ARRAY_SIZE > 0)//如果超过了,则将新容量赋值为Integer.MAX_VALUEnewCapacity = hugeCapacity(minCapacity);//拷贝出一个新数组,新数组的容量就是前面计算的newCapacity,扩容完毕elementData = Arrays.copyOf(elementData, newCapacity);
}
不要因为走得太远而忘记了我们为什么出发。我们一路追追追,只有一个目的:elementData数组目前是个空数组,要添加第一个元素 0 进入,必须先扩容,因此通过上述代码确定最终的 elmentData 数组长度为 newCapacity = 10,第一次扩容完毕
(7)逐层返回,第一次扩容elementData数组完毕(0 ——> 10)
grow 函数执行完毕,返回到 ensureExplicitCapacity 函数,如下:
可以看到, elementData 数组已经由原来的 "Object[0]@512" ---> "Object[10]@514" ,这也可以验证我们上文提到的 "当我们使用空参构造来创建 ArrayList 类对象时,elementData 数组的初始容量为 0 ,第一次添加元素时,将该数组扩容为 10 "。这里还需要注意的是扩容之后的数组是一个新的数组,是存储在不同地址空间的,所以 @512 ---> @514
执行完 ensureExplictitCapacity 方法,接着返回到 ensureCapacityInternal 方法,如下:
执行完 ensureCapacityInternal 方法,就回到了一开始的 add 方法了,然后是执行 elementData[size++] = e ,把当前元素 e 加入到数组中,如下 GIF 图所示:
可以看到第一个元素 e = 0 成功地被添加进了 elementData 数组,size 也更新为 1,即当前 ArrayList 集合中的元素个数为 1
接着返回到测试类中,如下:
(8)向集合中添加第二个元素(不需要扩容)
第一次扩容完成后,elementData 数组的长度由 0 ---> 10 ,因此, for 循环中后续几个元素的添加都不再需要扩容。以第二个元素的添加为例,这里再过一遍,加深印象,这次主要是体验一下流程,不会像第一次一样描述得那么细致了
如下 GIF 图所示,随着循环变量i的自增,for 的第一次循环顺利结束,第二次循环开始,向集合中添加第二个元素 1 :
可以看到,还是老规矩,需要先将 int 类型的数据 1 做装箱处理
接着,再次跳入 add 方法,如下图所示 :
跟我们前面提到的一样,需要先执行 ensureCapacityInternal 函数确定数组是否需要扩容,之后再将元素 e 加入到数组中,size++
追入 ensureCapacityInternal 方法,这里我以 GIF 图展示,不然篇幅会太长:
可以看到,这里计算得到的 minCapacity = 2 ,即加入该元素所需的数组的最小长度 minCapacity = 2,而在前面我们第一次 add 时,已经将数组的长度扩容为 10 了,所以 elementData.length = 10
因此,不会进入 grow 函数,即不需要对数组进行扩容操作
执行完 ensureCapacityInternal 后,回到 add 方法,将当前元素 e = 1 加入到数组中,然后更新 size = 2,如下 GIF 所示:
(9)将集合中的元素添加到10个(达到第一个临界点)
之后的 8 次循环(向集合中添加 2~9 这 8 个数),流程均与第二次循环相同,我们直接一笔带过,如下 GIF 图所示 :
(10)集合的第二次扩容开始
因为第一次对 elementData 数组进行扩容时,默认只从 0 ---> 10 。而 for 循环结束后,我们已经向集合对象中添加了 10 个元素,即 ArrayList 底层的 elementData 数组已被装满。现在想添加第 11 个元素,就需要对 elementData 数组进行第二次扩容,先说结论,扩容后的数组容量扩大了 1.5 倍。即第二次扩容之后,elementData 数组为:10 --> 10 + 10 / 2 = 15
先跳入 add 方法,看看会发生什么,如下图所示 :
可以看到,由于从第 11 个元素开始,均为 String 类型,String 本身就是引用类型,因此不需要"装箱"的操作,所以直接跳入了 add 方法
跳入 ensureCapacityInternal ,判断是否需要对数组进行扩容,如下 GIF 所示:
可以看到,这里计算得到的 minCapacity = 11 ,即加入该元素所需的数组的最小长度minCapacity = 11,而当前的数组我们只在第一次 add 的时候扩容过一次,即将数组的长度扩容为10,所以此时 elementData.length = 10 ,所以需要执行 grow 函数对数组进行扩容
进入 grow 函数,对数组进行扩容:
private void grow(int minCapacity) {//minCapacity:11//记录当前数组的容量,oldCapacity = 10int oldCapacity = elementData.length;/*核心扩容算法:原来容量的 + 原来容量的一半(即原容量的1.5倍)扩容之后的数组容量:newCapacity = 10 + 10/2 = 15*/int newCapacity = oldCapacity + (oldCapacity >> 1);//确保新容量newCapacity大于等于最小容量minCapacityif (newCapacity - minCapacity < 0)newCapacity = minCapacity;//下面这个if语句不用管也没事,一般都不会搞这么大容量的列表//判断新容量是否超过了Integer.MAX_VALUE - 8if (newCapacity - MAX_ARRAY_SIZE > 0)//如果超过了,则将新容量赋值为Integer.MAX_VALUEnewCapacity = hugeCapacity(minCapacity);//拷贝出一个新数组,新数组的容量就是前面计算的newCapacity = 15 ,扩容完毕elementData = Arrays.copyOf(elementData, newCapacity);
}
不要因为走得太远而忘记了我们为什么出发。我们一路追追追,只有一个目的: elementData 数组当前是个容量为 10 的数组,要添加第 11 个元素字符串 e = "这是集合的第十一个元素捏",但是数组容量不够,所以需要先扩容。因此通过上述的 grow 函数确定扩容之后的 elmentData 数组长度为 newCapacity = 15,扩容完毕
(11)集合的第二次扩容结束
后面就是逐层返回到 add 函数了,然后将第 11 个元素 e = "这是集合的第十一个元素捏" 加入到数组中,并更新 size = 11 ,如下 GIF 图所示:
(12)将集合中的元素添加到15个(达到第二个临界点)
之后的第12到第15个元素的添加,与第 11 个元素的添加大同小异,只不过添加过程中不需要进入 grow 方法对数组进行扩容。接下来 4 个元素的添加,这里一笔带过。如下 GIF 所示:
(13)集合的第三次扩容开始
第二次扩容结束后,底层的 elementData 数组的容量由 10--->15 。而经过前面的一通操作过后,elementData 数组又满了。现在我们想向集合中添加第 16 个元素,就要进行集合的第三次扩容。从第二次扩容开始,之后的每次扩容在底层都与第二次扩容原理一样,并且每次都扩容到当前集合容量的 1.5 倍。即第三次扩容之后,elementData 数组为:15 --> 15 + 15 / 2 = 22
同样,直接跳入 add 方法。如下 GIF 图所示:
可以看到,第 16 个元素也为 String 类型,本身就是引用类型,因此不需要"装箱"的操作,所以直接跳入了 add 方法。size = 15 表示 ArrayList 集合中的元素个数为 15
跳入 ensureCapacityInternal ,判断是否需要对数组进行扩容:
可以看到,这里计算得到的 minCapacity = 16 ,即加入该元素所需的数组的最小长度 minCapacity = 16,而当前的数组经过前面的第二次扩容后长度为 15,所以 elementData.length = 15,所以需要执行 grow 函数对数组进行扩容
进入 grow 函数,对数组进行扩容:
private void grow(int minCapacity) {//minCapacity:16//记录当前数组的容量,oldCapacity = 15int oldCapacity = elementData.length;/*核心扩容算法:原来容量的 + 原来容量的一半(即原容量的1.5倍)扩容之后的数组容量:newCapacity = 15 + 15/2 = 22*/int newCapacity = oldCapacity + (oldCapacity >> 1);//确保新容量newCapacity大于等于最小容量minCapacityif (newCapacity - minCapacity < 0)newCapacity = minCapacity;//下面这个if语句不用管也没事,一般都不会搞这么大容量的列表//判断新容量是否超过了Integer.MAX_VALUE - 8if (newCapacity - MAX_ARRAY_SIZE > 0)//如果超过了,则将新容量赋值为Integer.MAX_VALUEnewCapacity = hugeCapacity(minCapacity);//拷贝出一个新数组,新数组的容量就是前面计算的newCapacity = 22 ,扩容完毕elementData = Arrays.copyOf(elementData, newCapacity);
}
还是那句话,不要因为走得sssssssssssssssssss了我们为什么出发。我们一路追追追,只有一个目的:elementData 数组当前是个容量为 15 的数组,要添加第 16 个元素字符串 e = "这是集合的第十六个元素捏" ,但是数组容量不够,所以需要先扩容。因此通过上述的 grow 函数确定扩容之后的 elmentData 数组长度为 newCapacity = 22,扩容完毕
(14)集合的第三次扩容结束
后面就是逐层返回到 add 函数了,然后将第 16 个元素 e = "这是集合的第十六个元素捏" 加入到数组中,并更新 size = 16 ,如下 GIF 图所示:
到此,无参构造的分步骤Debug演示,就到这里结束了
4.2 使用有参构造
如果用带参构造来初始化 ArrayList 对象,那么它底层的扩容机制与无参构造初始化 ArrayList 对象时的大同小异。唯一不同的一点在于,使用带参构造初始化 ArrayList 对象,底层的 elementData 数组在一开始不会置空,而是将其初始化为调用带参构造时中实参指定的长度。之后的扩容流程与空参构造初始化对象时无异。因此,这里就不会像之前空参构造时演示得那么细了
将用以下代码为演示,来进行 Debug 操作,代码如下 :
import java.util.ArrayList;
public class demo {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); //此处涉及到了自动装箱,int -> Integer}//3.再次向集合中添加元素。(扩容:4 ——> 6)arrayList.add("这是第五个元素捏");arrayList.add("这是第六个元素捏");System.out.println("添加六个元素后,集合 = " + arrayList);//4.再次向集合中添加元素。(扩容:6 ——> 9)arrayList.add("这是第七个元素捏");System.out.println("添加七个元素后,集合 = " + arrayList);}
}
(1)开始Debug
首先,进入 Debug 界面,并在第 6 行的有参构造调用行,跳入 ArrayList 类有参构造,如下GIF 所示 :
(2)集合的第一次扩容(初始化)
跳入 ArrayList 的带参构造,如下图所示 :
可以看到,elementData 数组被初始化为了长度为 4 的数组, 4 正是我们调用带参构造时指定的长度
ArrayList 类的该带参构造是一个 if - else if - else 的复合条件语句。因为我们指定的长度 initialCapacity = 4 > 0 ,所以它直接进入 if 控制的语句中,即将一个长度为 4 的新数组赋值给了 elementData 数组(其实就是改变了 elementData 引用的指向)。如果带参构造传入的实参为 0 ,它会将 EMPYT_ELEMENTDATA 赋值给数组,这里的 EMPYT_ELEMENTDATA 也是一个空数组,如下:
如果传入的实参 initialCapacity< 0 ,就会抛出一个异常对象
接着,跳出带参构造,回到测试类,可以看到底层的 elementData 数组被初始化为 4 的长度。如下 GIF 所示:
(3)向集合中添加第一个元素
该过程在使用无参构造时已经详细说明,所以这里我以 GIF 图的方式演示整个添加流程,对于一些重要的部分我会放慢:
以上就是添加一个元素的整个流程,和前面使用无参构造器的时候是完全一样的,对于后续的扩容机制也是完全一致的,所以这里就不再 Debug 了,不然本文的篇幅太长了
....
5.add (E e) 方法源码
这里我将 add (E e) 涉及到的所有方法集中在一起,方便查看,代码也加了详细的注释,如下:
/*** 往ArrayList添加元素** @param e 待添加的元素* @return*/
public boolean add(E e) {//调用ensureCapacityInternal方法判断是否需要对数组进行扩容ensureCapacityInternal(size + 1);//扩容结束后将元素e加入到数组中,并且更新 sizeelementData[size++] = e;return true;
}/*** 确保数组elementData容量充足* @param minCapacity 当前数组所需的最小容量*/
private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}/*** @param minCapacity 当前数组所需的最小容量*/
private static int calculateCapacity(int minCapacity) {//判断当前数组是否已被初始化if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//通过最小容量和默认容量10求出较大值 (用于第一次扩容)return Math.max(DEFAULT_CAPACITY, minCapacity);}return minCapacity;
}/*** 确保ArrayList扩容后的容量充足** @param minCapacity 当前数组所需最小容量*/
private void ensureExplicitCapacity(int minCapacity) {//集合修改次数++,该属性在扩容的过程中没用,主要是用于迭代器中,确保线程安全modCount++;//判断当前数组容量是否满足最小容量,不满足则调用grow进行进行扩容if (minCapacity - elementData.length > 0)//调用grow扩容方法grow(minCapacity);
}/*** 扩容 elementData 数组** @param minCapacity 当前数组所需最小容量*/
private void grow(int minCapacity) {//记录数组的当前长度,此时由于木有存储元素,长度为0int oldCapacity = elementData.length;//核心扩容算法:原来容量的 + 原来容量的一半(即原容量的1.5倍)int newCapacity = oldCapacity + (oldCapacity >> 1);//确保新容量newCapacity大于等于最小容量minCapacityif (newCapacity - minCapacity < 0)newCapacity = minCapacity;//下面这个if语句不用管也没事,一般都不会搞这么大容量的列表//判断新容量是否超过了Integer.MAX_VALUE - 8if (newCapacity - MAX_ARRAY_SIZE > 0)//如果超过了,则将新容量赋值为Integer.MAX_VALUEnewCapacity = hugeCapacity(minCapacity);//拷贝出一个新数组,新数组的容量就是前面计算的newCapacity,扩容完毕elementData = Arrays.copyOf(elementData, newCapacity);
}/*** 获取一个超大容量* @param minCapacity* @return*/
private static int hugeCapacity(int minCapacity) {if (minCapacity < 0) // overflow(健壮性代码)throw new OutOfMemoryError();// 如果当前超过了 MAX_ARRAY_SIZE(Integer.MAX_VALUE-8)就返回Integer.MAX_VALUE// 否则返回 MAX_ARRAY_SIZE(Integer.MAX_VALUE-8)return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;
}
相关文章:
【Java集合】ArrayList源码深度分析
参考笔记: java ArrayList源码分析(深度讲解)-CSDN博客 【源码篇】ArrayList源码解析-CSDN博客 目录 1.前言 2. ArrayList简介 3. ArrayList类的底层实现 4. ArrayList的源码Debug 4.1 使用空参构造 (1)开始De…...
ISIS单区域抓包分析
一、通用头部报文 Intra Domain Routing Protocol Discriminator:域内路由选择协议鉴别符:这里是ISIS System ID Length:NSAP地址或NET中System ID区域的长度。值为0时,表示System ID区域的长度为6字节。值为255时,表…...
关键业务数据如何保持一致?主数据管理的最佳实践!
随着业务规模的扩大和系统复杂性的增加,如何确保关键业务数据的一致性成为许多企业面临的重大挑战。数据不一致可能导致决策失误、运营效率低下,甚至影响客户体验。因此,主数据管理(Master Data Management,简称MDM&am…...
ISIS单区域配置
一、什么是ISIS单区域 ISIS(Intermediate System to Intermediate System,中间系统到中间系统)单区域是指使用ISIS路由协议时,所有路由器都位于同一个区域(Area)内的网络配置。 二、实验拓扑 三、实验目的…...
Visual Basic语言的物联网
Visual Basic语言在物联网中的应用 引言 物联网(IoT)作为一种新兴的技术趋势,正在深刻地改变我们的生活方式与工业制造过程。在众多编程语言中,Visual Basic(VB)凭借其简单易用的特性,逐渐成为…...
【小沐杂货铺】基于Three.JS绘制三维数字地球Earth(GIS 、WebGL、vue、react)
🍺三维数字地球系列相关文章如下🍺:1【小沐学GIS】基于C绘制三维数字地球Earth(456:OpenGL、glfw、glut)第一期2【小沐学GIS】基于C绘制三维数字地球Earth(456:OpenGL、glfw、glut)第二期3【小沐…...
Vite环境下解决跨域问题
在 Vite 开发环境中,可以通过配置代理来解决跨域问题。以下是具体步骤: 在项目根目录下找到 vite.config.js 文件:如果没有,则需要创建一个。配置代理:在 vite.config.js 文件中,使用 server.proxy 选项来…...
嵌入式Linux开发环境搭建,三种方式:虚拟机、物理机、WSL
目录 总结写前面一、Linux虚拟机1 安装VMware、ubuntu18.042 换源3 改中文4 中文输入法5 永不息屏6 设置 root 密码7 安装 terminator8 安装 htop(升级版top)9 安装 Vim10 静态IP-虚拟机ubuntu11 安装 ssh12 安装 MobaXterm (SSH)…...
React项目在ts文件中使用router实现跳转
前言: 默认你已经进行了router的安装,目前到了配置http请求的步骤,在配置token失效或其他原因,需要实现路由跳转。在普通的 TypeScript 文件中无法直接使用Router的 useNavigate Hook。Hook 只能在 React 组件或自定义 Hook 中调用…...
Java中的正则表达式Lambda表达式
正则表达式&&Lambda表达式 正则表达式和Lambda表达式是Java编程中两个非常实用的特性。正则表达式用于字符串匹配与处理,而Lambda表达式则让函数式编程在Java中变得更加简洁。本文将介绍它们的基本用法,并结合示例代码帮助理解。同时要注意&…...
【idea设置文件头模板】
概述 设置模板,在创建java类时,统一添加内容(作者、描述、创建时间等等自定义内容),给java类添加格式统一的备注信息。 1、在settings 中找到File and Code Templates 选择File Header 2、模板内容示例 /*** Author hweiyu* Descriptio…...
我与数学建模之顺遂!
下面一段时期是我一段真正走进数模竞赛的时期。 在大二上学期结束之后,就开始张罗队友一起报名参加美赛,然后同时开始学LaTeX和Matlab,当时就是买了本Matlab的书,把书上的例题还有课后题全部做完了,然后用latex将书上…...
【Python使用】嘿马推荐系统全知识和项目开发教程第2篇:1.4 案例--基于协同过滤的电影推荐,1.5 推荐系统评估【附代码
教程总体简介:1.1 推荐系统简介 学习目标 1 推荐系统概念及产生背景 2 推荐系统的工作原理及作用 3 推荐系统和Web项目的区别 1.3 推荐算法 1 推荐模型构建流程 2 最经典的推荐算法:协同过滤推荐算法(Collaborative Filtering) 3 …...
Linux makefile的一些语法
一、定义变量 1. 变量的基本语法 在 makefile 中,变量的定义和使用非常类似于编程语言中的变量。变量的定义格式(最好不要写空格)如下: VARIABLE_NAMEvalue 或者 VARIABLE_NAME:value 表示延迟赋值,变量的值在引…...
Educational Codeforces Round 177 (Rated for Div. 2)(A-D)
题目链接:Dashboard - Educational Codeforces Round 177 (Rated for Div. 2) - Codeforces A. Cloudberry Jam 思路 小数学推导问题,直接输出n*2即可 代码 void solve(){int n;cin>>n;cout<<n*2<<"\n"; } B. Large A…...
第十八节课:Python编程基础复习
课程复习 前三周核心内容回顾 第一周:Python基本语法元素 基础语法:缩进、注释、变量命名、保留字数据类型:字符串、整数、浮点数、列表程序结构:赋值语句、分支语句(if)、函数输入输出:inpu…...
动物多导生理信号采集分析系统技术简析
一 技术参数 通道数:通道数量决定了系统能够同时采集的生理信号数量。如中南大学湘雅医学院的生物信号采集系统可达 128 通道,OmniPlex 多导神经信号采集分析系统支持 16、32、64、128 通道在体记录。不过,这个也要看具体的应用场景ÿ…...
Linux——Linux系统调用函数练习
一、实验名称 Linux系统调用函数练习 二、实验环境 阿里云服务器树莓派 三、实验内容 1. 远程登录阿里云服务器 2. 创建目录 操作步骤: mkdir ~/xmtest2 cd ~/xmtest2结果: 成功创建并进入homework目录。 3. 编写C代码 操作步骤: …...
列表与列表项
认识列表和列表项 FreeRTOS 中的 列表(List) 和 列表项(ListItem)是其内核实现的核心数据结构,广泛用于任务调度、队列管理、事件组、信号量等模块。它们通过双向链表实现,支持高效的元素插入、删除和遍历…...
mofish软件(MacOS版本)手动初始化
mofish软件手动初始化MacOS 第一步,打开终端 command空格键唤起搜索页面,输入终端,点击打开终端 第二步,进入mofish配置目录,删除初始化配置文件 在第一步打开的终端中输入如下命令后按回车键,删除mofish配置文件 …...
基于javaweb的SpringBoot图片管理系统图片相册系统设计与实现(源码+文档+部署讲解)
技术范围:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文…...
密码学基础——DES算法
前面的密码学基础——密码学文章中介绍了密码学相关的概念,其中简要地对称密码体制(也叫单钥密码体制、秘密密钥体制)进行了解释,我们可以知道单钥体制的加密密钥和解密密钥相同,单钥密码分为流密码和分组密码。 流密码࿰…...
我与数学建模之波折
我知道人生是起起伏伏,但没想到是起起伏伏伏伏伏伏 因为简单讲讲,所以我没讲很多生活上的细节,其实在7月我和l学长一起在外面租房子备赛。这个时间节点其实我不太愿意讲,但是逃不了,那段时间因其他事情导致我那段时间…...
离线部署kubesphere(已有k8s和私有harbor的基础上)
前言说明:本文是在已有k8s集群和私有仓库harbor上进行离线安装kubesphere;官网的离线教程写都很详细,但是在部署部份把搭建集群和搭建仓库也写一起了,跟着做踩了点坑,这里就记录下来希望可以帮助到需要的xdm。 1.根据官…...
量子计算入门:Qiskit实战量子门电路设计
引言:量子计算的编程基石 量子门是量子计算的基本操作单元,其通过操控量子比特的叠加与纠缠实现并行计算。IBM开发的Qiskit框架为量子算法设计与模拟提供了强大工具。本文将从量子门基础、Qiskit实战、量子隐形传态案例三个维度,结合代码解析…...
AIGC8——大模型生态与开源协作:技术竞逐与普惠化浪潮
引言:大模型发展的分水岭时刻 2024年成为AI大模型发展的关键转折点:OpenAI的GPT-4o实现多模态实时交互,中国DeepSeek-MoE-16b模型以1/8成本达到同类90%性能,而开源社区如Mistral、LLama 3持续降低技术门槛。这场"闭源商业巨…...
FPGA练习
文章目录 一、状态机思想写一个 LED流水灯的FPGA代码二、 CPLD和FPGA芯片的主要技术区别是什么? 它们各适用于什么场合?1、CPLD适用场景2、FPGA适用场景 三、 在hdlbitsFPGA教程网站上进行学习1、练习题12、练习题2练习题3练习题4练习题5 一、状态机思想…...
阿里云服务器遭遇DDoS攻击有争议?
近年来,阿里云服务器频繁遭遇DDoS攻击的事件引发广泛争议。一方面,用户质疑其防御能力不足,导致服务中断甚至被迫进入“黑洞”(清洗攻击流量的隔离机制),轻则中断半小时,重则长达24小时…...
leetcode-代码随想录-哈希表-有效的字母异位词
题目 题目链接:242. 有效的字母异位词 - 力扣(LeetCode) 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的 字母异位词。 输入: s "anagram", t "nagaram" 输出: true输入: s "rat",…...
kotlin中主构造函数是什么
一 Kotlin 中的主构造函数 主构造函数(Primary Constructor)是 Kotlin 类声明的一部分,用于在 创建对象时初始化类的属性。它不像 Java 那样是一个函数体,而是紧跟在类名后面。 主构造函数的基本定义 class Person(val name: S…...
Julia语言的测试覆盖率
Julia语言的测试覆盖率探讨 引言 在现代软件开发中,测试是确保软件质量的重要环节。随着软件的复杂度不断增加,测试覆盖率作为衡量测试质量的一个重要指标,受到了越来越多开发者的关注。Julia语言作为一种高性能的动态编程语言,…...
Apache httpclient okhttp(2)
学习链接 Apache httpclient & okhttp(1) Apache httpclient & okhttp(2) okhttp github okhttp官方使用文档 okhttp官方示例代码 OkHttp使用介绍 OkHttp使用进阶 译自OkHttp Github官方教程 SpringBoot 整合okHttp…...
BUUCTF-web刷题篇(10)
19.EasyMD5 md5相关内容总结: ①string md5(&str,raw) $str:需要计算的字符串; raw:指定十六进制或二进制输出格式。计算成功,返回md5值,计算失败,返回false。 raw参数为true:16个字符的二进制格式&…...
CCF GESP C++编程 五级认证真题 2025年3月
C 五级 2025 年 03 月 题号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 答案 A A B B D C A D A B C A A D B 1 单选题 第 1 题 链表不具备的特点是( )。 A. 可随机访问任何一个元素 B. 插入、删除操作不需要移动元素 C. 无需事先估计存储空间大小 D. 所需存储空间与存储元素个数成…...
【AI学习】MCP的简单快速理解
最近,AI界最火热的恐怕就是MCP了。作为一个新的知识点,学习的开始,先摘录一些信息,从发展历程、通俗介绍到具体案例,这样可以快速理解MCP。 MCP发展历程 来自i陆三金 Anthropic 开发者关系负责人 Alex Albert&#…...
文档处理利器Docling,基于LangChain打造RAG应用
大家好,人工智能应用持续发展,对文档信息的有效处理、理解与检索提出了更高要求。大语言模型虽已在诸多领域发挥重要作用,但在文档处理方面仍有提升空间。 本文将详细阐述如何整合Docling 和 LangChain,创建检索增强生成…...
深度学习图像分类数据集—枣子水果成熟度分类
该数据集为图像分类数据集,适用于ResNet、VGG等卷积神经网络,SENet、CBAM等注意力机制相关算法,Vision Transformer等Transformer相关算法。 数据集信息介绍:3种枣子水果成熟度数据:g,r,y&#…...
第五讲(上) | string类的使用
string类的使用 一、string和C风格字符串的对比二、string类的本质三、string常用的API(注意只讲解最常用的接口)Member constants(成员常数)npos Member functionsIterators——迭代器Capacity——容量reserve和resizeElement ac…...
医药流通行业AI大模型冲击下的IT从业者转型路径分析
医药流通行业AI大模型冲击下的IT从业者转型路径分析 一、行业背景与技术变革趋势 在2025年的医药流通领域,AI技术正以指数级速度重塑行业格局。国家药监局数据显示,全国药品流通企业数量已从2018年的1.3万家缩减至2024年的8,900家,行业集中…...
【新能源汽车整车动力学模型深度解析:面向MATLAB/Simulink仿真测试工程师的硬核指南】
1. 前言 作为MATLAB/Simulink仿真测试工程师,掌握新能源汽车整车动力学模型的构建方法和实现技巧至关重要。本文将提供一份6000+字的深度技术解析,涵盖从基础理论到Simulink实现的完整流程。内容经过算法优化设计,包含12个核心方程、6大模块实现和3种验证方法,满足SEO流量…...
Android Fresco 框架动态图支持模块源码深度剖析(七)
上一期 Android Fresco 框架兼容模块源码深度剖析(六) 本人掘金号,欢迎点击关注:https://juejin.cn/user/4406498335701950 一、引言 在 Android 开发中,高效处理和展示动态图(如 GIF、WebP 动画等)是一个常见需求。…...
蓝桥杯专项复习——双指针
目录 双指针算法:双指针算法-CSDN博客 最长连续不重复子序列 P8783 [蓝桥杯 2022 省 B] 统计子矩阵 双指针优化思路:当存在重复枚举时,可以考虑是否能使用双指针进行优化 双指针算法:双指针算法-CSDN博客 最长连续不重复子序列…...
详解大模型四类漏洞
关键词:大模型,大模型安全,漏洞研究 1. 引入 promptfoo(参考1)是一款开源大语言模型(LLM)测试工具,能对 LLM 应用进行全面漏洞测试,它可检测包括安全风险、法律风险在内…...
【HC-05蓝牙模块】基础AT指令测试
一、视频课程 HC-05 蓝牙模块 第2讲 二、视频课件...
文件操作(c语言)
本关任务:给定程序的功能是:从键盘输入若干行文本(每行不超过 80 个字符),写到文件myfile4.txt中,用 -1(独立一行)作为字符串输入结束的标志。然后将文本的内容读出显示在屏幕上。文…...
Apache Camel指南-第四章:路由径构建之异常处理
摘要 Apache的骆驼提供几种不同的机制,让您在处理不同的粒度级别的例外:您可以通过处理一个路线中的异常doTry,doCatch以及doFinally; 或者您可以指定要采取什么行动每种类型的异常,并应用此规则的所有路由RouteBuilder使用onExc…...
赚钱模拟器--百宝库v0.1.0
#include<bits/stdc.h> #include<windows.h> using namespace std; int n; void welcome(); void zhuye(); int main(){welcome();zhuye();return 0; }void welcome(){cout<<"欢迎您使用更多资源-百宝库v0.1.0"<<endl;system("pause&q…...
SSL证书自动化管理(ACME协议)工作流程介绍
SSL证书自动化管理(ACME协议)是一种用于自动化管理SSL/TLS证书的协议,以下是其详细介绍: 一、ACME协议概述 ACME协议由互联网安全研究小组(ISRG)设计开发,旨在实现SSL证书获取流程的自动化。通…...
推理模型与普通大模型如何选择?
👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家📕系列专栏:Spring原理、JUC原理、Kafka原理、分布式技术原理、数据库技术、JVM原理、AI应用🔥如果感觉…...
人工智能与计算机技术融合下的高中教育数字化教学模式探索
一、引言 1.1 研究背景与意义 1.1.1 教育数字化转型的国家战略需求 在当今时代,数字化浪潮正席卷全球,深刻改变着人们的生产生活方式。教育领域作为培养未来人才的重要阵地,也不可避免地受到数字化的影响。教育数字化转型已成为世界各国的…...