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

news2025/1/12 23:17:29

前言

随着响应式编程的理念&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; // 恢复原样
      });
    });
  }

  @override
  Widget 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();

  @override
  Widget 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> {
  @override
  void initState() {
    super.initState();
    widget.counterModel.addListener(_update);
  }

  @override
  void dispose() {
    widget.counterModel.removeListener(_update);
    super.dispose();
  }

  void _update() {
    setState(() {});
  }

  @override
  Widget 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);

  @override
  Widget 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>();
  }

  @override
  bool updateShouldNotify(CounterProvider oldWidget) {
    return counterModel != oldWidget.counterModel;
  }
}

class MyApp extends StatelessWidget {
  final count = CounterModel();

  @override
  Widget 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;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    counterModel = CounterProvider.of(context)!.counterModel;
    listener = () {
      if (mounted) setState(() {});
    };
    counterModel.addListener(listener);
  }

  @override
  void dispose() {
    counterModel.removeListener(listener);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('Counter Value: ${counterModel.counter}'),
    );
  }
}

class CounterControl extends StatelessWidget {
  @override
  Widget 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 {
  @override
  Widget 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 {
  @override
  Widget 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 = [];

  @override
  void initState() {
    super.initState();
    // 初始化Stream,将空列表添加到Stream中
    _controller.sink.add(_todos);
  }

  @override
  void 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);
  }

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

jmeter While控制器

一种常见的循环控制语句&#xff0c;用于重复执行一段代码块&#xff0c;直到指定的条件不再满足。 参数&#xff1a; 空LASTJMeter变量、函数、属性或任意其他可用表达式 &#xff08;jmeter提供的方法&#xff09;。判断变量值count_num小于等于20&#xff0c;推荐简单的几…

Python入门教程 | Python3 元组(tuple)

创建元组 Python 的元组与列表类似&#xff0c;不同之处在于元组的元素不能修改。 元组使用小括号 ( )&#xff0c;列表使用方括号 [ ]。 元组创建很简单&#xff0c;只需要在括号中添加元素&#xff0c;并使用逗号隔开即可。 >>> tup1 (Google, Tarzan, 1997, …

组相联cache如何快速实现cache line eviction并使用PMU events验证

如何快速实现cache line eviction 一&#xff0c;什么是cache hit、miss、linefill、evict &#xff1f;1.1 如果要程序员分别制造出cache hit、miss、linefill、evict这四种场景&#xff0c;该怎么做&#xff1f; 二&#xff0c;实现cache line eviction的方法1.1 直接填充法3…

STM32WB55开发(1)----监测STM32WB连接状态

STM32WB55开发----1.监测STM32WB连接状态 概述硬件准备视频教学样品申请选择芯片型号配置时钟源配置时钟树RTC时钟配置查看开启STM32_WPAN条件配置HSEM配置IPCC配置RTC启动RF开启蓝牙LED配置设置工程信息工程文件设置参考文档SVCCTL_App_Notification结果演示 概述 STM32WB系列…

useRef 定义的 ref 在控制台可以打印但是页面不生效?

useRef 是一个 React Hook&#xff0c;它能让你引用一个不需要渲染的值。 点击计时器 点击按钮后在控制台可以打印但是页面不生效。 useRef 返回的值在函数组件中不会自动触发重新渲染&#xff0c;所以控制台可以显示变化而按钮上无法显示 ref.current的变化。 import { use…

ConcurrentHashMap集合

什么是ConcurrentHashMap&#xff1f; ConcurrentHashMap 和HashMap一样&#xff0c;是一个存放键值对的容器。使用Hash算法来获取值的地址&#xff0c;因此时间复杂度是O(1)。查询非常快。ConcurrentHashMap 同时也是线程安全版的HashMap&#xff0c;可以实现线程安全的集合的…

文献阅读:Chain-of-Thought Prompting Elicits Reasoning in Large Language Models

文献阅读&#xff1a;Chain-of-Thought Prompting Elicits Reasoning in Large Language Models 1. 文章简介2. 具体方法3. 实验结果 1. 数学推理 1. 实验设计2. 实验结果3. 消解实验4. 鲁棒性考察 2. 常识推理 1. 实验设计2. 实验结果 3. 符号推理 1. 实验设计2. 实验结果 4.…

融合MMEdu和Transformers技术的视障出行智能辅助系统(上海浦育AI未来夏令营结题论文)

融合MMEdu和Transformers技术的视障出行智能辅助系统 摘要 面对社会生活中众多视障者对出行的需求&#xff0c;视障出行智能辅助系统融合MMEdu和Transformers技术为视障者提供实时路况分析。本系统利用图像分类、目标检测和深度估计等软件技术&#xff0c;对摄像头实时获取的每…

MATLAB中编译器中的变量联系到Simulink

MATLAB中编译器中的变量联系到Simulink 现在编译器中创建变量&#xff0c;进行编译&#xff0c;使其生成在工作区。 然后在Simulink中国使用变量即可。

操作视频的开始与暂停

调用 ref.current.play() 方法来播放视频&#xff1b; 如果视频需要暂停&#xff0c;我们调用 ref.current.pause() 方法来暂停视频。 通过 useRef 创建的 ref 操作视频的开始与暂停 当用户点击按钮时&#xff0c;根据当前视频的状态&#xff0c;我们会开始或暂停视频&…

如何利用开源工具搭建AI大模型底座

开源社区是技术发展的一个重要部分&#xff0c;对于AI大模型来说&#xff0c;也是如此。 我们在这篇文章中来尝试通过开源工具来构建AI大模型的底座&#xff0c;涉及到的技术包括&#xff1a; LangchainOpenAIFlowiseLocalAILlama 使用Langchain构建第一个对话应用 如果你使…

时序预测 | MATLAB实现TCN-LSTM时间卷积长短期记忆神经网络时间序列预测

时序预测 | MATLAB实现TCN-LSTM时间卷积长短期记忆神经网络时间序列预测 目录 时序预测 | MATLAB实现TCN-LSTM时间卷积长短期记忆神经网络时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 1.MATLAB实现TCN-LSTM时间卷积长短期记忆神经网络时间序列预测…

17.Oauth2-微服务认证

1.Oauth2 OAuth 2.0授权框架支持第三方支持访问有限的HTTP服务&#xff0c;通过在资源所有者和HTTP服务之间进行一个批准交互来代表资源者去访问这些资源&#xff0c;或者通过允许第三方应用程序以自己的名义获取访问权限。 为了方便理解&#xff0c;可以想象OAuth2.0就是在用…

reference based image enhancement 论文调研

Enhance Images as You Like with Unpaired Learning 这是IJCAI 2021的文章文章提出一个条件GAN模型&#xff0c;用reference image作为条件&#xff0c;可以在unpaired images上训练暗图增强模型&#xff0c;使得增强结果根据reference image来调节色调亮度和对比度。训练的监…

Redis事务为什么不支持回滚

Redis事务中过程中的错误分类两类&#xff1a; 在exec执行之前的错误&#xff0c;这种错误通常是指令错误&#xff0c;比如指令语法错误、内存不足等... --> 在开始事务后&#xff0c;传输指令时&#xff0c;遇到这种错误&#xff0c;Redis会给出Error错误提示&#xff0c;…

【多线程案例】定时器应用及实现

文章目录 1. 定时器是什么&#xff1f;2. 定时器的应用3. 自己实现定时器 1. 定时器是什么&#xff1f; 定时器就类似生活中的闹钟&#xff0c;它是软件开发中的一个重要组件。当有些线程我们并不希望它立刻执行&#xff0c;这个时候我们就可以使用定时器&#xff0c;规定线程在…

苹果iPhone15系列不再使用皮革保护壳?“FineWoven“官方认证替代

根据9月3日的报道&#xff0c;苹果即将推出的iPhone 15系列将不再使用皮革保护壳&#xff0c;取而代之的将是一种名为"FineWoven"的新材料编织工艺保护壳。 这种保护壳将有十种颜色可供选择&#xff0c;包括黑色、桑葚色、灰褐色、常绿色、太平洋蓝色、紫藤色、古白色…

Elasticsearch安装,Springboot整合Elasticsearch详细教程

Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎&#xff0c;能够实现近乎实时的搜索。 Elasticsearch官网https://www.elastic.co/cn/ 目录 第一步&#xff1a;下载Elasticsearch 下载7.6.2版本 下载其他版本 第二步&#xff1a;安装Elasticsearch 第三…

【Spring+SpringMVC+Mybatis】SSM框架的整合、思想、工作原理和优缺点的略微讲解

&#x1f680;欢迎来到本文&#x1f680; &#x1f349;个人简介&#xff1a;陈童学哦&#xff0c;目前学习C/C、算法、Python、Java等方向&#xff0c;一个正在慢慢前行的普通人。 &#x1f3c0;系列专栏&#xff1a;陈童学的日记 &#x1f4a1;其他专栏&#xff1a;CSTL&…

Redis布隆过滤器原理

其实布隆过滤器本质上要解决的问题&#xff0c;就是防止很多没有意义的、恶意的请求穿透Redis&#xff08;因为Redis中没有数据&#xff09;直接打入到DB。它是Redis中的一个modules&#xff0c;其实可以理解为一个插件&#xff0c;用来拓展实现额外的功能。 可以简单理解布隆…