Java List数据结构与常用方法

1.1 数据结构概述 

Java的集合框架其实就是对数据结构的封装,在学习集合框架之前,有必要先了解下数据结构。

1.1.1 什么是数据结构

所谓数据结构,其实就是计算机存储、组织数据的方式。

数据结构是用来分析研究数据存储操作的,其实就是对数据做增删改查操作。

  增:把某个数据存储到某个容器中

  删:从容器中把某个数据删除掉

   改:把容器中某个数据替换成另一个数据

  查:把容器中的数据查询出来

开发意识

只要提到容器,那核心操作一定是增删改查。

不同的数据结构,底层采用不同的存储方式(算法),在具体操作的时候效率是不一样的,比如有的查 询速度很快,有的插入速度很快,有的操作头和尾速度很快等。

1.1.2 为什么要熟悉数据结构

常见的数据结构:

.  数组(Array            

  链表(Linked List)     

哈希表(Hash)            

  栈(Stack                  

   队列(Queue         

树(Tree                    

   图(Graph

  堆(Heap

在未来具体业务场景时,我们需要分析具体的需求场景,是查询的操作多,还是添加的操作多?如果是 查询操作多,我们就要选择适合查询性能高的集合,如果添加操作多,我们就需要选择添加性能高的集 合。

一句话:根据具体业务场景,选择合适的集合类。

1.2 数据结构 

1.2.1 数组的性能分析

在计算机科学中,算法的时间复杂度是一个函数,它定性描述了该算法的运行时间,常用大符号来表 述。

时间复杂度是同一问题可用不同算法解决,而一个算法的质量优劣将影响到算法乃至程序的效率。算法 分析的目的在于选择合适算法和改进算法。

我们在这里针对数组存储数据的增删改查(CRUD)做性能分析:

  添加操作:

如果保存在数组的最后一个位置,至少需要操作一次。

如果保存在数组的第一个位置,如果存在N个元素,此时需要操作N(后面的元素要整体后移)

平均:  (N+1) /2 次。 N表示数组中元素的个数。  如果要扩容,更慢,性能更低。

  删除操作:

如果删除最后一个元素,操作一次。

如果删除第一个元素,操作N次。

平均:(N+1) / 2.

  修改操作:  给定索引时,操作1.

  查询操作:根据索引查询元素需要操作1次;根据内容查询需要N次;

结论:基于数组的数据结构做查询是和修改是非常快的,添加和删除操作比较慢了。

那如果想保证保存和删除操作的性能,此时就得提提链表这种数据结构了。

1.2.2 其他线性数据结构

1.2.1. 链表(了解)

链表结构(火车和火车车厢):

1):单向链表,只能从头遍历到尾/只能从尾遍历到头。

2):双向链表,既可以从头遍历到尾,又可以从尾遍历到头。

通过引用来表示上一个节点和下一个节点的关系。

单向链表:

双向链表:

对链表操作的性能分析:

双向链表可以直接获取自己的第一个和最后一个节点。

  添加操作

如果新增的元素在第一个或最后一个位置,那么操作只有1次。

  删除操作

如果删除第一个元素 :操作一次

如果删除最后一个元素:操作一次

如果删除中间的元素:

① 找到元素节点平均操作: (1+N ) / 2

② 找到节点之后做删除操作: 1

  修改操作

平均:(N+1)/2

  查询操作:

平均:(N+1)/2

结论:

数组:   查询、更改较快,新增和删除较慢。

链表:   查询、更改较慢,新增和删除较快。

1.2.2. 队列

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端 rear)进行插入操作,队列是一种操作受限制的线性表。

进行添加操作的端称为队尾,进行删除操作的端称为队头。

单向队列(Queue):先进先出(FIFO),只能从队列尾插入数据,只能从队列头删除数据。

双向队列(Deque):可以从队列尾/头插入数据,只能从队列头/尾删除数据。

结论:最擅长操作头和尾。

1.2.3. 栈(了解)

栈(stack)又名堆栈,它是一种运算受限的线性表,后进先出(LIFO),和手枪弹夹类似。

栈结构仅允许在表的一端进行插入和删除运算,这一端被称为栈顶,相对地,把另一端称为栈底。 向一个栈中插入新元素又称作入栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素。从一 个栈中删除元素又称作出栈,表示把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素(LIFO)。

1.3 非线性数据结构

1.3.1. 哈希表

一般的,数组中元素在数组中的索引位置是随机的,元素的取值和元素的位置之间不存在确定的对 应关系。因此,在数组中查找特定的值时,需要把查找值和一系列的元素进行比较。

此时的查询效率依赖于查找过程中所进行的比较次数,如果比较次数较多,查询效率还是不高。

如果元素的值(value)和在数组中的索引位置(index)有一个确定的对应关系,我们把这种关系 称之为哈希(hash)。则元素值和索引对应的公式为: index = hash(value)。也就是说,通过给定元素 值,只要调用hash(value)方法,就能找到数组中取值为value的元素的位置。

比如,图中的hash算法公式为: index = value / 10 - 1

在往哈希表中存储对象时,该hash算法就是对象的hashCode方法。

注:这里仅仅是假设算法公式是这样的,真实的算法公式我们可以不关心。

1.3.4. 树和二叉树

前面我们介绍的数据结构数组、栈、队列,链表都是线性数据结构,除此之外还有一种比较复杂的 数据结构——树。

计算机中的树,是根据生活中的树抽象而来的,表示N个有父子关系的节点的集合。

   N0的时候,该节点集合为空,这棵树就是空树

  任何非空树中,有且只有一个根节点(root

   N>1时,一颗树由根和若干棵子树组成,每棵子树由更小的若干子树组成

树中的节点根据有没有子节点,分成两种:

   普通节点:拥有子节点的节点。

   叶子节点:没有字节点的节点。

二叉树:一种特殊的,遵循某种规则的树。

树的结构因为存在多种子节点情况,真的太复杂了,如果我们对普通的树加上一些约束,比如让每 一棵树的节点最多只能包含两个子节点,而且严格区分左子节点和右子节点(左右位置不能交换),此 时就形成了二叉树。

排序二叉树,有顺序的树:

  若左子树不为空,则左子树所有节点的值小于根节点的值。

  若右子树不为空,则右子树所有节点的值大于根节点的值。

  左右子树也分别是排序二叉树。

红黑树:更高查询效率的的排序二叉树。

排序二叉树可以快速查找,但是如果只有左节点或者左右右节点的时候,此时二叉树就变成了普通 的链表结构,查询效率比较低。为此一种更高效的二叉树出现了——红黑树。

  每个节点要么是红色的,要么是黑色的。

  根节点永远是黑色的。

.  所有叶子节点都是空节点(null),是黑色的。

  每个红色节点的两个子节点都是黑色的。

  从任何一个节点到其子树每个叶子节点的路径都包含相同数量的黑色节点。

1.3 集合框架体系  

1.3.1 集合框架概述

集合是Java中提供的一种容器,可以用来存储多个数据,根据不同存储方式形成的体系结构,就叫做集 合框架体系。集合也时常被称为容器。

每一种容器类底层拥有不同的底层算法(数据结构)

既然数组可以存储多个数据,为什么要出现集合?

  数组的长度是固定的,集合的长度是可变的。

  使用Java类封装出一个个容器类,开发者只需要直接调用即可,不用再手动创建容器类。

集合中存储的数据,叫做元素(element),元素只能是对象(引用类型)。

Java中,集合操作都被设计成接口,被不同底层数据结构的实现类所实现。

1.3.2 集合的分类

根据容器的存储特点的不同,可以分成三种情况:

.  List(列表):允许记录元素的添加顺序,允许元素重复。

.  Set(数据集):不记录元素的添加顺序,不允许元素重复。

.  Map(映射):容器中每一个元素都包含一keyvalue  key不允许重复, value可以重复。严格上 说,并不是容器(集合),是两个容器中元素映射关系。

注意: ListSet接口继承于Collection接口, Map接口不继承Collection接口。

  Collection接口:泛指广义上集合,主要表示ListSet两种存储方式。

   List接口:表示列表,规定了允许记录添加顺序,允许元素重复的规范。

  Set接口:表示狭义上集合,规定了不记录添加顺序,不允许元素重复的规范。

   Map接口:表示映射关系,规定了两个集合映射关系的规范。

注意:我们使用的容器接口或类都处于java.util包中。

1.4 List接口

List接口是Collection接口子接口, List接口定义了一种规范,要求该容器允许记录元素的添加顺 序,也允许元素重复。那么List接口的实现类都会遵循这一种规范。

List集合存储特点:

  允许元素重复

  允许记录元素的添加先后顺序

该接口常用的实现类有:

  ArrayList类:数组列表,表示数组结构,底层采用数组实现,开发中使用最多的实现类,重点。

   LinkedList类:链表,表示双向列表和双向队列结构,采用链表实现,使用不多。 

  Stack类:栈,表示栈结构,采用数组实现,使用不多。

  Vector类:向量,其实就是古老的ArrayList,采用数组实现,使用不多。

一般来说,集合接口的实现类命名规则:(底层数据结构 + 接口名)例如: ArrayList

1.4.1. List常用API方法

添加操作

.  boolean add(object e):将元素添加到列表的末尾

.  void add(int index, object element):在列表的指定位置插入指定的元素

.  boolean addAll(Collection c):把c列表中的所有元素添加到当前列表中

删除操作

.  object remove(int index):从列表中删除指定索引位置的元素,并返回被删除的元素

.  boolean removeAll(Collection c):从此列表中移除c列表中的所有元素

修改操作

.  object set(int index, object ele):修改列表中指定索引位置的元素,返回被替换的旧元素

查询操作

.  int size():返回当前列表中元素个数

.  boolean isEmpty():判断当前列表中元素个数是否为0

.  object get(int index):查询列表中指定索引位置对应的元素

  object[] toArray():把列表对象转换为object数组

.  boolean contains(object o):判断列表是否存在指定对象

注意,标红的是经常使用的方法。

1.4.2.ArrayList

ArrayList类,基于数组算法的列表,通过查看源代码会发现底层其实就是一个object数组。

需求1:操作List接口常用方法

public class ArrayListDemo {

public static void main(String[] args) {

// 创建一个默认长度的列表对象

List list = new ArrayList();

// 打印集合中元素的个数

System.out.println("元素数量:"+list.size());//0

// 添加操作:向列表中添加4个元素

list.add("Will");

list.add(100);

list.add(true);

list.add("Lucy");

// 查询操作:

System.out.println("列表中所有元素:"+list);//输出 :[Will, 100, true, Lucy] System.out.println("元素数量:"+list.size());//4

System.out.println("第一个元素:"+list.get(0));//Will

// 修改操作:把索引为2的元素,替换为wolfcode

list.set(2, "wolfcode");

System.out.println("修改后:"+list);//输出 :[Will, 100, wolfcode, Lucy]

// 删除操作:删除索引为1的元素

list.remove(1);

System.out.println("删除后:"+list);//输出 :[Will, wolfcode, Lucy] }

}

需求2:创建四个User对象,存储在List中,分析内存图。

public class User {

private String name;

private int age;

//省略两个参数构造器

// getter/setter方法

// 需要重写toString方法

}

public class ArrayListDemo2 {

public static void main(String[] args) {

List girls = new ArrayList();

User u1 = new User("西施", 18);

girls.add(u1);

girls.add(new User("王昭君",19));

girls.add(new User("貂蝉",20));

girls.add(new User("杨玉环",21));

System.out.println(girls);

//修改u1对象的名字和年龄

u1.setName("小施");

u1.setAge(17);

System.out.println(girls);

}

}

运行结果(观察变化):

[User [name=西施 , age=18], User [name=王昭君 , age=19], User [name=貂蝉 , age=20], User [name=杨玉环 , age=21]]

[User [name=小施 , age=17], User [name=王昭君 , age=19], User [name=貂蝉 , age=20], User [name=杨玉环 , age=21]]

内存分析,解释原因:

结论:集合类中存储的对象,都存储的是对象的引用,而不是对象本身。

1.4.3.LinkedList

ArrayList类,基于数组算法的列表,通过查看源代码会发现底层其实就是一个object数组。

LinkedList类,底层采用链表算法,实现了链表,队列,栈的数据结构。无论是链表还是队列主要 操作的都是头和尾的元素,因此在LinkedList类中除了List接口的方法,还有很多操作头尾的方法。

.  void addFirst(object e) 将指定元素插入此列表的开头。

.  void addLast(object e) 将指定元素添加到此列表的结尾。

  object getFirst() 返回此列表的第一个元素。

  object getLast() 返回此列表的最后一个元素。

  object removeFirst() 移除并返回此列表的第一个元素。

  object removeLast() 移除并返回此列表的最后一个元素。

总结:以上API都是返回异常的。

.  boolean offerFirst(object e) 在此列表的开头插入指定的元素。

.  boolean offerLast(object e) 在此列表末尾插入指定的元素。

.  object peekFirst() 获取但不移除此列表的第一个元素;如果此列表为空,则返回 null。   .  object peekLast() 获取但不移除此列表的最后一个元素;如果此列表为空,则返回 null .  object pollFirst()  获取并移除此列表的第一个元素;如果此列表为空,则返回 null

.  object pollLast() 获取并移除此列表的最后一个元素;如果此列表为空,则返回 null

.  void push(object e) 将元素推入此列表所表示的栈。

.  object pop() 从此列表所表示的栈处弹出一个元素。

.  object peek() 获取但不移除此列表的头(第一个元素)。

LinkedList之所以有这么多方法,是因为自身实现了多种数据结构,而不同的数据结构的操作方法 名称不同,在开发中LinkedList使用不是很多,知道存储特点就可以了。

public class LinkedListDemo {

public static void main(String[] args) {

LinkedList list = new LinkedList();

//添加元素

list.addFirst("A");

list.addFirst("B");

System.out.println(list);

list.addFirst("C");

System.out.println(list);

list.addLast("D");

System.out.println(list);

//获取元素

System.out.println("获取第一个元素:" + list.getFirst());//C System.out.println("获取最后一个元素:" + list.getLast());//D //删除元素

list.removeFirst();

System.out.println("删除第一个元素后:" + list);//[B, A, D]

list.removeLast();

System.out.println("删除最后一个元素后:" + list);//[B, A]

}

}

程序运行结果:

[B, A]

[C, B, A]

[C, B, A, D]

获取第一个元素:C

获取最后一个元素:D

删除第一个元素后: [B, A, D]

删除最后一个元素后: [B, A]

1.4.4. StackVector

Vector类:基于数组算法实现的列表,其实就是ArrayList类的前身。和ArrayList的区别在于方法使用 synchronized修饰,所以相对于ArrayList来说,线程安全,但是效率就低了点。

Stack类:表示栈,是Vector类的子类,具有后进先出(LIFO)的特点,拥有push(入栈), pop(出栈) 方法。

1.5. 泛型

1.5.1. 什么是泛型

其实就是一种类型参数 主要用于某个类或接口中数据类型不确定时,可以使用一个标识符来表示 未知的数据类型 ,然后在使用该类或接口时指定该未知类型的真实类型。

泛型可用到的接口、类、方法中,将数据类型作为参数传递,其实更像是一个数据类型模板。

如果不使用泛型,从容器中获取出元素,需要做类型强转,也不能限制容器只能存储相同类型的元 素。

List list = new ArrayList();

list.add("A");

list.add("B");

String ele = (String) list.get(0);

1.5.2. 自定义和使用泛型

定义泛型:使用一个标识符,比如T在类中表示一种未知的数据类型。

使用泛型: 一般在创建对象时,给未知的类型设置一个具体的类型,当没有指定泛型时,默认类型 Object类型。

需求:定义一个类Point xy表示横纵坐标,分别使用String Integer Double表示坐标类型。

如果没有泛型需要设计三个类,如下:

定义泛型:

//在类上声明使用符号T,表示未知的类型

//在类上声明使用符号T,表示未知的类型

public class Point<T> {

private T x;

private T y;

//省略getter/setter

}

使用泛型:

// 没有使用泛型(未指明泛型),默认类型是object

Point p1 = new Point();

object x1 = p1.getX();

//使用String作为泛型类型(指明泛型为String类型)

Point<String> p2 = new Point<String>();

String x2 = p2.getX();

//使用Integer作为泛型类型(指明泛型为Double类型)

Point<Integer> p3 = new Point<Integer>();

Integer x3 = p3.getX();

画图分析:

注意:这里仅仅是演示泛型类是怎么回事,并不是要求定义类都要使用泛型。

1.5.3. 在集合框架中使用泛型

List接口和ArrayList类举例。

class ArrayList<E>{

public boolean add(E e){ }

public E get(int index){  }

}

此时的E也仅仅是一个占位符,表示元素(Element)的类型,那么当使用容器时给出泛型就表示该 容器只能存储某种类型的数据。

//只能存储String类型的集合

List<String> list1 = new ArrayList<String>();

list1.add("A");

list1.add("B");

//只能存储Integer类型的集合

List<Integer> list2 = new ArrayList<Integer>();

list2.add(11);

list2.add(22);

因为前后两个泛型类型相同(也必须相同),泛型类型推断:

List<String> list1 = new ArrayList<String>();

可以简写为

List<String> list1 = new ArrayList<>();

通过反编译工具,会发现泛型其实是语法糖,也就是说编译之后,泛型就不存在了。

注意:泛型必须是引用类型,不能是基本数据类型(错误如下):

List<int> list = new ArrayList<int>();//编译错误

泛型不存在继承的关系(错误如下):

List<object> list = new ArrayList<String>();    //错误的

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

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

相关文章

【Mac】Keyboard Maestro for Mac(键盘大师)软件介绍及安装教程

软件介绍 Keyboard Maestro for mac&#xff08;键盘大师&#xff09;是目前Mac OS平台上功能最为齐全的Mac键盘增强工具&#xff0c;它能将你的Keyboard作用发挥到极致&#xff0c;可以根据命令或计划自动执行简单或复杂的应用程序或网站&#xff0c;文本或图像。使用Keyboar…

力扣234. 回文链表

给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true # Definition for singly-linked list. # c…

Facebook开户 | Facebook海外三不限的价值

在当今数字化时代&#xff0c;海外数字营销已经成为企业推广和品牌建设的重要手段。在这个过程中&#xff0c;社交媒体平台扮演着至关重要的角色&#xff0c;而Facebook作为全球最大的社交媒体平台之一&#xff0c;其海外三不限账户近年来引起了越来越多数字营销从业者的关注。…

【数据结构与算法 | 二叉树篇】力扣101, 104, 111

1. 力扣101 : 对称二叉树 (1). 题 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1&#xff1a; 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true示例 2&#xff1a; 输入&#xff1a;root [1,2,2,null,3,null,3] 输出&#xff1a;false…

【Redis数据库】数据类型(2.3w字超详细)

文章目录 一、字符串类型概述1.1、数据类型1.2、字符串简介1.3、字符串应用场景 二、字符串命令三、哈希类型概述3.1、哈希介绍3.2、哈希类型应用场景3.3、哈希命令 四、列表类型概述4.1、列表简介4.2、使用场景4.3、列表命令 五、集合概述5.1、集合简介5.2、使用场景5.3、集合…

Vue.js 动画与过渡效果实战

title: Vue.js 动画与过渡效果实战 date: 2024/6/4 updated: 2024/6/4 description: 这篇文章介绍了如何在网页设计中使用过渡动画和组件效果&#xff0c;以及如何利用模式和列表展示信息。还提到了使用钩子实现组件间通信的方法。 categories: 前端开发 tags: 过渡动画组件…

VS 2022调试技巧:远程调试、线程检查、性能检查

&#x1f3c6;作者&#xff1a;科技、互联网行业优质创作者 &#x1f3c6;专注领域&#xff1a;.Net技术、软件架构、人工智能、数字化转型、DeveloperSharp、微服务、工业互联网、智能制造 &#x1f3c6;欢迎关注我&#xff08;Net数字智慧化基地&#xff09;&#xff0c;里面…

总交易量突破 3000 亿美元,APX Finance 成本轮牛市最大的黑马?

“APX Finance 总交易量现已突破 3000 亿美元&#xff0c;已然成为链上衍生品赛道的主力军” 自 2021 年链上衍生品市场进入萌芽期以来&#xff0c;该板块始终保持着较高的市场增速&#xff0c;即便如此该领域仍旧存在极大的发展空间。一方面&#xff0c;衍生品板块交易量目前占…

微信小程序发布遇到的一些问题记录

1.报错组件没有按需导入 在该路径配置微信小程序添加"lazyCodeLoading" : "requiredComponents" "mp-weixin" : { "appid" : "你的appid", "setting" : { "urlCheck" : f…

工业设备联网监控

在当今这个工业4.0和智能制造蓬勃发展的时代&#xff0c;如何对工业设备进行高效、智能的联网监控&#xff0c;已经成为众多企业关注的焦点。HiWoo Cloud平台以其卓越的联网监控能力和创新的技术应用&#xff0c;正成为更多企业的选择。今天&#xff0c;我们就来详细探讨一下Hi…

如何制作Peppol文件?

Peppol (Pan-European Public Procurement Online) 是一种用于跨境电子采购的标准协议和网络。它允许企业和政府机构以电子方式交换文件&#xff0c;如电子发票、订单和发货单。如果你需要制作Peppol文件&#xff0c;可以参考如下步骤&#xff1a; 准备必要工具和资源 1.Pepp…

STC设计与RTX51--RTX51操作系统

知不足而奋进 望远山而前行 目录 文章目录 前言 内容 操作系统 我们认知的操作系统 最小的操作系统 RTX51系统 RTX51 Real-Time Kernel RTX51 Tiny环境搭建 库函数与RTX51 RTX51的延时问题 K_TMO与K_IVL的区别 K_SIG信号等待 总结 前言 理解操作系统功能学会使用…

Docker搭建redis-cluster集群

1. 前期准备 1.1 拉redis镜像 docker search redis docker pull redis1. 2 创建网卡 docker network create myredis --subnet 172.28.0.0/16#查看创建的网卡 docker network inspect myredisdocker network rm myredis #删除网卡命令 多个中间 空格隔开 docker network --h…

排序数组 ---- 分治-快排

题目链接 题目: 分析: 回顾一下快速排序, 快速排序的思想就是找一个基准值key, 按照基准值, 将数组分成两块, 分别是<key和>key的区域,然后继续对这两个区域划分, 那么快速排序的时间复杂度是O(N*logN), 但是如果数组中有许多相同的元素, 如果我们选定的基准值就有相同…

补上缺失的一环----一种数据库系统主动对外推送表的增删改实时变动数据的实践

在实践中&#xff0c;一些应用程序或模块需要实时获取某些数据库表的增删改变动数据。 对此需求&#xff0c;常见的方案有: 1、应用程序通过轮循查询数据库方式获取数据库表的增删改变动数据. 2、应用程序在把数据写入数据库表之前&#xff0c;通过事件方式向外通知数据库表的增…

【HarmonyOS】应用振动效果实现

一、问题背景&#xff1a; 应用在强提醒场景下&#xff0c;一般会有马达振动的效果&#xff0c;提示用户注意力的关注。 比如消息提醒&#xff0c;扫码提示&#xff0c;删除键确认提示等。 针对高定制化或者固定的振动方式&#xff0c;我们需要有不同的方案实现&#xff0c;马…

【已解决】c++ QT继承基类界面页面丢失问题

本博文源于自己在工位上遇到的一个问题&#xff0c;这个问题不只犯了一次了。首先我继承CBaseDialog里的一个标题栏&#xff0c;结果发现&#xff0c;界面本来想这样结果变成这样&#xff1a; 结果变成这个样子&#xff1a; 问题原因 在于ui.setupUi这个层面&#xff0c;错…

C语言王国——字符函数和字符串函数(2)

目录 5 strtok函数 5.1 函数的表达式 5.2 函数模拟 6 strstr函数 6.1 函数表达式 7 strerror函数 7.1 函数表达式 7.2 例子 7.3 perror 8 strncpy、strncat、strncmp函数 四 结论 5 strtok函数 strtok函数我的理解是他是一个分割字符串的函数 5.1 函数的表达式 cha…

光伏无人机踏勘需要使用哪些设备?用到哪些原理?

随着全球能源结构的转型和绿色能源的大力推广&#xff0c;光伏电站的建设和运维正成为能源领域的热点。然而&#xff0c;光伏电站的选址、建设和后期运维过程中&#xff0c;往往面临着地形复杂、设备分散、巡检难度大等挑战。在这一背景下&#xff0c;无人机踏勘技术以其独特的…

最新 Navicat Data Modeler 4 | 产品介绍

在过去的几周里&#xff0c;我们已经介绍了 Navicat 版本 17&#xff0c;现在我们来把注意力转移到另外两个值得关注的产品上&#xff0c;即 Navicat Data Modeler 和 Navicat BI&#xff08;之前称为 Navicat Chart Creator&#xff09;。今天的博客将介绍 Navicat Data Model…