当前位置: 首页 > 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…...

模型参数、模型存储精度、参数与显存

模型参数量衡量单位 M&#xff1a;百万&#xff08;Million&#xff09; B&#xff1a;十亿&#xff08;Billion&#xff09; 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的&#xff0c;但是一个参数所表示多少字节不一定&#xff0c;需要看这个参数以什么…...

《通信之道——从微积分到 5G》读书总结

第1章 绪 论 1.1 这是一本什么样的书 通信技术&#xff0c;说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号&#xff08;调制&#xff09; 把信息从信号中抽取出来&am…...

【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)

要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况&#xff0c;可以通过以下几种方式模拟或触发&#xff1a; 1. 增加CPU负载 运行大量计算密集型任务&#xff0c;例如&#xff1a; 使用多线程循环执行复杂计算&#xff08;如数学运算、加密解密等&#xff09;。运行图…...

Swagger和OpenApi的前世今生

Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章&#xff0c;二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑&#xff1a; &#x1f504; 一、起源与初创期&#xff1a;Swagger的诞生&#xff08;2010-2014&#xff09; 核心…...

.Net Framework 4/C# 关键字(非常用,持续更新...)

一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

站群服务器的应用场景都有哪些?

站群服务器主要是为了多个网站的托管和管理所设计的&#xff0c;可以通过集中管理和高效资源的分配&#xff0c;来支持多个独立的网站同时运行&#xff0c;让每一个网站都可以分配到独立的IP地址&#xff0c;避免出现IP关联的风险&#xff0c;用户还可以通过控制面板进行管理功…...

Oracle11g安装包

Oracle 11g安装包 适用于windows系统&#xff0c;64位 下载路径 oracle 11g 安装包...

嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)

目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 ​编辑​编辑 UDP的特征 socke函数 bind函数 recvfrom函数&#xff08;接收函数&#xff09; sendto函数&#xff08;发送函数&#xff09; 五、网络编程之 UDP 用…...

论文阅读:Matting by Generation

今天介绍一篇关于 matting 抠图的文章&#xff0c;抠图也算是计算机视觉里面非常经典的一个任务了。从早期的经典算法到如今的深度学习算法&#xff0c;已经有很多的工作和这个任务相关。这两年 diffusion 模型很火&#xff0c;大家又开始用 diffusion 模型做各种 CV 任务了&am…...

6.9-QT模拟计算器

源码: 头文件: widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QMouseEvent>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);…...