当前位置: 首页 > news >正文

Flutter状态管理 — 探索Flutter中的状态

前言

随着响应式编程的理念&Flutter被大众所了解以来,状态管理一直是一个引人深思的话题。如果想要学习好Flutter这样的响应式的编程框架就一定是离不开状态管理的。我遇到过很多没有了解过响应式编程框架的,或者从事后端开发,自己想用Flutter写个app玩玩的朋友,一上来,不管在哪里都用setState,我问为啥不用状态管理,大部分都回了一句:啥是状态管理?当然,曾经的我也一样,啥也不懂,一上来就用上了GetX这个“大杀器”从而导致走了许多弯路。

ps:都2023年了,已经有那么多的大佬写了与状态管理相关的文章,为啥我还要“炒冷饭”?因为一句话:GetX你害得我好惨!同样,不同的人对于状态管理有不同的看法,一千个读者眼中就会有一千个哈姆雷特。我希望能通过几篇文章来帮助和我类似经历的朋友,更好的明白,啥是状态管理。

需要我们管理的状态有哪些

在一个应用中,存在着大量的状态,例如某个组件的动画状态、界面的外观效果、字体…当然,很多的状态并不需要我们自己去做管理,框架本身就已经做了这部分的工作了,例如:将Widget树转换为底层的图像或纹理、无需手动跟踪和管理动画状态,以及处理布局,包括大小、位置和约束,无需手动计算和管理布局状态。这才可以让我们开发者更专注于构建用户界面和交互逻辑,而无需过多关注底层的状态细节。而需要我们开发者去管理的状态可以分为两类:

  • 短时状态

你可以简单的将其理解为:某些数据状态只需要在当前的Widget中使用,不需要将这些数据和状态共享给其他组件或者页面。通常这样的情况,你不需要用到一些provider、GetX这样的状态管理框架,你仅仅需要一个StatefulWidget,依靠其自身的State管理即可,这种状态也不会以复杂的方式而改变。像一个提示消息状态(Toast)、页面切换时的一些动画(淡入淡出效果)、动画状态(一个淡入淡出的动画或一个旋转动画都是短时状态)。

class _MyHomePageState extends State<MyHomePage> {Color _buttonColor = Colors.blue; // 初始颜色void _changeButtonColor() {setState(() {_buttonColor = Colors.green; // 更新颜色为绿色});// 等待1秒后恢复原样Future.delayed(Duration(seconds: 1), () {setState(() {_buttonColor = Colors.blue; // 恢复原样});});}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('短时状态'),),body: Center(child: ElevatedButton(onPressed: _changeButtonColor,style: ElevatedButton.styleFrom(backgroundColor: _buttonColor, // 使用当前状态中的颜色padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),),child: const Text('Change Color',style: TextStyle(fontSize: 18),),),),);}
}

当用户点击 “Change Color” 按钮时,按钮的颜色会在一秒钟内变为绿色,然后恢复为蓝色。这里的按钮颜色变化过程就是一个短时状态,因为它只在特定的时间段内有效,不需要长期存储,也不需要共享。

  • 应用状态

当数据或状态需要被共享时,就是当一个组件的状态发生变化后,其他的组件也会跟着发生变化,这就是应用状态。建议通过状态管理库(如Provider)来管理。应用状态的生命周期更长,通常会影响应用全局行为,例如用户身份、主题、国际化等。

状态提升

在使用状态管理之前,我们还需学习下状态提升,它能使我们更好的理解到状态管理的作用!话不多说,直接上案例:

可以从图中发现,第一个页面的值与第二个页面的值是不同步的。在很多业务场景下,都需要将二级的详情页面的值与一级页面的值同步,例如文章的点赞数。那么这个时候会有一种简单的方法,就是将这个值提取到父级页面,由父级页面把状态传递下去。

