究竟是什么样的讲解数组算法的博客让我写了三小时???
版本说明
当前版本号[20231004]。
| 版本 | 修改说明 |
|---|---|
| 20231004 | 初版 |
目录
文章目录
- 版本说明
- 目录
- 二. 基础数据结构
- 2.1 数组
- 1) 概述
- 2) 动态数组
- 1)插入
- addlast 方法
- 测试: addlast 方法
- add 方法
- 测试:add方法
- addlast 方法与 add 方法合并版
- get 方法
- 测试:get 方法
- 2) 遍历
- 遍历方法1:consumer 遍历
- 测试:consumer 遍历
- 遍历方法2:迭代器遍历
- 测试:迭代器遍历
- 遍历方法3:stream 遍历
- 测试:stream 遍历
- 插入与遍历方法汇总
- 插入与遍历测试类汇总
- 3)删除
- 测试:删除方法
- 4)扩容
- 5)插入或删除性能
- 3) 二维数组
- **测试**
- 4) 局部性原理
- **对效率的影响**
二. 基础数据结构
2.1 数组
1) 概述
定义
在计算机科学中,数组是由一组元素(值或变量)组成的数据结构,每个元素有至少一个索引或键来标识
因为数组内的元素是连续存储的,所以数组中元素的地址,可以通过其索引计算出来,例如:
int[] array = {1,2,3,4,5}
知道了数组的数据起始地址 B a s e A d d r e s s BaseAddress BaseAddress,就可以由公式 B a s e A d d r e s s + i ∗ s i z e BaseAddress + i * size BaseAddress+i∗size 计算出索引 i i i 元素的地址
- i i i 即索引,在 Java、C 等语言都是从 0 开始
- s i z e size size 是每个元素占用字节,例如 i n t int int 占 4 4 4, d o u b l e double double 占 8 8 8
小测试
byte[] array = {1,2,3,4,5}
已知 array 的数据的起始地址是 0x7138f94c8,那么元素 3 的地址是什么?
答:0x7138f94c8 + 2 * 1 = 0x7138f94ca
空间占用
Java 中数组结构为
- 8 字节 markword
- 4 字节 class 指针(压缩 class 指针的情况)
- 4 字节 数组大小(决定了数组最大容量是 2 32 2^{32} 232)
- 数组元素 + 对齐字节(java 中所有对象大小都是 8 字节的整数倍[^12],不足的要用对齐字节补足)
例如
int[] array = {1, 2, 3, 4, 5};
的大小为 40 个字节,组成如下
8 + 4 + 4 + 5*4 + 4(alignment)
随机访问性能
即根据索引查找元素,时间复杂度是 O ( 1 ) O(1) O(1)
2) 动态数组
1)插入
addlast 方法
意在把新增的元素加入到最后一个到动态数组里。
public class 动态数组
{private int size = 0;//逻辑大小private int capacity = 8;//所创建数组的数组容量private int[] array = new int[capacity];/*** 向最后位置 [size] 添加元素** @param element 待添加元素*/public void addlast(int element){array[size] = element;size++;}
}
这个类具有以下特点:
size变量表示当前数组中已存储的元素数量。capacity变量表示数组的容量,即可以容纳的最大元素数量。array是一个整型数组,用于存储元素。addlast(int element)方法用于在数组的末尾添加一个元素,并将size增加1。
测试: addlast 方法
这段代码作为一个JUnit测试类,在测试方法test1中,首先创建了一个动态数组对象,然后向数组中添加了5个元素,最后遍历数组并打印出每个元素的值。
package SuanFa.test;import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import SuanFa.初始算法.第一章数组.动态数组;
import java.util.Arrays;public class 动态数组Test {@Testpublic void test1(){动态数组 dynamicarray = new 动态数组();dynamicarray.addlast(1);dynamicarray.addlast(2);dynamicarray.addlast(3);dynamicarray.addlast(4);dynamicarray.addlast(5);for(int i = 0; i < 5; i++){System.out.println(dynamicarray.get(i));}}
}
测试结果如下:

