当前位置:网站首页>数组相关 内容 解析
数组相关 内容 解析
2022-08-04 03:05:00 【牧..】
文章目录
数组
什么是 数组?
数组 : 可以看成是相同类型元素的一个集合。在内存中是一段连续的空间。
这里 就可以 拿 车库 来举例子:
在java中,包含6个整形类型元素的数组,就相当于上图中连在一起的6个车位,从上图中可以看到:
数组中存放的元素其类型相同
数组的空间是连在一起的
每个空间有自己的编号,其实位置的编号为0,即数组的下标 、
另外 : int[] array 这里的 int[] 相当 于 是 一个 数据类型(整形数组)。 array 相当于 一个变量 , 这里就 是 创建了 一个变量 array 他的 数据 类型 是 整形 数组,
这里我们有了 数组就能 解决 一下 不必要的麻烦,
如: 这里我们 要 为 六个 同学 记录 他们的 年龄信息 , 如果 创建 变量,就会 显得 频繁 这里 就可以直接使用 数组来创建。
下面就来 了解 一下 如何 创建数组。
创建数组 的 三种方式
1.静态 初始化
基本 语法 : 数据类型[] 数据名称 = {初始化 数据};
代码演示:
public static void main(String[] args) {
int[] arr = {
1,2,3,4,5,6};
}
这里 就 提出一个问题, 整形 数组 能不能存储 其他类型的 数据呢?
举例: 创建 一个 整形数组,存储 浮点型 , 可以明显看到这里报错了。
注意事项: 静态初始化的时候, 数组元素个数和初始化数据的格式是一致的(依据初始化元素的数量,来决定数组的大小)
另外 : 我们 整形 数组 是可以 存储 字符类型的
这里 字符的 本质 是 ASCII 码值 , 我们就可以 通过 整形 数组 来存放。
静态 初始化看完,我们来看 动态。
2.动态 初始化
基本语法: 数据类型[] 数组名称 = new 数据类型 [] { 初始化数据 };
public class TheDefinitionAndUseOfArrays {
public static void main(String[] args) {
int[] array = new int[]{
1,2,3,4,5,6};//其表达意思与作用与第一种方法相同,就是创建一个数组,并初始化数据
}
}
注意:
两个[]中,都不能有数字的存在
new 是java中的一个关键字,其作用是实例化一个对象 ,
这里就 意味着 Java 当中的 数组 也是 一个 对象。
对象 会在 类和 对象中 ,讲 ,这里 不要急 。先知道 这个东西。
这里我们 动态初始化 还 有一种 :
基本 语法: 类型[] 数组名 = new 类型[元素个数];
注意: 这种 写法, 没有 初始 化 数组, 会默认 分配 0 给数组。
public class TheDefinitionAndUseOfArrays {
public static void main(String[] args) {
int[] array = new int[6];
// 该写法表达的意思是 现在有一个数组,长度为6,注意数组的元素,并没有对其初始化,在Java中,默认6个元素都是0
// 等价于 int[] array={0,0,0,0,0,0},
}
}
另外 : 在Java中的数组 也可 写成 c 一样 int arr[] ,但 一般 不推荐 这么 写 。
数组的使用
获取数组长度: 通过 数组名 .length
在 java 中 没有和 c 语言 的 sizeof
,但 是 有 比 sizeof 更加 方便的 length
, 它能够 制动获取 数组的长度(元素个数),十分方便,这里我们就来看一下。
这里 就 求出了 数组的 长度。
获取 某下表的 元素
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int[] arr = new int[10];
for(int i = 0;i<arr.length;i++){
arr[i] = i;
}
int n = sc.nextInt();
int ret = arr[n];
System.out.println(ret);
}
这里我们 来 看一下 数组 越界 异常 , 多看 看 错误 信息, 在 后面 讲到 异常的 时候 能 更好 理解。
了解 数组 越界 异常
这里 我们 还是 那 上面的 代码 , 我们 输入 11 ,数组的 大小 为 10 这里 就 会 越界, 的
错误 信息 : java.lang.ArrayIndexOutOfBoundsException
这里 的 11 就算 错误 的 地方。
遍历 数组
1. for 循环 遍历 数组
2. 增强for 循环 , for - each循环 遍历 数组
这里 我们 来 区别 一下 for 和 for - each
for 循环 可以 拿到 下标, 而 增强for 循环 拿不到 下标, 这就算最大 的 区别,当我们 只要元素 值 ,而不需要 下标是 就可以 使用 增强for 循环。
另外 for-each 是 for 循环的另外一种使用方式. 能够更方便的完成对数组的遍历. 可以避免循环条件和更新语句写错.
3. 通过 java 中 写 好的类 Arrays
Arrays.toString
: toString 将当前的数组元素,转换成字符串形式,并将其返回(也可以说 将参数的数组,以字符串形式进行输出)
Arrays.toString 讲 数组 变为 字符串 输出 , 我们 可以 用 一个 字符串 类型 的 变量来接收, 这里 也可以 直接打印, 为了 好看这里就没有 直接 打印。
java 中 的 引用
在 了解 引用 之前我们 先来 简单的 了解 一下 jvm 的 内存分布:
内存是一段连续的存储空间,主要用来存储程序运行时数据的。比如:
- 程序运行时代码需要加载到内存
- 程序运行产生的中间数据要存放在内存
- 程序中的常量也要保存
- 有些数据可能需要长时间存储,而有些数据当方法运行结束后就要被销毁
如果对内存中存储的数据不加区分的随意存储,那对内存管理起来将会非常麻烦。比如:
因此JVM也对所使用的内存按照功能的不同进行了划分:
程序计数器 (PC Register): 只是一个很小的空间, 保存下一条执行的指令的地址
(假设 写代码的时候, 中途 有事 退出,程序计数器,就会 帮你 记住 当前 写 的 位置,等你 回来的 时候,就能够 告诉你 )
虚拟机栈(JVM Stack): 与方法调用相关的一些信息,每个方法在执行时,都会先创建一个栈帧,栈帧中包含
这里 java虚拟机栈 就 先当与 我们 常说 的 栈帧
有:局部变量表、操作数栈、动态链接、返回地址以及其他的一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。
本地方法栈(Native Method Stack): 本地方法栈与虚拟机栈的作用类似. 只不过保存的内容是Native方法的局部变量. 在有些版本的 JVM 实现中(例如HotSpot), 本地方法栈和虚拟机栈是一起的
堆(Heap): JVM所管理的最大内存区域. 使用 new 创建的对象都是在堆上保存 (例如前面的 new int[]{1, 2,3} ),堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销毁。
(堆 一般 存放 的 是 对象)
方法区(Method Area): 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据. 方法编译出的的字节码就是保存在这个区域
稍微 了解 一下 即可 ,下面我们继续 回到 数组。
这里我们 的数组 就是 引用类型, 存放 的 堆区 的 一块 空间 (地址)。
这里 引用 很像 指针, 这里 我们 就来 谈谈 他们的 相同之处,和 区别, 引用 和指针 都是 存放 地址, 但是 指针 能够后 解引用,而 引用 不能 解引用, 这里 就 能够 更好的证明, 为啥java 中没有 传 址 ,而 c 能够 传地址。
那么 这里就 会 有 另外 一个问题, c语言中的 指针 能够 赋值 NULL ,那么java 中 的引用 能不能 赋值 NULL呢 ?
其实 是 可以的 但是 java 中的 NULL是 小写的 null。 这里 只是简单的 举例 c 和 java 中的 引用 和 指针 的 相同 和 区别,c 中的 指针 和 java 中的 引用 还是 有 很大 区别的。
演示:
这里 就表示 数组 不 指向 任何 地方,打印 数组 ,自然为空。
如果 这里 想要 求 数组 长度呢?
这 里 就 报错了,来 看看
错误 信息 : java.lang.NullPointerException
,空指针异常, 这里 我们的 引用都为空何来的 长度,这里就是 错误。要注意。
下面我们 就 了解一下 数组 传参
1.通过 方法打印 数组
这里我们就来画图 来看看 是 如何 讲 数组 当作 参数 传递 的 。
下面图简化了 :
下面我们 来 看 两个 题目 , 有助于 更好的 理解 引用、
思考一下 这里 两次 打印 的 结果 为 ?
可以看到 我们 的 func2 将 数组 改变了 ,而 func 却 没有 ,为啥呢 ?
请看:
第一步:
第二步:
第三步:
下面我们 来 谈谈 引用 能否 同时 指向 多个 对象
这里我们 打印 数组 的长度 发现 是 5 ,这里 我们 也 印证 引用 不能 指向 多个 对象,如果 多次给 引用 赋值 对象 ,那么 引用 指向 会 是 最后 那个赋值 的 对象
结论: 一个引用只能指向一个对象(一个引用只能保存一个对象的地址)
讲了 这么 久 , 那么 这里 有个 疑问 引用 都是 在 栈 上的 吗?
答案是不一定的,因为一个变量在不在栈上,是你变量的性质决定的
如果你的引用是一个局部变量,那就一定在栈上 实例成员变量那就不一定了。(有关于 成员变量 的 知识 ,会 在 类和 对象 中 讲到)。
局部变量的引用保存在栈上, new 出的对象保存在堆上. 堆的空间非常大, 栈的空间比较小.因为 堆 是整个 JVM 共享一个, 而 栈 每个线程具有一份 (一个 Java 程序中可能存在多个栈)
填坑 : 使用 数组 交换 两个变量
下面我们 就来 填坑, 还记得 在方法中 ,讲过 2种交换 变量 的 值 ,其中 之一 就是 通过 数组,那么我们 就来 操作 一下。
这里 就通过 数组 交换了 两个 变量的 值。
数组 除了 当 参数 也可以 当作方法 的返回值,这里 我们就来看一下。
数组作为返回值:
题目: 不改变 原有的 数组情况 下, 将 数组 扩大 两倍
如果 我们 这样写 就会 破坏 原有 的 数组。
那么 我们 就可以 在 方法中 ,创建 一个 新 的 数组,
new 一个 大小 为 arr.length 的 数组 ,即便 方法 结束 ,栈区的 空间 被销毁 但 我们 通过 返回 一个 数组 ,将 在 堆区 新开辟 的 地址 给 返回 了 过来。
做完 这个练习 ,我们 一直 用人家 的 toString 方法
,那么 我们 就来自己 实现 一下 自己 的 toString方法
MytoString
我们 可以看到 通过 toString 打印出来的 数组 是
[ + ,(逗号) + 数字 +, 逗号 +] ,这里我们 就可以通过 之前我们学过的String 拼接 ,在 一开始 添加 一个[ 然后 拼接 上我们 的 数组 元素,
在拼接 上 , (逗号 )
注意: 这里 我们 可以看到 最后 一个 元素没有 , (逗号) 这里 我们 就 需要 添加 条件 . 那么 要添加 什么 条件呢 , 其实 也非常 简单 ,这里我们 只需最后 一个 元素 不添
加 逗号 那么 当 i < arr.length - 1
就需要 添加 逗号 而 i > arr.length - 1
我们 就不要 添加 逗号.
那么代码 就可以 这么 写:
实现完 我们自己 的 toString 方法. 接下来 来 几道 练习 题 来 更好 的熟悉数组.
题目 一 : 找数组中的最大元素
题目 很简单 这里我们 就 直接 遍历数组 然后 一个 一个比较 找出 最大值.
public static int max(int[] arr){
if(arr == null){
// 这里 我们 arr 如果为空 我们直接返回 -1, 后面
//学到 异常时我们 可以 通过 抛出异常。
return -1;
}
int max = 0;
for(int i = 0;i<arr.length;i++){
if(max > arr[i]){
//找到 大于 当前max 更新 max;
max = arr[i];
}
}
return max;
}
public static void main(String[] args) {
int[] arr = {
2,3,5,1,11,4,66,55};
int max = max(arr);
System.out.println(max);
//为了 体现 方法的 返回值,我们 这样写 ,也可以直接打印。
}
题目二 : 查找数组中指定 元素
这题 也 非常简单 我们 还是 遍历 数组 就可以 直接 完成
public static int find(int[] arr,int n ){
//规定一下 如果 没有找到 返回 -1
for(int i = 0;i<arr.length;i++){
if(arr[i] == n) {
return i;
}
}
// 走到 这里 循环结束 还没有找到 返回 -1、
return -1;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// n 为 要查找 的 元素。
int n = sc.nextInt();
int[] arr = {
2,3,5,1,11,4,66,55};
int ret = find(arr,n);
System.out.println(ret);
}
这里我们 改一下题目 ,我们 只需确定 数组 中 是否 含有某个元素,不需要返回其下标.
那么我们这里就可以 使用 for - each 来 完成 (通过 for 循环 也可以 ,主要加深点 for - each 印象)
public static boolean find2(int[] arr,int n){
for(int x : arr){
if(x == n){
return true;
}
}
return false;
}
这里 返回类型 就可以 改为 boolean ,直接 返回 真 或 假 .
题目 三: 二分查找
注意 二分 查找 是 针对 有序 数组
的 , 每次 查找 会 减少 一半 他 的时间 复杂度 就为 O(log N) ,如果 不知道 时间复杂度这里 会在 数据结构中 学习到,这里 知道 有 这么 个东西 即可.
知道了 大致流程 我们就来 完成我们 的 代码:
public static int BinarySearch(int[] arr, int n) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (arr[mid] < n) {
// 此时 说明 n 在 mid 的 右边
left = mid + 1;
} else if (arr[mid] > n) {
//此时 说明 n 在 mid 的 左边
right = right - 1;
} else {
// 此时 说明 找到了
return mid;
}
}
//如果 left > right 那么 就说明 没有找到
return -1;
}
public static void main(String[] args) {
int[] arr = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int ret = BinarySearch(arr,5);
System.out.println(ret);
}
这里 完成了 我们的 二分查找.
补充: 其实 在 java中 我们 可以通过 Arrays 这个 类, 来 调用 java中 只带 的二分查找,
语法: Arrays.binarySearch()
public static void main(String[] args) {
int[] arr = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int ret = Arrays.binarySearch(arr,5);
System.out.println(ret);
}
题目 三 : 判断 数组 是否有序 (这里 按照 升序)
直接 上代码 也不难 还 是 针对 拿 几种 遍历 数组 来 展开.
如果 是 这样 写 就要 注意:
这里 报错 了 了 为啥 呢 ? 这里我们 要注意 的就是 i < arr.lengt 当 i == arr.lengt - 1 是 arr[ i + 1] 是不是 就 越界 了 , 这里 我们 就 需要 将 条件
改为 i < arr.length - 1 即可 .
将 i < arr.length 改为 i < arr.length - 1
题目 四 : 冒泡 排序
这里 偷个懒 : 可以 看一下 我 排序 的博客 里面 就 通过 图 的 形式 将 冒泡 排序 完成 了 , 还有 其他 几大排序 ,如果觉得 难 等 学到 ,在 看 也行,现在 只看 冒泡 排序 即可 : 上 链接 :排序(sort)
可以 看到这 里 我们就 完成 了 冒泡 排序, 下面 我们 在来看 一下 优化 .
public static void bubbleSort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
boolean flg = true;
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flg = false;
}
}
if (flg) {
break;
}
}
}
public static void main(String[] args) {
int[] arr = {
1, 3, 44, 21, 2, 45, 66, 1, 2};
System.out.println(Arrays.toString(arr));
bubbleSort(arr);
System.out.println(Arrays.toString(arr));
}
这里我们 添加 了 一个 flg 判断 ,如果 没有 进入 if 语句 就 说明 数组 已经 是 有序的 .
补充: 这里我们 java 也是 有排序 的 ,但 不是 基于 冒泡 排序 实现 的 ,这里冒泡 排序 的 时间 复杂度 不管 有没有 优化 都是 O(N^2) 太慢了.
下面 就来 看一下
这里 就 通过 Arrays.sort 完成我们 的排序
下面再来 补充 一个 Arrays 类 中 的方法 fill
fill 单词 的 意思 是 填满 , 这里 就是 将 数组 填满 成 某个元素
演示:
可以 看到我们 默认 值 0 全部 变为 了 520 .
另外 Arrays.fill 也是 可以 指定 填充 范围 的 .
演示:
注意: 在 java 中 很多 带范围 的 都是 左 闭 右开 的结构.
下面 继续
题目 五 : 数组逆序
通过 两个 指针 一个 指向下标 0 一个 指向 下标 arr.length - 1 , 交换即可 知道 left = right 那么 就 说明 逆序 完成.
public static void reversed(int[] arr) {
int left = 0;
int right = arr.length - 1;
while (left < right) {
int tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
left++;
right--;
}
}
public static void main(String[] args) {
int[] arr = {
1,2,3,4,5};
System.out.println(Arrays.toString(arr));
reversed(arr);
System.out.println(Arrays.toString(arr));
}
题目 六 : 数组数字排列
给定一个整型数组, 将所有的偶数放在前半部分, 将所有的奇数放在数组后半部分 例如 {1, 2, 3, 4} 调整后得到 {4, 2, 3, 1}
沿着 上一题 思路 : 我们 只需要 在 前面找到 为 奇数 的 放在与 后面 找到 为 偶数 的 交换 即可
public static void swap(int[] arr) {
int left = 0;
int right = arr.length-1;
while (left < right) {
while (left < right && arr[left] % 2 == 0) {
left++;
}
while (left < right && arr[right] % 2 != 0) {
right--;
}
int tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
}
}
public static void main(String[] args) {
int[] arr = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
swap(arr);
System.out.println(Arrays.toString(arr));
}
题目 看完是不是 对 数组有 很 深刻 的理解 了,那么 下面我们来看一下 数组 的 几种 拷贝
数组拷贝
1.循环拷贝
直接 通过 for 循环 来拷贝.
2.通过 java提供 的方法 拷贝
java 提供 了 拷贝 方法 Arrays.copyOf
这里我们 打开 java 的 帮助 手册 ,可以 看到 copyOf 需要 的参数 ,这里 我们就来使用 一下.
另外 :
我们 拷贝 的 长度 大于 数组 原来 的 长度 ,copyOf 就 会 帮我们 扩容 , 元素 补 0 , 注意 : 后面 商用 copyOf 完成我们的 扩容 操作
除了 copyOf ,我们 可以 使用 copyOfRange
下面 就来 演示 一下:
这里 就 指定 了 0 到 5 下标 的元素 拷贝. 同样 5 是 没有 包含 的 这里 也 是 左闭 右开 .
最后我们 来 看一下 , c / c ++ 实现的 一个 方法 .
System.arraycopy
,
这里 我们 可以 通过 Ctrl + 鼠标左键
来 查看 他 的 源码
看到 native 就说明 他是 被 c / c++实现 的 ,我们 如果 在 Ctrl + 鼠标左键
点击 arraycopy 是查看 不了的 .
另外 : native 方法 是 由 c++ 、c 实现的优点快
数组 克隆
语法: 数组名. clone().
注意: 深浅拷贝
学完 拷贝 其实 会有 一个 问题 , 深浅 拷贝 ,这里 面试 是 会 被问到 .
下面我们 就来看 一下.
深拷贝 : 拷贝完成 后 通过一个引用来修改 拷贝 的数组 ,原来的数组不会受影响 就为 深拷贝
浅拷贝 :拷贝完成 后 通过 一个引用来修改 拷贝的数组,原来的数组一同被 修改就为 浅拷贝
注意 :大多情况 下 拷贝的是对象 ,深拷贝 或 浅拷贝 都是 人为实现 的
一维数组 差不多 就 这么 多 知识 ,下面 我们 来 看一下二维数组.
二维数组
1、二维数组 的创建
基本语法 :
1.数据类型[][] 数组名 = { 初始化数据 }
2.数据类型[][] 数组名 = new 数据类型[][]{ 初始化数据 }
3.数据类型[][] 数组名 = new 数据类型[行数] [列数]
演示 : 创建 一个两行 三列的 二维数组
public static void main(String[] args) {
不能这么写,需要自己定义行和列
int[][] array = {
1,2,3,4,5,6}; error
int[][] array = {
{
1,2,3},{
4,5,6}};
int[][] array2 = new int[][]{
{
1,2,3},{
4,5,6}};
int[][] array3 = new int[2][3];
}
}
一般 我们 认为 二维 数组 是 这样 的 ,但java 中 的 二维数组 相对于 c 又有点 不同 ,c 语言 中 的 二维 数组 ,是连续的,而 java 中 的 二维 数组 其实 是 这样的
下面我们 就来 对 二维 数组 打印
1.通过 for 循环 打印
这里 我们 通过 arr.length 求出列数 , 通过 arr[i].length 求出 每列 的 行数, 有没有 疑问 这里 为啥 不直接 写 arr[0].length ,而是 写 arr[i].length
不要急 ,这就是 下面 要 说 java 不一样 的 地方.
2.通过 for - each 来 打印 二维数组
注意: arr 元素的数据类型是一个一维数组,我就需要一个相同的类型的变量来接收.
还是 这 张图 ,可以 明显 的看到 我们 的 二维 数组 存放的 是一维数组的 地址 ,那么我们 通过 for - each 就 得 使用 一个 一维数组 来接收.
最后 我们 来看一下 我们的 不规则二维数组
不规则 二维 数组
演示:
注意: 这里 我们 的 列 是 可以 省略 的
如 :
那 这样 创建 有啥 意义 呢 ?
其实 这里 就 是 不规则 数组 的 精髓 我们 可以 根据 我们的 需要 设计 二维 数组 中 存放 一维数组 的 大小 .
另外 : 我们 如果 没有 给 二维数组 赋值 的 话 使用 就会出现 错误
如果 省略 掉 行 , 加 上 了 列 也会 就会报错 .
边栏推荐
- 查看mysql死锁语法
- Deep Learning (3) Classification Theory Part
- 【原创】启动Win10自带的XPS/OXPS阅读器
- activiti流程执行过程中,数据库表的使用关系
- Detailed analysis of scaffolding content
- Gigabit 2 X light 8 electricity management industrial Ethernet switches WEB management - a key Ring Ring net switch
- 融云「音视频架构实践」技术专场【内含完整PPT】
- 各位大佬好,麻烦问一下flink cdc oracle写入doris的时候,发现cpu异常,一下下跑
- LeetCode每日一题(2285. Maximum Total Importance of Roads)
- 自制蓝牙手机app控制stm8/stm32/C51板载LED
猜你喜欢
一个属于程序员的七夕节!
4-way two-way HDMI integrated business high-definition video optical transceiver 8-way HDMI high-definition video optical transceiver
架构实战营模块三作业
高效IO模型
new Date converts strings into date formats Compatible with IE, how ie8 converts strings into date formats through new Date, how to replace strings in js, and explain the replace() method in detail
Rongyun "Audio and Video Architecture Practice" technical session [complete PPT included]
Pine脚本 | 如何显示和排版绘图开关?
Zabbix设置邮件告警+企业微信告警
base address: environment variable
sqoop ETL工具
随机推荐
Mockito单元测试
出现504怎么办?由于服务器更新导致的博客报504错误[详细记录]
The general SQL injection flow (sample attached)
new Date converts strings into date formats Compatible with IE, how ie8 converts strings into date formats through new Date, how to replace strings in js, and explain the replace() method in detail
【翻译】Terraform和Kubernetes的交集
董明珠直播时冷脸离场,员工频犯低级错误,自家产品没人能弄明白
STM8S105k4t6c--------------点亮LED
FPGA parsing B code----serial 3
sqoop ETL tool
What is the source of flinkcdc consuming mysql binlog data without sqltype=delete
typescript type 和 interface 的区别
activiti流程执行过程中,数据库表的使用关系
全网没有之一的JMeter 接口测试流程详解
脚手架内容详解分析
如何读取 resources 目录下的文件路径?
验证码业务逻辑漏洞
P3384 【模板】轻重链剖分/树链剖分
base address: environment variable
2022年最新海南建筑八大员(材料员)模拟考试试题及答案
Simple record of Flink principle flow chart