我们抽离出一个Count类,并在父级界面实例化一个Count类,通过这种方式,第一个页面和第二个页面将共享同一个计数器对象,因此无论在哪个页面中增加计数器的值,都会同步地影响另一个页面中的计数器。这就实现了状态的提升和同步。不过,我们虽然通过状态提升的方法做到了状态同步这一点,但是,仅仅是一个Count数字的改变,却需要将第一个页面和第二个页面上的所有组件进行重新build,简单的页面当然看不出什么,一旦页面复杂,渲染的成本实在是太高,也违背了我们使用状态管理的一个初衷:控制组件局部刷新。为了能更好的解决这样的问题,我们需要探索新的方法,这时,状态管理就起到作用了!

Flutter中有哪些可以做到状态管理

1.setState

我相信无论是新手还是老手,只要是体验过Flutter框架99%的朋友都知道setState,大部分情况只需要在改变UI数据的逻辑外面套上一个setState就可以实现对界面的更新。在本文中我们不过多讨论setState的使用方式以及原理,我们聊聊它的优点和缺点。

  • 优点

相信大家在各种Flutter交流群中应该都有遇到过群友问性能相关的问题。而一些新手朋友这个时候看的多了就会有一个疑惑:我写的一些页面也卡卡的,帧率只有30几帧,会不会是因为我使用了setState去刷新页面啊?其实不然,setState不但不会造成性能困扰,它反而在帮你!最关键的一点,是因为Flutter在底层实现了一些机制来减少不必要的重绘。

具体一点来说,当去调用setState时,Flutter会将新的状态放入队列中,并计划在下一个绘制周期(Frame)中更新UI。在下一个绘制周期到来之前,Flutter会执行一些优化步骤:

    • 差异校验:Flutter会比较前后两个状态,确定实际上哪些部分需要更新。这种差异校验能够有效地减少不必要的布局、绘制和合成操作。例如:
    • 合并多个setState调用:如果在同一个绘制周期内连续调用了多次setState,Flutter会将它们合并成一个,以避免不必要的重复工作。setState只是调用了_element的markNeedsBuild方法,以标记这个元素需要在下一个绘制周期中进行重建。这个标记会将新的状态放入队列中,并在下一个帧(绘制周期)中触发重建。
    • 重绘策略:Flutter会尽量减少需要重新绘制的部分。如果某个部分没有发生变化,那么不会重新绘制它,从而节省CPU和GPU资源。将组件单独抽离为一个widget,就可以通过setState来更新局部状态,实现局部刷新。

所以,只要能正确的使用setStatesetState的节点越远离根部,那么布局、绘制渲染的开销就会越少。(使用绝对宽高也可以减少开销哦~)

  • 缺点

聊完了setState的优点,那么我们再来聊聊它的缺点:

    • **无法做到跨组件共享数据
      **setStateState的函数,一般我们会将State的子类设置为私有,所以无法做到让别的组件调用StatesetState函数来刷新。
    • 维护成本极高
      在文章的开头,就提到过,一些新手写Flutter时,不管在哪里都用setState,搞的哪哪都是。随着页面状态的增多,调用setState的地方会越来越多,不能统一管理。难以维护。
    • **状态和UI耦合
      **使用状态管理很重要的一点就是解耦,而随意使用setState会导致数据逻辑和视图混合在一起,比如数据库的数据取出来setState到ui上,这样编写代码,会导致状态和UI耦合在一起,不利于测试,不利于复用。
    • 实现局部刷新复杂度高
      setState是整个Widget重新构建(子Widget也会跟着销毁重建),如果页面足够复杂,就会导致非常严重的性能损耗。而如果想要通过setState进行局部刷新,就需要对组件进行提取,如果每个组件都要封装提取一下,这个工作量太多了!
2.ChangeNotifier

为了解决setState带来的一些问题,可以通过ChangeNotifier作为状态管理的方案。 ChangeNotifier可以将状态逻辑和UI逻辑分开,从而使得状态管理更加集中和可控。通过创建一个单独的ChangeNotifier类来管理某一部分状态,然后在需要使用这些状态的地方进行订阅。