注:接下来的所有方法中将只保留最主要的代码。
add 方法
意在向某一个索引里加入新值。
/*** 向 [0 .. size] 位置添加元素** @param index 索引位置* @param element 待添加元素*/public void add(int index, int element){if(index >=0 && index < size){System.arraycopy(array, index, array, index+1, size - index);array[index] = element;size++;}}
这个类具有以下特点:
size变量表示当前数组中已存储的元素数量。capacity变量表示数组的容量,即可以容纳的最大元素数量。array是一个整型数组,用于存储元素。add(int index, int element)方法用于在指定索引位置插入到指定索引位置,并确保索引在合法范围内(大于等于0且小于当前数组大小)。- 如果索引合法,使用
System.arraycopy()方法将原数组中从指定索引位置开始的元素向后移动一个位置,腾出空间来插入新元素。(复制 array 数组中从下标为 index 的元素复制到 array 数组的下标为 index+1 的元素上,并且只复制 size - index 的元素) - 将新元素赋值给指定索引位置。
- 增加
size变量的值,表示数组中已存储的元素数量增加了1。 - 如果索引不合法,该方法不会执行任何操作。
测试:add方法
这段代码作为一个JUnit测试类,在测试方法test1中,首先创建了一个动态数组对象,然后向数组中添加了4个元素,接着使用add方法在索引为2的位置插入了一个值为9的元素。最后遍历数组并打印出每个元素的值。
@Testpublic void test2(){动态数组 dynamicarray = new 动态数组();dynamicarray.addlast(1);dynamicarray.addlast(2);dynamicarray.addlast(3);dynamicarray.addlast(4);//dynamicarray.addlast(5);dynamicarray.add(2, 9);for(int i = 0; i < 5; i++){System.out.println(dynamicarray.get(i));}}
}
测试结果如下:

addlast 方法与 add 方法合并版
由于addlast与add方法中,都有 array[下标] = element; size++; 这两段代码,于是也可以对他们进行优化,放在一起
/*** 向 [0 .. size] 位置添加元素** @param index 索引位置* @param element 待添加元素*/public void add(int index, int element){// 添加逻辑if(index >=0 && index < size){// 向后挪动, 空出待插入位置System.arraycopy(array, index,array, index+1, size - index);}array[index] = element;size++;}/*** 向最后位置 [size] 添加元素** @param element 待添加元素*/public void addlast(int element){add(size, element);}
这个类具有以下特点:
size变量表示当前数组中已存储的元素数量。capacity变量表示数组的容量,即可以容纳的最大元素数量。array是一个整型数组,用于存储元素。add(int index, int element)方法用于在指定索引位置插入一个元素。addlast(int element)方法用于在数组的末尾添加一个元素。add(int index, int element)方法将传入的元素插入到指定索引位置,并确保索引在合法范围内(大于等于0且小于当前数组大小)。- 如果索引合法,使用
System.arraycopy()方法将原数组中从指定索引位置开始的元素向后移动一个位置,腾出空间来插入新元素。 - 将新元素赋值给指定索引位置。
- 增加
size变量的值,表示数组中已存储的元素数量增加了1。 - 如果索引不合法,该方法不会执行任何操作。
addlast(int element)方法调用add(int index, int element)方法,将元素添加到数组的末尾。- 数组会自动调整大小以适应新元素的添加。
get 方法
意在接收一个整数参数 index ,返回数组中下标为 index 的元素。
/*** 查询元素** @param index 索引位置, 在 [0..size) 区间内* @return 该索引位置的元素*/public int get(int index){return array[index];}
这个类具有以下特点:
size变量表示当前数组中已存储的元素数量。capacity变量表示数组的容量,即可以容纳的最大元素数量。array是一个整型数组,用于存储元素。- 查询方法
get接收一个整数参数 index ,返回数组中下标为 index 的元素。
测试:get 方法
与测试:addlast 方法相同
@Testpublic void test1(){动态数组 dynamicarray = new 动态数组();dynamicarray.addlast(1);dynamicarray.addlast(2);dynamicarray.addlast(3);dynamicarray.addlast(4);dynamicarray.addlast(5);for(int i = 0; i < 5; i++){System.out.println(dynamicarray.get(i));}}
2) 遍历
遍历方法1:consumer 遍历
这段代码定义了一个名为 foreach 的方法,该方法接收一个 Consumer 类型的参数consumer。在方法内部,使用for循环遍历数组array,**对于每个元素,调用consumer的accept方法执行传入的操作。**最后,forEach方法没有返回值(void)。
/*** 遍历方法1** @param consumer 遍历要执行的操作, 入参: 每个元素*/public void foreach(Consumer<Integer> consumer){for(int i = 0; i<size ; i++)// 提供 array[i]// 返回 void{consumer.accept(array[i]);}}
测试:consumer 遍历
在该测试方法中,首先创建了一个动态数组对象dynamicarray,并向其中添加了5个元素。然后调用forEach方法遍历数组,对于每个元素,使用Lambda表达式打印出该元素的值。
public class 动态数组Test {@Testpublic void test3(){动态数组 dynamicarray = new 动态数组();dynamicarray.addlast(1);dynamicarray.addlast(2);dynamicarray.addlast(3);dynamicarray.addlast(4);dynamicarray.forEach((element->{System.out.println(element);}));}
}
测试结果如下:

遍历方法2:迭代器遍历
该动态数组类实现了Iterable接口,并重写了iterator方法。
在类中重写了Iterable接口的iterator方法,返回一个Iterator对象,该对象实现了Iterator接口的方法hasNext和next。其中,hasNext方法用于判断是否还有下一个元素,next方法用于**返回当前元素并将指针移动到下一个元素。**由于没有给出数组元素的添加和删除操作,因此该类的迭代器只能从头到尾遍历一遍数组。
public class 动态数组 implements Iterable<Integer>
{private int size = 0;//逻辑大小private int capacity = 8;//容量private int[] array = new int[capacity];/*** 遍历方法2 - 迭代器遍历*/@Overridepublic Iterator<Integer> iterator(){return new Iterator<Integer>() {int i = 0;@Overridepublic boolean hasNext() { // 有没有下一个元素return i < size;}@Overridepublic Integer next() { // 返回当前元素,并移动到下一个元素return array[i++];}};
}
测试:迭代器遍历
使用增强for循环遍历数组,对于每个元素,打印出该元素的值。
@Testpublic void test4(){动态数组 dynamicarray = new 动态数组();dynamicarray.addlast(1);dynamicarray.addlast(2);dynamicarray.addlast(3);dynamicarray.addlast(4);for(Integer element: dynamicarray){System.out.println(element);}}
测试结果如下:

遍历方法3:stream 遍历
这段代码定义了一个名为stream的公共方法,该方法返回一个IntStream类型的对象。在方法内部,使用IntStream.of()方法创建一个包含数组array中前size个元素的IntStream流,并返回该流。
/*** 遍历方法3 - stream 遍历** @return stream 流*/
public IntStream stream(){return IntStream.of(Arrays.copyOfRange(array,0,size));}
测试:stream 遍历
@Testpublic void test5(){动态数组 dynamicarray = new 动态数组();dynamicarray.addlast(7);dynamicarray.addlast(2);dynamicarray.addlast(4);dynamicarray.addlast(1);dynamicarray.stream().forEach(element->{System.out.println(element);});}
测试结果如下:

插入与遍历方法汇总
以下这段代码定义了一个名为动态数组的类,汇总了所有方法,实现了Iterable接口。该类方法分别有:
add(int index, int element):向指定索引位置添加元素的方法。如果索引有效(即大于等于0且小于当前大小),则将该位置及之后的元素向后移动一位,然后将新元素放置在指定索引位置上,并将大小加1。addlast(int element):向数组末尾添加元素的方法。调用add(size, element)实现。get(int index):获取指定索引位置的元素的方法。foreach(Consumer<Integer> consumer):遍历数组并对每个元素执行给定的消费者操作。stream():返回一个IntStream流,其中包含数组中的所有元素。使用Arrays.copyOfRange(array, 0, size)创建一个包含数组元素的新数组,并将其转换为IntStream流。
package SuanFa.初始算法.第一章数组;import java.util.Arrays;
import java.util.Iterator;
import java.util.function.Consumer;
import java.util.stream.IntStream;public class 动态数组 implements Iterable<Integer> {private int size = 0;//逻辑大小private int capacity = 8;//容量private int[] array = new int[capacity];@Overridepublic Iterator<Integer> iterator() {return new Iterator<Integer>() {int i = 0;@Overridepublic boolean hasNext() { // 有没有下一个元素return i < size;}@Overridepublic Integer next() { // 返回当前元素,并移动到下一个元素return array[i++];}};}public void add(int index, int element) {if (index >= 0 && index < size) {System.arraycopy(array, index,array, index + 1, size - index);}array[index] = element;size++;}public void addlast(int element) {add(size, element);}public int get(int index) {return array[index];}public void foreach(Consumer<Integer> consumer) {for (int i = 0; i < size; i++) {consumer.accept(array[i]);}}public IntStream stream() {return IntStream.of(Arrays.copyOfRange(array, 0, size));}
}
插入与遍历测试类汇总
该类包含了5个测试方法:
test1():测试了向动态数组末尾添加元素的功能。首先创建了一个空的动态数组对象dynamicarray,然后连续调用addlast()方法向数组末尾添加了5个元素(1到5)。最后使用循环遍历数组并打印每个元素的值。test2():测试了在指定位置插入元素的功能。首先创建了一个空的动态数组对象dynamicarray,然后连续调用addlast()方法向数组末尾添加了5个元素(1到5)。接着调用add()方法在索引为2的位置插入了一个新元素9。最后使用循环遍历数组并打印每个元素的值。test3():测试了使用IntStream流遍历动态数组的功能。首先创建了一个空的动态数组对象dynamicarray,然后连续调用addlast()方法向数组末尾添加了5个元素(1到5)。接着调用stream()方法获取一个包含数组元素的IntStream流,并使用forEach()方法对每个元素执行打印操作。test4():测试了使用for-each循环遍历动态数组的功能。首先创建了一个空的动态数组对象dynamicarray,然后连续调用addlast()方法向数组末尾添加了5个元素(1到5)。接着使用for-each循环遍历数组,并在每次迭代中打印当前元素的值。test5():测试了在动态数组中间插入元素的功能。首先创建了一个空的动态数组对象dynamicarray,然后连续调用addlast()方法向数组末尾添加了4个元素(7、2、4、1)。最后使用流
package SuanFa.test;import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;import SuanFa.初始算法.第一章数组.动态数组;import java.lang.annotation.ElementType;
import java.util.Arrays;public class 动态数组Test {@Testpublic void test1() {动态数组 dynamicarray = new 动态数组();dynamicarray.addlast(1);dynamicarray.addlast(2);dynamicarray.addlast(3);dynamicarray.addlast(4);dynamicarray.addlast(5);for (int i = 0; i < 5; i++) {System.out.println(dynamicarray.get(i));}}@Testpublic void test2() {动态数组 dynamicarray = new 动态数组();dynamicarray.addlast(1);dynamicarray.addlast(2);dynamicarray.addlast(3);dynamicarray.addlast(4);dynamicarray.add(2, 9);for (int i = 0; i < 5; i++) {System.out.println(dynamicarray.get(i));}}@Testpublic void test3() {动态数组 dynamicarray = new 动态数组();dynamicarray.addlast(1);dynamicarray.addlast(2);dynamicarray.addlast(3);dynamicarray.addlast(4);dynamicarray.stream().forEach(element -> {System.out.println(element);});}@Testpublic void test4() {动态数组 dynamicarray = new 动态数组();dynamicarray.addlast(1);dynamicarray.addlast(2);dynamicarray.addlast(3);dynamicarray.addlast(4);for (Integer element : dynamicarray) {System.out.println(element);}}@Testpublic void test5() {动态数组 dynamicarray = new 动态数组();dynamicarray.addlast(7);dynamicarray.addlast(2);dynamicarray.addlast(4);dynamicarray.addlast(1);dynamicarray.stream().forEach(element -> {System.out.println(element);});}
}
3)删除
该方法接受一个整数参数 index,表示要删除元素的索引位置。
首先,它将要删除的元素保存在变量 removed 中。然后,使用 System.arraycopy() 方法将索引位置后的元素向前移动一位,覆盖掉要删除的元素。
接下来,通过将数组的大小减一来更新数组的大小。
最后,返回被删除的元素。
/*** 从 [0 .. size) 范围删除元素** @param index 索引位置* @return 被删除元素*/
public int remove(int index){// 保存要删除的元素int removed = array[index];// 将索引位置后的元素向前移动一位System.arraycopy(array, index + 1, array, index, size - index - 1);// 更新数组的大小size--;// 返回被删除的元素return removed;
}
测试:删除方法
这是一个J用于测试动态数组的测试类。首先创建一个dynamicarray的动态数组对象,然后使用addlast方法向数组中添加元素1、2、3、4和5。接着**调用remove方法移除索引为2的元素,并将其值赋给变量removed。**最后,使用stream方法和forEach遍历打印数组中的所有元素。
@Testpublic void test6() {动态数组 dynamicarray = new 动态数组();dynamicarray.addlast(1);dynamicarray.addlast(2);dynamicarray.addlast(3);dynamicarray.addlast(4);dynamicarray.addlast(5);int removed = dynamicarray.remove(2);System.out.println(removed);System.out.println("-----");dynamicarray.stream().forEach(element -> {System.out.println(element);});}
测试结果如下:

4)扩容
- 该方法首先进行容量检查。
- 如果当前数组的大小为0,即数组为空,那么将创建一个新的数组,其容量为
capacity。 - 如果当前数组的大小等于容量,即数组已满,那么将通过将容量增加至原来的两倍来扩展数组。
- 然后,创建一个新的数组
newArray,其容量为新的容量值。 - 接下来,使用
System.arraycopy()方法将原数组中的元素复制到新数组中。 - 最后,将新数组赋值给原数组,完成数组的扩展操作。
- 这个方法通常用于动态数组的管理,以确保数组在插入元素时能够自动调整其容量大小,以适应不断增长的数据量。
private void checkAndGrow() {// 容量检查if (size == 0) {array = new int[capacity];} else if (size == capacity) {capacity += capacity >> 1;int[] newArray = new int[capacity];System.arraycopy(array, 0,newArray, 0, size);array = newArray;}}
5)插入或删除性能
头部位置,时间复杂度是 O ( n ) O(n) O(n)
中间位置,时间复杂度是 O ( n ) O(n) O(n)
尾部位置,时间复杂度是 O ( 1 ) O(1) O(1)(均摊来说)
3) 二维数组
int[][] array = {{11, 12, 13, 14, 15},{21, 22, 23, 24, 25},{31, 32, 33, 34, 35},
};
-
二维数组占 32 个字节,其中 array[0],array[1],array[2] 三个元素分别保存了指向三个一维数组的引用
-
三个一维数组各占 40 个字节
-
它们在内层布局上是连续的
更一般的,对一个二维数组 A r r a y [ m ] [ n ] Array[m][n] Array[m][n]
- m m m 是外层数组的长度,可以看作 row 行
- n n n 是内层数组的长度,可以看作 column 列
- 当访问 A r r a y [ i ] [ j ] Array[i][j] Array[i][j], 0 ≤ i < m , 0 ≤ j < n 0\leq i \lt m, 0\leq j \lt n 0≤i<m,0≤j<n时,就相当于
- 先找到第 i i i 个内层数组(行)
- 再找到此内层数组中第 j j j 个元素(列)
测试
Java 环境下(不考虑类指针和引用压缩,此为默认情况),有下面的二维数组
byte[][] array = {{11, 12, 13, 14, 15},{21, 22, 23, 24, 25},{31, 32, 33, 34, 35},
};
已知 array 对象起始地址是 0x1000,那么 23 这个元素的地址是什么?
答:
- 起始地址 0x1000
- 外层数组大小:16字节对象头 + 3元素 * 每个引用4字节 + 4 对齐字节 = 32 = 0x20
- 第一个内层数组大小:16字节对象头 + 5元素 * 每个byte1字节 + 3 对齐字节 = 24 = 0x18
- 第二个内层数组,16字节对象头 = 0x10,待查找元素索引为 2
- 最后结果 = 0x1000 + 0x20 + 0x18 + 0x10 + 2*1 = 0x104a
4) 局部性原理
这里只讨论空间局部性
- cpu 读取内存(速度慢)数据后,会将其放入高速缓存(速度快)当中,如果后来的计算再用到此数据,在缓存中能读到的话,就不必读内存了
- 缓存的最小存储单位是缓存行(cache line),一般是 64 bytes,一次读的数据少了不划算啊,因此最少读 64 bytes 填满一个缓存行,因此读入某个数据时也会读取其临近的数据,这就是所谓空间局部性
对效率的影响
比较下面 ij 和 ji 两个方法的执行效率
int rows = 1000000;
int columns = 14;
int[][] a = new int[rows][columns];StopWatch sw = new StopWatch();
sw.start("ij");
ij(a, rows, columns);
sw.stop();
sw.start("ji");
ji(a, rows, columns);
sw.stop();
System.out.println(sw.prettyPrint());
ij 方法
public static void ij(int[][] a, int rows, int columns) {long sum = 0L;for (int i = 0; i < rows; i++) {for (int j = 0; j < columns; j++) {sum += a[i][j];}}System.out.println(sum);
}
ji 方法
public static void ji(int[][] a, int rows, int columns) {long sum = 0L;for (int j = 0; j < columns; j++) {for (int i = 0; i < rows; i++) {sum += a[i][j];}}System.out.println(sum);
}
执行结果
0
0
StopWatch '': running time = 96283300 ns
---------------------------------------------
ns % Task name
---------------------------------------------
016196200 017% ij
080087100 083% ji
可以看到 ij 的效率比 ji 快很多,为什么呢?
- 缓存是有限的,当新数据来了后,一些旧的缓存行数据就会被覆盖
- 如果不能充分利用缓存的数据,就会造成效率低下
以 ji 执行为例,第一次内循环要读入 [ 0 , 0 ] [0,0] [0,0] 这条数据,由于局部性原理,读入 [ 0 , 0 ] [0,0] [0,0] 的同时也读入了 [ 0 , 1 ] . . . [ 0 , 13 ] [0,1] ... [0,13] [0,1]...[0,13],如图所示