class CounterModel extends ChangeNotifier {int _counter = 0;int get counter => _counter;void increment() {_counter++;notifyListeners();}
}class MyApp extends StatelessWidget {final CounterModel _counterModel = CounterModel();@overrideWidget build(BuildContext context) {return MaterialApp(home: Scaffold(appBar: AppBar(title: Text('ChangeNotifier Example')),body: Column(mainAxisAlignment: MainAxisAlignment.center,children: [CounterDisplay(_counterModel),CounterControl(_counterModel),],),),);}
}class CounterDisplay extends StatefulWidget {final CounterModel counterModel;CounterDisplay(this.counterModel);@override_CounterDisplayState createState() => _CounterDisplayState();
}class _CounterDisplayState extends State<CounterDisplay> {@overridevoid initState() {super.initState();widget.counterModel.addListener(_update);}@overridevoid dispose() {widget.counterModel.removeListener(_update);super.dispose();}void _update() {setState(() {});}@overrideWidget build(BuildContext context) {return Center(child: Text('Value: ${widget.counterModel.counter}'),);}
}class CounterControl extends StatelessWidget {final CounterModel counterModel;const CounterControl(this.counterModel, {Key? key}) : super(key: key);@overrideWidget build(BuildContext context) {return Center(child: ElevatedButton(onPressed: () {counterModel.increment();},child: Text('+'),),);}
}

不过,虽然ChangeNotifier解决了一些问题,但在大型应用中,仍然可能遇到一些挑战。例如,当应用状态较为复杂时,可能需要多个ChangeNotifier,从而导致状态分散和层级复杂。

3.ChangeNotifier+InheritedWidget

在前面我们提到可以通过状态提升的方法,实现跨组件之间的数据传递。但是一些简单的小项目都会有很多很多的组件,组件之间的继承关系会很复杂,如果只是使用状态提升,然后通过传参和回调函数这样的方式,那么你会发现,在继承关系中间的一些组件需要传入大量的参数,相当麻烦!那怎么让底层的子组件直接去访问被我们提升到父级的状态呢?Flutter已经帮我们解决了这个问题,就是使用InheritedWidget,它可以高效快捷的实现共享数据的跨组件传递。一些初学者可能会觉得InheritedWidget很陌生,但是,你一定使用过InheritedWidget传递状态的场景:

Theme.of(context).primaryColor //获取主题色
MediaQuery.of(context).size.width;  // 获取屏幕宽度

点击他们的实现源码就可以看到使用了context.dependOnInheritedWidgetOfExactType。使用inheritedWidget的时候,会有一个取数据的过程,这个时候就会通过子节点的BuildContext使用context.getElementForInheritedWidgetOfExactTypecontext.dependOnInheritedWidgetOfExactType来获取到这个widget(element),从而获取到数据。

InheritedWidget通常用于子组件共享父组件中的数据,但它不具备修改更新父组件中数据的能力。它解决了访问状态和根据状态更新的问题,但是没有改变状态的能力,所以通常会把InheritedWidgetChangeNotifier结合一起使用,通过ChangeNotifier去跟踪变化的数据。

class CounterModel extends ChangeNotifier {int _counter = 0;int get counter => _counter;void increment() {_counter++;notifyListeners(); // Notify listeners when the state changes}
}class CounterProvider extends InheritedWidget {final CounterModel counterModel;final Widget child;CounterProvider({required this.counterModel, required this.child}): super(child: child);static CounterProvider? of(BuildContext context) {return context.dependOnInheritedWidgetOfExactType<CounterProvider>();}@overridebool updateShouldNotify(CounterProvider oldWidget) {return counterModel != oldWidget.counterModel;}
}class MyApp extends StatelessWidget {final count = CounterModel();@overrideWidget build(BuildContext context) {return MaterialApp(home: Scaffold(appBar: AppBar(title: Text('ChangeNotifier + InheritedWidget')),body: CounterProvider(counterModel: count,child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [CounterDisplay(),CounterControl(),],)),),);}
}class CounterDisplay extends StatefulWidget {@override_CounterDisplayState createState() => _CounterDisplayState();
}class _CounterDisplayState extends State<CounterDisplay> {late CounterModel counterModel;late VoidCallback listener;@overridevoid didChangeDependencies() {super.didChangeDependencies();counterModel = CounterProvider.of(context)!.counterModel;listener = () {if (mounted) setState(() {});};counterModel.addListener(listener);}@overridevoid dispose() {counterModel.removeListener(listener);super.dispose();}@overrideWidget build(BuildContext context) {return Center(child: Text('Counter Value: ${counterModel.counter}'),);}
}class CounterControl extends StatelessWidget {@overrideWidget build(BuildContext context) {final counterModel = CounterProvider.of(context)!.counterModel;return Center(child: GestureDetector(onTap: () {counterModel.increment();},child: Text('+'),),);}
}

通过这样一个简单的例子来体验InheritedWidget结合ChangeNotifier实现状态管理的功能。不过InheritedWidget也存在缺点,InheritedWidget的通知机制是基于它的数据发生变化时触发,而不是针对特定的子小部件,无法定向通知,这可能导致不必要的刷新,尤其是在大型小部件树中。它会触发整个子小部件树的重建,即使只有一部分子小部件受到了影响,这也有可能导致性能问题,尤其是在需要精细控制刷新的情况下。当然,在后续的文章中,我们也会详细分析InheritedWidget的实现机制,来帮助更好的理解整套流程。同时,考虑到这些缺点,在后续的文章也会分析更好的状态管理解决方案。

4.Notification

Notification是一种用于在小部件树中传递信息的机制,它可以用于实现子树中的特定部分之间的通信。Notification并不像状态管理或全局状态传递那样普遍,它主要用于特定场景下的通信,比如当某个事件发生时,需要在小部件树的各个部分之间传递消息。Notification的工作方式是通过Notification对象在小部件树中传递,然后从父级小部件开始逐级向上冒泡,直到找到一个处理该通知的小部件为止。每个处理通知的小部件可以根据需要执行特定的操作。你可以把InheritedWidget 理解为从上到下传递、共享的方式,而Notification则是从下往上。Notification它提供了dispatch方法,沿着context对应的Element节点向上逐层发送通知。

class MyNotification extends Notification {final String message;MyNotification(this.message);
}class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(home: Scaffold(appBar: AppBar(title: Text('Notification')),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [NotificationListener<MyNotification>(onNotification: (notification) {print(notification.message);return true;},child: ChildWidget(),),],),),),);}
}class ChildWidget extends StatelessWidget {@overrideWidget build(BuildContext context) {return ElevatedButton(onPressed: () {MyNotification('Hello 这里是子Widget!').dispatch(context);},child: Text('ChildWidget'),);}
}

当点击按钮后,就会向上传递消息,被NotificationListener所监听,在控制台打印出消息。你可以尝试InheritedWidget + Notification来控制状态之间的访问。

5.Stream

Stream是一种用于在应用程序中管理状态和数据流的重要工具。Stream是异步数据流的抽象表示,它可以在应用程序中传递和监听数据的变化。但是它和Flutter关系并不大,它是通过纯dart去实现的。你可以理解为flutter只是通过StreamBuilder去构建了一个Stream通道。它的使用其实也并没有复杂太多,通常只需要创建StreamController,然后去监听控制器(可以直接去监听StreamController,然后通过setState更新UI,也可以通过StreamBuilder),最后将更新后的数据通过Stream的sink属性添加到Stream中即可。知名的状态管理库Bloc,就是基于Stream的封装。