但很遗憾,第二次内循环要的是 [ 1 , 0 ] [1,0] [1,0] 这条数据,缓存中没有,于是再读入了下图的数据

这显然是一种浪费,因为 [ 0 , 1 ] . . . [ 0 , 13 ] [0,1] ... [0,13] [0,1]...[0,13] 包括 [ 1 , 1 ] . . . [ 1 , 13 ] [1,1] ... [1,13] [1,1]...[1,13] 这些数据虽然读入了缓存,却没有及时用上,而缓存的大小是有限的,等执行到第九次内循环时

缓存的第一行数据已经被新的数据 [ 8 , 0 ] . . . [ 8 , 13 ] [8,0] ... [8,13] [8,0]...[8,13] 覆盖掉了,以后如果再想读,比如 [ 0 , 1 ] [0,1] [0,1],又得到内存去读了
相关文章:
究竟是什么样的讲解数组算法的博客让我写了三小时???
版本说明 当前版本号[20231004]。 版本修改说明20231004初版 目录 文章目录 版本说明目录二. 基础数据结构2.1 数组1) 概述2) 动态数组1)插入addlast 方法测试: addlast 方法 add 方法测试:add方法 addlast 方法与 add 方法合并版get 方法测试&#x…...
Day-05 CentOS7.5 安装docker
参考 : Install Docker Engine on CentOS | Docker DocsLearn how to install Docker Engine on CentOS. These instructions cover the different installation methods, how to uninstall, and next steps.https://docs.docker.com/engine/install/centos/ Doc…...
Makefile
目录 Makefile Makefile格式 Makefile函数:foreach和wildcard $(foreach var,list,text) $(wildcard pattern) 完善Makefile Makefile 在Linux中使用make工具来编译程序(特别是大程序),而make工具所执行的动作依赖于Makefil…...
c语言练习77:公因⼦的数⽬
公因⼦的数⽬ 题⽬描述: 给你两个正整数 a 和 b ,返回 a 和 b 的公因⼦的数⽬。 如果 x 可以同时整除 a 和 b ,则认为 x 是 a 和 b 的⼀个公因⼦ 。 • ⽰例 1: 输⼊:a 12, b 6 输出:4 解释&#…...
【C++】C++11——右值引用和移动语义、左值引用和右值引用、右值引用使用场景和意义、完美转发、新的类功能
文章目录 C115.右值引用和移动语义5.1左值引用和右值引用5.2左值引用与右值引用比较5.3右值引用使用场景和意义5.4右值引用引用左值及其一些更深入的使用场景分析5.5完美转发 6.新的类功能 C11 5.右值引用和移动语义 右值引用是C11引入的一个新特性,用于支持移动语义…...
Spring Boot的创建和使用(JavaEE进阶系列2)
目录 前言: 1.什么是Spring Boot?为什么要学习Spring Boot? 2.Spring Boot优点 3.创建Spring Boot项目 3.1准备工作 3.2Spring Boot创建 3.2.1通过idea的方式创建 3.2.2通过网页创建 4.Spring Boot中的配置文件 4.1Spring Boot配置…...
【OLSR路由协议】链路状态路由(OLSR)协议中选择多点中继节点算法研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
双重差分模型(DID)论文写作指南与操作手册
手册链接:双重差分模型(DID)论文写作指南与操作手册https://www.cctalk.com/m/group/90983583?xh_fshareuid60953990 简介: 当前,对于准应届生们来说,毕设季叠加就业季,写作时间显得十分宝贵…...
ping 的工作原理
ping 是一个常用于网络诊断的命令行工具,用于测试两台计算机之间的网络连通性。它的工作原理如下: 发出 ICMP Echo 请求: 当你在终端中运行 ping 命令并指定目标主机的IP地址或域名时,计算机会创建一个特殊的ICMP(In…...
93. 复原 IP 地址
有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 . 分隔。 例如:"0.1.2.201" 和 "192.168.1.1" 是 有效 IP 地址,但是 "0.011.255.24…...
竞赛选题 机器视觉的试卷批改系统 - opencv python 视觉识别
文章目录 0 简介1 项目背景2 项目目的3 系统设计3.1 目标对象3.2 系统架构3.3 软件设计方案 4 图像预处理4.1 灰度二值化4.2 形态学处理4.3 算式提取4.4 倾斜校正4.5 字符分割 5 字符识别5.1 支持向量机原理5.2 基于SVM的字符识别5.3 SVM算法实现 6 算法测试7 系统实现8 最后 0…...
第15届蓝桥STEMA测评真题剖析-2023年8月20日Scratch编程中级组
[导读]:超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成,后续会不定期解读蓝桥杯真题,这是Scratch蓝桥杯真题解析第155讲。 第15届蓝桥第1次STEMA测评,这是2023年8月20日举办的STEMA,比赛仍然采取线上形式。这…...
html5 checkbox
1 选中状态的设置与获取 (1)获取checkbox是否选中: $("#checkbox").is(":checked");$("#checkbox-id")get(0).checked$(#checkbox-id).attr(checked)(2)设置checkbox是否选中 $("…...
安装matplotlib_
安装pip 安装matplotlib 安装完毕 导入出现bug.........
STM32复习笔记(六):STM32远程升级BootLoader相关
目录 Preface: (一)STM32上电启动流程 (二)BootLoader相关 (三)Clion配置 Preface: 有关STM32的BootLoader主要还是参考了许多大佬的文章,这里只是简单地列举一下&am…...
MASA MAUI iOS 文件下载与断点续传
文章目录 背景介绍方案及代码1、新建MAUI项目2、建立NSUrlSession会话连接3、使用NSUrlSessionDownloadTask 创建下载任务4、DidWriteData 监听下载5、DidFinishDownloading 完成下载6、CancelDownload (取消/暂停)下载7、ResumeDownload 恢复下载8、杀死进程-恢复下载 效果图总…...
NPDP产品经理知识(产品创新流程)
1.复习组合管理: 组合管理的目标 > 价值最大化,项目平衡,战略一致,管道平衡(资源需求和供给),盈利充分 (实现财务目标) 产品创新流程就是管理风险的过程。 模糊前端: 产品创新章程:PIC 包…...
Android日常——记一次Android事件分发需求的实现
背景:在一个事件分发复杂的view中,插入一个可点击的控件,且不能影响到本身的事件分发。 尝试: 1.对view本身设置点击事件;由于view整体是交由root view去处理分发,存在滑动、边界处理、调出其他界面等复杂操作。设置点…...
【Python】函数(function)和方法(method)的区别
这里先说结论,为了满足心急的小伙伴:method与function的最大区别就是参数有无进行绑定。 自定义类Test: 首先先来一个自定义类: class Test:def Func_normal(arg):print(Func_normal:,arg)staticmethoddef Func_static(arg):pri…...
linux入门---信号的理解
目录标题 如何理解计算机中的信号如何查看计算机中的信号初步了解信号的保存和发送如何向目标进程发送信号情景一:使用键盘发送信号情景二:系统调用发送信号情景三:硬件异常产生信号情景四:软件条件产生信号 核心转储信号的两个问…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论
路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...
计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...
消息队列系统设计与实践全解析
文章目录 🚀 消息队列系统设计与实践全解析🔍 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡💡 权衡决策框架 1.3 运维复杂度评估🔧 运维成本降低策略 🏗️ 二、典型架构设计2.1 分布式事务最终一致…...