class Todo {final String text;bool isCompleted;Todo(this.text, this.isCompleted);
}class _TodoAppState extends State<TodoApp> {final _controller = StreamController<List<Todo>>();List<Todo> _todos = [];@overridevoid initState() {super.initState();// 初始化Stream,将空列表添加到Stream中_controller.sink.add(_todos);}@overridevoid dispose() {_controller.close(); // 关闭StreamController以释放资源super.dispose();}void _addTodo(String text) {// 添加新的Todo项并将更新后的列表添加到Stream中final newTodo = Todo(text, false);_todos.add(newTodo);_controller.sink.add(_todos);}void _toggleTodoCompletion(int index) {// 切换指定Todo项的完成状态并将更新后的列表添加到Stream中_todos[index].isCompleted = !_todos[index].isCompleted;_controller.sink.add(_todos);}void _deleteTodo(int index) {// 删除指定Todo项并将更新后的列表添加到Stream中_todos.removeAt(index);_controller.sink.add(_todos);}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('备忘录 —— Stream'),),body: Column(children: [Padding(padding: EdgeInsets.all(16.0),child: TextField(onSubmitted: (text) {if (text.isNotEmpty) {_addTodo(text);}},decoration: InputDecoration(labelText: '添加任务',border: OutlineInputBorder(),),),),Expanded(child: StreamBuilder<List<Todo>>(stream: _controller.stream,builder: (context, snapshot) {if (snapshot.hasData) {return ListView.builder(itemCount: snapshot.data!.length,itemBuilder: (context, index) {final todo = snapshot.data![index];return ListTile(title: Text(todo.text),trailing: Checkbox(value: todo.isCompleted,onChanged: (_) => _toggleTodoCompletion(index),),onLongPress: () => _deleteTodo(index),);},);} else {return Center(child: CircularProgressIndicator());}},),),],),);}
}

Stream的缺点也同样是它的优点:

  • 缺点:需要对其定制化,才能满足更复杂的场景。
  • 优点:可以针对业务进行定制化,足够灵活,可以基于它做一个好的设计。

总结

看完这篇文章后,相信您对Flutter中的状态应该有了一个更深刻的记忆。对Flutter自带的状态管理的方式也有了一定的了解,那么在后续的文章中,我们将进一步深入学习ChangeNotifier+InheritedWidget的一些机制,以及主流的状态管理框架。在文章的最后,我想引用一下flutter.cn的一句话:状态管理是一个相当复杂的话题。如果您在浏览后发现一些问题并未得到解答,或者并不适用于您的具体需求场景,自信些,您的实现就是对的。

参考

状态 (State) 管理参考 —— flutter.cn

Flutter 工程化框架选择 — 状态管理何去何从 —— 恋猫de小郭

Flutter 对状态管理的认知与思考 —— 小呆呆666

关于我

Hello,我是Taxze,如果您觉得文章对您有价值,希望您能给我的文章点个❤️,有问题需要联系我的话:我在这里 。如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章~万一哪天我进步了呢?😝

相关文章:

Flutter状态管理 — 探索Flutter中的状态

前言 随着响应式编程的理念&Flutter被大众所了解以来&#xff0c;状态管理一直是一个引人深思的话题。如果想要学习好Flutter这样的响应式的编程框架就一定是离不开状态管理的。我遇到过很多没有了解过响应式编程框架的&#xff0c;或者从事后端开发&#xff0c;自己想用F…...

Python中重要的条件语句教程

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 一. 了解条件语句 假设一个场景&#xff1a; 同学们这个年龄去过网吧吗&#xff1f; 去网吧进门想要上网必须做的一件事是做什么&#xff1f;&#xff08;考虑重点&#xff09; 为什么要把身份证给工作人员&#xf…...

记录一下自己对linux分区挂载的理解

一直狠模糊&#xff0c;分两个区&#xff0c;一个挂载/, 一个挂载/home 两者是什么关系 实测 先看挂载的内容 然后umount /home后创建一个新文件 再挂载回去 发现旧分区又回来了&#xff0c;说明路径只是个抽象的概念&#xff0c;分区挂载&#xff0c;互相之间数据是不影响…...

【机器学习】人工智能概述(文末送书)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…...

电子学会 2023年3月 青少年软件编程Python编程等级考试三级真题解析(选择题+判断题+编程题)

青少年编程Python编程等级考试三级真题解析(选择题+判断题+编程题) 2023年3月 一、选择题(共25题,共50分) 十进制数111转换成二进制数是?( ) A. 111 B. 1111011 C. 101111 D. 1101111 答案选:D 考点分析:考察python 进制转换 十进制转二进制,采用除二倒取余数,直到商…...

C++算法 —— 动态规划(1)斐波那契数列模型

文章目录 1、动规思路简介2、第N个泰波那契数列3、三步问题4、使用最小花费爬楼梯5、解码方法6、动规分析总结 1、动规思路简介 动规的思路有五个步骤&#xff0c;且最好画图来理解细节&#xff0c;不要怕麻烦。当你开始画图&#xff0c;仔细阅读题时&#xff0c;学习中的沉浸…...

Elasticsearch 对比传统数据库:深入挖掘 Elasticsearch 的优势

当你为项目选择数据库或搜索引擎时&#xff0c;了解每个选项的细微差别至关重要。 今天&#xff0c;我们将深入探讨 Elasticsearch 的优势&#xff0c;并探讨它与传统 SQL 和 NoSQL 数据库的比较。 1. Elasticsearch简介 Elasticsearch 以强大的 Apache Lucene 库为基础&#…...

ICG-Tetrazine的合成方法和步骤-星戈瑞

ICG-Tetrazine的合成方法可以通过多步反应完成&#xff0c;以下是一种常见的合成方法和步骤的示例&#xff1a; 吡咯环合成&#xff1a;首先合成吡咯环作为ICG-Tetrazine的核心结构。可以使用合适的反应条件将相应的吡咯前体化合物&#xff08;例如吡咯-2-甲酸&#xff09;与活…...

C ++ 学习之分文件 实现类

前言 当您在 C 中编写较大的程序时&#xff0c;将所有代码都放在一个文件中可能会变得混乱和不可维护。为了更好地组织代码并提高可维护性&#xff0c;您可以使用分文件实现&#xff08;Separate Compilation&#xff09;的概念。 正文 我的 circle.h 文件 #pragma once #i…...

vue+elementui前端rules校验缓存问题

场景&#xff1a; 最近公司要求项目前端不要用element-ui&#xff0c;改为使用公司其他组开发的ui组件。 这个ui组件使用基本就是安装后&#xff0c;直接全局替换elementui的el-前缀为公司开发的xx-前缀。 替换之后&#xff0c;发现替换倒是很丝滑&#xff0c;问题不大。可以运…...

使用Vue3和Vite升级你的Vue2+Webpack项目

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…...

WebSocket 协议及其使用案例

文章目录 前言一、初识 WebSocket 协议1.1 什么是 WebSocket 协议1.2 WebSocket 与 HTTP 的关系1.3 WebSocket 握手的过程1.4 WebSocket 解决了什么问题 二、WebSocket 数据帧格式2.1 WebSocket 数据帧格式图示2.2 各字段的详细说明 三、SpringBoot 项目中引入 WebSocket3.1 创…...

Java应用CPU占用过高故障排除

一、背景 最近测试反馈测试环境接口偶现有访问超时&#xff0c;然后APP提示是网络失败&#xff0c;看了一下测试环境的应用完全没啥问题&#xff0c;一直以为是网络问题。 今天测试有反馈了&#xff0c;赶紧看了一下测试服务器&#xff0c;这次终于有症状了&#xff0c;CPU直…...

嵌入式Linux开发实操(十五):nand flash接口开发(2)

通用NAND驱动程序支持几乎所有基于NAND的芯片,并将它们连接到Linux内核的内存技术设备(MTD)子系统。这个接口走的是nand的并口,可以在shell的/dev中看到设备,比如/mtd0、/mtd0ro…,mtdblock0、mtdblock1… sysfs在设备层次结构中提供了几个视角。设备必须挂在某条总线bus…...

作为一家游戏开发公司,有哪些经验可以分享?

在竞争激烈的游戏开发行业中&#xff0c;成功的游戏开发公司需要不断学习、创新和积累经验。作为一家经验丰富的游戏开发公司&#xff0c;我们愿意分享一些我们认为对于取得成功至关重要的经验和教训。这些经验涵盖了游戏开发的各个方面&#xff0c;从创意构思到发布和营销。希…...

【100天精通Python】Day51:Python 数据分析_数据分析入门基础与Anaconda 环境搭建

目录 1 科学计算和数据分析概述 2. 数据收集和准备 2.1 数据收集 2.1.1 文件导入&#xff1a; 2.1.2 数据库连接&#xff1a; 2.1.3 API请求&#xff1a; 2.1.4 网络爬虫&#xff1a; 2.2 数据清洗 2.2.1 处理缺失值&#xff1a; 2.2.2 去除重复值&#xff1a; 2.2…...

网络安全(黑客)自学路线

很多人上来就说想学习黑客&#xff0c;但是连方向都没搞清楚就开始学习&#xff0c;最终也只是会无疾而终&#xff01;黑客是一个大的概念&#xff0c;里面包含了许多方向&#xff0c;不同的方向需要学习的内容也不一样。 算上从学校开始学习&#xff0c;已经在网安这条路上走…...

HTML5

写在前面 一、简单认识HTML 1.1 什么是网页【2023/08/31】 网站是指因特网上根据一定的规则&#xff0c;使用HTML等制作的用于展示特定内容相关的网页集合。 网页是网站中的一“页”&#xff0c;通常是HTML格式的文件&#xff0c;它要通过浏览器来阅读。 网页是构成网站的…...

Vue+Element-ui实现表格本地导入

表格文件存储在前端 如图&#xff0c;表格文件template.xlsx存储在public下的static文件夹下 注意这里的路径容易报错 a链接下载文件失败的问题(未发现文件&#xff09; a.href ‘./static/template.xlsx’ 写的时候不能带public&#xff0c;直接这么写就可以 DownloadTemp…...

Golang参数输入

Golang参数输入 1.命令行参数&#xff08;os.Args&#xff09; package mainimport ("fmt""os""strconv" )func main() {for i, num : range os.Args[1:] {fmt.Println("参数"strconv.Itoa(i)": ", num)} } //输入&#x…...

2023年8月第4周大模型荟萃

2023年8月第4周大模型荟萃 2023.8.31版权声明&#xff1a;本文为博主chszs的原创文章&#xff0c;未经博主允许不得转载。 1、美国法官最新裁定&#xff1a;纯AI生成的艺术作品不受版权保护 美国华盛顿一家法院近日裁定&#xff0c;根据美国政府的法律&#xff0c;在没有任何…...

Kafka监控工具,LinkedIn详解

Kafka监控工具包括以下几种&#xff1a; Kafka Manager&#xff1a;这是一个开源的Kafka集群管理工具&#xff0c;可以监控Kafka集群的健康和性能&#xff0c;并提供可视化的用户界面。 Kafka Monitor&#xff1a;这是LinkedIn开发的一个监控工具&#xff0c;可以监控Kafka集群…...

如何处理 Flink 作业频繁重启问题?

分析&回答 Flink 实现了多种重启策略 固定延迟重启策略&#xff08;Fixed Delay Restart Strategy&#xff09;故障率重启策略&#xff08;Failure Rate Restart Strategy&#xff09;没有重启策略&#xff08;No Restart Strategy&#xff09;Fallback重启策略&#xff…...

Windows 安装 RabbitMq

Windows 上安装 RabbitMQ 的步骤 RabbitMQ 是一个强大的开源消息队列系统&#xff0c;广泛用于构建分布式、可扩展的应用程序。本教程将带您一步一步完成在 Windows 系统上安装 RabbitMQ 的过程。无需担心&#xff0c;即使您是初学者&#xff0c;也能够轻松跟随这些简单的步骤…...

Mybatis的关系关联配置

前言 MyBatis是一个流行的Java持久化框架&#xff0c;它提供了一种简单而强大的方式来映射Java对象和关系数据库之间的数据。在MyBatis中&#xff0c;关系关联配置是一种用于定义对象之间关系的方式&#xff0c;它允许我们在查询数据库时同时获取相关联的对象。 在MyBatis中&…...

【知识积累】准确率,精确率,召回率,F1值

二分类的混淆矩阵&#xff08;预测图片是否是汉堡&#xff09; 分类器到底分对了多少&#xff1f; 预测的图片中正确的有多少&#xff1f; 有多少张应该预测为是的图片没有找到&#xff1f; 精确率和召回率在某种情况下会呈现此消彼长的状况。举个极端的例子&#xf…...

什么是分布式系统?

分布式系统是由多个独立的计算机或计算节点组成的系统&#xff0c;这些节点通过消息传递或共享数据的方式进行协调和通信&#xff0c;以实现共同的目标。分布式系统的设计目标是提高系统的可靠性、可扩展性、性能和容错性。 在一个分布式系统中&#xff0c;各个计算机节点之间…...

[AGC043D] Merge Triplets

题目传送门 引 很有意思的计数题 解法 考虑经过操作后得到的排列的性质 性质1&#xff1a; 设 p r e ( i ) pre(i) pre(i):前i个位置的最大值&#xff0c;则不会出现超过3个的连续位置的 p r e pre pre相同 必要性&#xff1a; 考虑反证&#xff0c;若有超过 3 3 3个的连续…...

2023年人工智能开源项目前20名

推荐&#xff1a;使用 NSDT场景编辑器快速搭建3D应用场景 1. Tensorflow 2. Hugging Face Transformers 3. Opencv 4. Pytorch 5. Keras 6. Stable Diffusion 7. Deepfacelab 8. Detectron2 9. Apache Mxnet 10. Fastai 11. Open Assistant 12. Mindsdb 13. Dall E…...

ThinkPHP 集成 jwt 技术 token 验证

ThinkPHP 集成 jwt 技术 token 验证 一、思路流程二、安装 firebase/php-jwt三、封装token类四、创建中间件&#xff0c;检验Token校验时效性五、配置路由中间件六、写几个测试方法&#xff0c;通过postman去验证 一、思路流程 客户端使用用户名和密码请求登录服务端收到请求&…...

网站权限分配代码/关键词怎么找出来

InitializingBean接口只包括afterPropertiesSet方法&#xff0c;凡是继承该接口的类&#xff0c;在初始化bean的时候会执行该方法 import org.springframework.beans.factory.InitializingBean; public class TestBean implements InitializingBean{   Override   public v…...

幕墙配件在那个网站做推广好/推广平台的方法

汽车才是主语&#xff0c;互联网只是定语阿里巴巴移动事业群总裁兼高德集团总裁 俞永福最近两年&#xff0c;汽车领域诞生了很多时髦的词&#xff1a;智能汽车、互联网汽车、车联网&#xff0c;意味着这个产业正在经历一些深刻的变化。场面很热闹&#xff0c;但我们需要想想&am…...

有什么网站可以在线做试题/台州网络推广

NTP(network time protocol) NTP是由NTP客户端和NTP服务器构成&#xff0c;客户端的时间从NTP服务器上取得即可。一般是一个linux服务器集群中配置一台NTP服务器&#xff0c;其他的机器上都启动NTP客户端&#xff0c;然后同步时间。 经常会把同步时间的操作放到crontab中定时执…...

asp网站 工具/123网址之家

Python入门从哪开始&#xff0c;90%以上的书上都是这样讲的&#xff1a;先介绍 Python 的基本语法规则、list、dict、tuple 等数据结构&#xff0c;然后再介绍字符串处理和正则表达式&#xff0c;介绍文件等IO操作.... 就这样一点一点往下说。然而这种按部就班的学习方法&#…...

杭州做网站怎么收费多少/合肥搜索引擎推广

什么是 Penpot &#xff1f; Penpot 是第一个面向跨域团队的开源设计和原型制作平台。 Penpot 是基于 Web 的&#xff0c;不依赖于操作系统&#xff0c;使用开放式 Web 标准&#xff08;SVG&#xff09;&#xff0c;为所有人提供服务并由开源社区授权。 官网地址&#xff1a;ht…...

wordpress 中文 字体大小/咸阳网站建设公司

2019独角兽企业重金招聘Python工程师标准>>> /*** 非优雅版本 times 重试次数 */ private String test(int times) {while (times > 0) {try {dosomting();return "1";} catch (Exception e) {// 可以在这里加上一些延时 times--;return test(times);…...