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

再聊 Flutter Riverpod ,注解模式下的 Riverpod 有什么特别之处,还有发展方向

三年前我们通过 《Flutter Riverpod 全面深入解析》 深入理解了 riverpod 的内部实现,而时隔三年之后,如今Riverpod 的主流模式已经是注解,那今天就让我们来聊聊 riverpod 的注解有什么特殊之处。

前言

在此之前,我们需要先回忆一下,riverpod 最明显的特点是将 BuildContext 转换成 WidgetRef 抽象 ,从而让状态管理不直接依赖 BuildContext ,所以对应的 Provider 可以按需写成全局对象,而在 riverpod 里,主要的核心对象有:

  • ProviderScopeInheritedWidget 实现,共享实例的顶层存在,提供一个 ProviderContainer 全局共享
  • ProviderContainer:用于管理和保存各种 “Provider” 的 State ,并且支持 override 一些特殊 “Provider” 的行为,还有常见的 read\watch\refesh
  • Ref :提供 riverpod 内的 “Provider” 交互接口,是 riverpod 内 ProviderElementBase 的抽象
  • ProviderElementBase : Ref 的实现,每个 “Provider” 都会有自己的 “Element” ,而构建 “Provider” 时是传入的 Create 函数会在 “Element” 内通过 “setState” 调用执行,比如 StateProvider((ref)=> 0) 这里的 ref ,就是内部在 ”Element“ 里通过 setState(_provider.create(this)); " 的时候传入的 this
  • WidgetRef :替代 Flutter BuildContext 的抽象,内部通过继承 StatefulWidget 实现,作为 BuildContext 的对外替代

所以 “Provider” 里的 Ref 和 “Consumer” 的 WidgetRef 严格来说是两个不同的东西,只是它们内部都可以获取到 ProviderContainer ,从而支持对应 read\watch\refesh 等功能,这也是为什么你在外部直接通过 ProviderContainer 也可以全局直接访问到 read\watch\refesh 的原因。

另外,riverpod 内部定义了自己的 「Element」 和 「setState」实现,它们并不是 Flutter 里的 Element 和 setState,所以上面都加了 “”,甚至 riverpod 里的 “Provider” 和 Provider 状态管理库也没有关系, 这么设计是为了贴合 Flutter 本身的 「Element」 和 「setState」概念,所以这也是为什么说 riverpod 是专为 Flutter 而存在的设计。

注解模式

现在 riverpod 更多提倡使用注解模式,注解模式可以让 riverpod 使用起来更方便且规范,从一定程度也降低了使用难度,但是也对初学者屏蔽了不少过去的手写实现,导致在出现问题时新手也可能会相对更蒙。

简单函数注解

首先我们看这个简单的代码,我们在 main.dart 里添加了了一个 @riverpodhelloWorld ,然后运行 flutter pub run build_runner build --delete-conflicting-outputs ,可以看到此时生成了对应的 main.g.dart 文件:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';part 'main.g.dart';
String helloWorld(Ref ref) {return 'Hello world';
}
class MyApp extends ConsumerWidget {Widget build(BuildContext context, WidgetRef ref) {final String value = ref.watch(helloWorldProvider);return MaterialApp(home: Scaffold(appBar: AppBar(title: const Text('Example')),body: Center(child: Text(value),),),);}
}

我们看 main.g.dart 文件,可以看到,根据 @riverpod 的规则, helloWorld 会生成一个 helloWorldProvider 实例让我们在使用时 read/watch/refresh :

// GENERATED CODE - DO NOT MODIFY BY HANDpart of 'main.dart';// **************************************************************************
// RiverpodGenerator
// **************************************************************************String _$helloWorldHash() => r'9abaa5ab530c55186861f2debdaa218aceacb7eb';/// See also [helloWorld].
(helloWorld)
final helloWorldProvider = AutoDisposeProvider<String>.internal(helloWorld,name: r'helloWorldProvider',debugGetCreateSourceHash:const bool.fromEnvironment('dart.vm.product') ? null : _$helloWorldHash,dependencies: null,allTransitiveDependencies: null,
);('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef HelloWorldRef = AutoDisposeProviderRef<String>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

通过生成的代码,我们可以看到:

  • _$helloWorldHash() :它主要是用于提供一个唯一标识,用于追踪 Provider 的来源和状态,它是被 debugGetCreateSourceHash 所使用,例如在 Debug 模式下 hotload 时,riverpod 会用这个值来判断当前 provider 是否需要重建,比如当你重新生成的时候 hash 值就会出现变化。
  • helloWorldProviderAutoDisposeProvider 的实例,也就是默认情况下 @riverpod 生成的都是自动销毁的 Provider ,

这里默认使用 AutoDisposeProvider ,也是为了更好的释放内存和避免不必需要的内存泄漏等场景, AutoDisposeProvider 内部,在每次 readinvalidate 、页面退出、ProviderContainer 销毁等场景会自动调用 dispose 。

异步函数注解

接着,如果给 helloWorld 增加 async ,那么我们得到一个 AutoDisposeFutureProvider ,同理,如果是 async* 就会生成一个 AutoDisposeStreamProvider


Future<String> helloWorld(Ref ref) async{return 'Hello world';
}------------------------------GENERATED CODE---------------------------------(helloWorld)
final helloWorldProvider = AutoDisposeFutureProvider<Object?>.internal(helloWorld,name: r'helloWorldProvider',debugGetCreateSourceHash:const bool.fromEnvironment('dart.vm.product') ? null : _$helloWorldHash,dependencies: null,allTransitiveDependencies: null,
);

当然,在返回结果使用上会有些差别, 异步的 Provider 会返回一个 AsyncValue ,或者需要 .value 获取一个非空安全的对象:


Widget build(BuildContext context, WidgetRef ref) {final AsyncValue<String> asyncValue = ref.watch(helloWorldProvider);final String? value = ref.watch(helloWorldProvider).value;return MaterialApp(home: Scaffold(appBar: AppBar(title: const Text('Example')),body: Center(child: Text(asyncValue.when(data: (v) => v,error: (_, __) => "error",loading: () => "loading")),),),);
}

函数注解带参数

当你需要给 helloWorld 增加参数的时候,此时的 helloWorldProvider 就不再是一个 AutoDisposeFutureProvider 实例,它将变成 HelloWorldFamily ,它是一个 Family 的实现:


Future<String> helloWorld(Ref ref, String value, String type) async {return 'Hello world $value $type';
}
Widget build(BuildContext context, WidgetRef ref) {final AsyncValue<String> asyncValue = ref.watch(helloWorldProvider("1", "2"));final String? value = ref.watch(helloWorldProvider("1", "2")).value;
}------------------------------GENERATED CODE---------------------------------/// See also [helloWorld].
class HelloWorldFamily extends Family<AsyncValue<String>> {/// See also [helloWorld].const HelloWorldFamily();/// See also [helloWorld].HelloWorldProvider call(String value,String type,) {return HelloWorldProvider(value,type,);}HelloWorldProvider getProviderOverride(covariant HelloWorldProvider provider,) {return call(provider.value,provider.type,);}static const Iterable<ProviderOrFamily>? _dependencies = null;Iterable<ProviderOrFamily>? get dependencies => _dependencies;static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;Iterable<ProviderOrFamily>? get allTransitiveDependencies =>_allTransitiveDependencies;String? get name => r'helloWorldProvider';
}

在 Dart 中,call 方法是一个特殊的方法,它可以让一个类的实例像函数一样调用。

说到 Family , 它的作用是主要就是支持使用额外的参数构建 Provider ,因为前面 helloWorld 需要传递参数,所以 HelloWorldFamily 的主要作用,就是提供创建和覆盖需要参数的 Provider,例如前面的:

  final AsyncValue<String> asyncValue = ref.watch(helloWorldProvider("1", "2"));final String? value = ref.watch(helloWorldProvider("1", "2")).value;

当然,这里你需要注意,不同与前面的 helloWorldProvider 实例,需要参数的 Provider 需要你每次使用时通过参数构建,而此时你每次调用如 helloWorldProvider("1", "2") 都是创建了一个全新实例,如果你需要同一个数据源下 read/watch ,那么你应该在调用时共用一个全局 helloWorldProvider("1", "2") 实例。

如果是不同 Provider 实例,那么你获取到的参数其实是不一样的,因为内部 map 登记的映射关系就是基于 Provider 实例为 key :

不过对比之下,过去你使用 FutureProvider.family 只能覆带一个 Arg 参数,虽然可以通过语法糖传递多个参数,但是终究还是比注解生成的麻烦:

final helloWorldFamily =FutureProvider.family<String, (String, String)>((value, type) async {return 'Hello world $value $type';
});

另外,注解生成时,还会动态生成一个对应的 “Element” ,让 Element 支持获取 Provider 的参数,并实现对应 build 方法,也就是通过 ref 可以获取到相关参数:

mixin HelloWorldRef on AutoDisposeFutureProviderRef<String> {/// The parameter `value` of this provider.String get value;/// The parameter `type` of this provider.String get type;
}class _HelloWorldProviderElementextends AutoDisposeFutureProviderElement<String> with HelloWorldRef {_HelloWorldProviderElement(super.provider);String get value => (origin as HelloWorldProvider).value;String get type => (origin as HelloWorldProvider).type;
}

最后,带参数之后,生成的 _SystemHash 也会根据参数动态变化,从而支持 hotload 等场景:

class _SystemHash {_SystemHash._();static int combine(int hash, int value) {// ignore: parameter_assignmentshash = 0x1fffffff & (hash + value);// ignore: parameter_assignmentshash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));return hash ^ (hash >> 6);}static int finish(int hash) {// ignore: parameter_assignmentshash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));// ignore: parameter_assignmentshash = hash ^ (hash >> 11);return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));}
}

类注解

接着,我们看 @riverpod 除了可以注解函数之后,还可以直接注解 class ,只是 class 需要继承 _$*** 一个子类:


class HelloWorld extends _$HelloWorld {String build() {return 'Hello world';}changeValue(String value) {state = value;}
}
Widget build(BuildContext context, WidgetRef ref) {final String asyncValue = ref.watch(helloWorldProvider);ref.read(helloWorldProvider.notifier).changeValue("next");
}

通过生成代码可以看到,此时生成的是 AutoDisposeNotifierProvider ,也就是在读取时,可以通过 read(****Provider.notifier) 去改变状态:

String _$helloWorldHash() => r'52966cfeefb6334e736061e19443e4c8b94160d8';/// See also [HelloWorld].
(HelloWorld)
final helloWorldProvider =AutoDisposeNotifierProvider<HelloWorld, String>.internal(HelloWorld.new,name: r'helloWorldProvider',debugGetCreateSourceHash:const bool.fromEnvironment('dart.vm.product') ? null : _$helloWorldHash,dependencies: null,allTransitiveDependencies: null,
);typedef _$HelloWorld = AutoDisposeNotifier<String>;

也就是,通过 @riverpod 注解的 class ,是带有 state 状态的 NotifierProvider ,这是对比注解函数最明显的差异

而如果注解 class 需要携带参数,那么可以在 build 上添加需要的参数,最终同样和函数一样会生成一个对应的 HelloWorldFamily


class HelloWorld extends _$HelloWorld {String build(String value, String type) {return 'Hello world';}changeValue(String value) {state = value;}
}

同理,如果你给 build 增加了 async,那么就会生成一个 AutoDisposeAsyncNotifierProviderImpl 的相关实现:


class HelloWorld extends _$HelloWorld {Future<String> build(String value, String type) async {return 'Hello world';}changeValue(String value) {final currentValue = state.valueOrNull ?? "";state = AsyncData(currentValue + value);}removeString(String value) {final currentValue = state.valueOrNull ?? "";state = state.copyWithPrevious(AsyncData(currentValue.replaceAll(value, "")));}
}

可以看到,在注解 class 下可操作空间是在 build ,并且需要注意的是,当你调用 refresh 的时候,State 是会被清空,并且重新调用 build

KeepAlive

那么我们前面说的都是 AutoDispose ,如果我不想他被释放呢?那就是需要用到大写字母开头的 @Riverpod ,给参数配置上 keepAlive: true

(keepAlive: true)
class HelloWorld extends _$HelloWorld 

然后再看输出文件,你就会看到此时 HelloWorldProvider 继承的是 AsyncNotifierProviderImpl 而不是 AutoDispose 了:

class HelloWorldProvider extends AsyncNotifierProviderImpl<HelloWorld, String> {/// See also [HelloWorld].HelloWorldProvider(String value,String type,) : this._internal(

dependencies

另外 @Riverpod 还有另外一个可配置参数 dependencies ,从名字上理解起来是依赖的意思,但是其实它更多用于「作用域」相关的处理。

在 riverpod 里,框架的设计是支持多个 ProviderContainer 的场景,并且每个容器可以覆盖(override)某些 Provider 的数据,例如我只是添加了一个 dependencies: []此时无论列表是否为空,它都可以被认为是一个具有作用域支持的 Provider,从而实现根据上下文进行数据隔离,另外不为空时还可以看作声明 Provider 在作用域内的依赖关系

(dependencies: [])

但是,不是你加了 dependencies 它就自动产生作用域隔离了,不为空时也不会自动追加依赖,它只是一个声明作用,后续还是需要代码配合。

如下代码所示,这里简单的声明了一个带有 dependenciesCounter ,然后:

  • 在页面通过 ref.watch(counterProvider) 监听了 Counter
  • 在新的 dialog 也通过 ref2.watch(counterProvider) 监听了 Counter
(dependencies: [])
class Counter extends _$Counter {int build() => 0;void update(int count) {state = count;}
}class MyApp extends StatelessWidget {Widget build(BuildContext context) {return ProviderScope(child: MaterialApp(home: Consumer(builder: (ctx, ref, __) {final count = ref.watch(counterProvider);return Scaffold(appBar: AppBar(),body: Column(mainAxisAlignment: MainAxisAlignment.start,children: [Text('Counter: $count'),ElevatedButton(onPressed: () {showDialog(context: ctx,builder: (context) => AlertDialog(title: Text('Dialog'),content: Consumer(builder: (_, ref2, __) {final count2 = ref2.watch(counterProvider);return InkWell(onTap: () {ref2.read(counterProvider.notifier).update(count2 + 1);},child: Text('Dialog Counter: $count2'),);})),);},child: Text('Open Dialog'),),],),floatingActionButton: FloatingActionButton(onPressed: () {ref.read(counterProvider.notifier).update(count + 1);}),);}),),);}
}

结果最后运行发现,Dialog 和主页的 Counter 其实还是共享的, dependencies 并没有起到作用:

之所以这样,原因在于没有增加新的 ProviderScope ,如下代码所示,只要将上面的 showDialog 部分修改为如下代码所示:

  • 新增一个 的 ProviderScope
  • 通过 overrides 指定对应 counterProvider
showDialog(context: ctx,builder: (context) => ProviderScope(overrides: [counterProvider,///你还可以 overrideWith 覆盖修改//counterProvider.overrideWith(()=>Counter())],child: AlertDialog(title: Text('Dialog'),content: Consumer(builder: (_, ref2, __) {final count2 = ref2.watch(counterProvider);return InkWell(onTap: () {ref2.read(counterProvider.notifier).update(count2 + 1);},child: Text('Dialog Counter: $count2'),);})),),
);

以上条件缺一不可以,运行后如下图所示,可以看到此时 counterProvider 在主页和 Dialog 之间被有效分割开:

其实原因从源码里也可以看出来,在 ProviderContainer 内部源码我们可以看到,要产生一个独立的作用域,你需要:

  • root 不为空,也就是有一个上级 ProviderContainer
  • 其次存在 dependencies ProviderContainer 的 override 不为空,也就是 dependencies 不为 null 就行,但是 override 必须有 Provider
  • 最后才是返回全新的 _StateReader 用于提供状态数据

所以,从这里就可以看出,dependencies 只是一个先置条件,具体它是不是局部作用域,还得是你用的时候怎么用

同理依赖也是,比如你写了一个 @Riverpod(dependencies: [maxCountProvider]) ,但是你还是需要对应写上 ref.watch(maxCountProvider) ,不然它也并不起作用:

(dependencies: [maxCountProvider])
int limitedCounter(LimitedCounterRef ref) {final max = ref.watch(maxCountProvider); // 监听 return 0.clamp(0, max); 
}

PS ,如果你只是正常监听,不需要作用域的场景,其实直接写 ref.watch 而不需要 dependencies: [maxCountProvider] 也是可以的。

如果我们从输出端看,可以看到有没有 dependencies ,主要就是 _dependencies_allTransitiveDependencies 是否为空的区别:

注意事项

最后也有一些注意事项,例如:

  • 通过注解生成的 Provider 好不要依赖非生成的 Provider,比如这里的 example 是注解,它监听了一个非注解生成的 depProvider ,这样并不规范:

    final depProvider = Provider((ref) => 0);
    void example(Ref ref) {// Generated providers should not depend on non-generated providersref.watch(depProvider);
    }
    
  • 有作用域时,如果监听了某个 Provider ,那么 dependencies 里必须写上依赖 Provider,以下写法就不合规:

    (dependencies: [])
    void example(Ref ref) {// scopedProvider is used but not present in the list of dependenciesref.watch(scopedProvider);
    }
    
  • Provider 里不应该接收 BuildContext

    // Providers should not receive a BuildContext as a parameter.
    
    int fn(Ref ref, BuildContext context) => 0;
    class MyNotifier extends _$MyNotifier {int build() => 0;// Notifiers should not have methods that receive a BuildContext as a parameter.void event(BuildContext context) {}
    }
    

其实类型的注意事项在 riverpod_lint 里都声明了,只是 Custom lint rules 不会直接展示在 dart analyze ,所以需要用户在添加完 riverpod_lint 后,执行对应的 dart run custom_lint

最后

可以看到,通过注解模式,riverpod 可以让开发者少些很多代码,在整体设计理念没有变化的情况下,模版生成的代码会更规范,并且在上层屏蔽了许多复杂度和工作量。

另外通过 dependencies 我们可以可以看到 riverpod 在存储管理上它是统一的,但是在组合上它是分散的的设计理念。

而 Flutter 状态管理一直以来也是「是非之地」,比如近期就出现说 riverpod 在基准性能测试表示不如 signals 的情况,但是作者也回应了该测试属于「春秋笔法」之流:

另外,由于Dart 宏功能推进暂停 ,而 build runner 与数据类的优化还没落地,作者也在探索没有 codegen 下如何也可以便捷使用 riverpod ,比如让 family 支持多个参数:

当然,从作者的维护体验上看,貌似作者又有停滞 codegen 的倾向,看起来左右摇摆的状态还会持续一段时间:

那么, 2025 年 riverpod 还是你状态管理的首选吗

相关文章:

再聊 Flutter Riverpod ,注解模式下的 Riverpod 有什么特别之处,还有发展方向

三年前我们通过 《Flutter Riverpod 全面深入解析》 深入理解了 riverpod 的内部实现&#xff0c;而时隔三年之后&#xff0c;如今Riverpod 的主流模式已经是注解&#xff0c;那今天就让我们来聊聊 riverpod 的注解有什么特殊之处。 前言 在此之前&#xff0c;我们需要先回忆…...

Go语言集成DeepSeek API和GoFly框架文本编辑器实现流式输出和对话(GoFly快速开发框架)

说明 本文是GoFly快速开发框架集成Go语言调用 DeepSeek API 插件&#xff0c;实现流式输出和对话功能。为了方便实现更多业务功能我们在Go服务端调用AI即DeepSeek接口&#xff0c;处理好业务后再用Gin框架实现流失流式输出到前端&#xff0c;前端使用fetch请求接收到流式的mar…...

docker不停机部署

背景 最近做大疆项目时&#xff0c;后台更新部署时&#xff0c;机场和无人机就会掉线。设备自动重连注册时间比较长&#xff0c;应用长时间不可用。所以需要灰色发布服务。docker-compose的swarm模式可解决此问题。 服务构建脚本Dockerfile # 使用官方Java基础镜像&#xff…...

ZLG嵌入式笔记 | ZLG核心板散热设计指引

在嵌入式系统设计中&#xff0c;散热是影响处理器性能与稳定性的关键问题。本文聚焦于高端嵌入式处理器的散热设计&#xff0c;探讨核心板的热设计与系统级热设计方法&#xff0c;以及导热材料和布局的建议&#xff0c;为解决高温问题提供参考。 用高端嵌入式处理器设计系统&am…...

[Java]使用java进行JDBC编程

首先要从中央仓库下载api(类似驱动程序)&#xff0c;为了链接java和mysql 下载jar包&#xff0c;需要注意的是jar包的版本要和mysql保持一致 下面是新建文件夹lib&#xff0c;把jar包放进去&#xff0c;并添加为库 sql固定的情况下运行 import com.mysql.cj.jdbc.MysqlDataSo…...

MySQL进阶-关联查询优化

采用左外连接 下面开始 EXPLAIN 分析 EXPLAIN SELECT SQL_NO_CACHE * FROM type LEFT JOIN book ON type.card book.card; 结论&#xff1a;type 有All ,代表着全表扫描&#xff0c;效率较差 添加索引优化 ALTER TABLE book ADD INDEX Y ( card); #【被驱动表】&#xff0…...

Ubuntu22.04修改root用户并安装cuda

由于本人工作原因&#xff0c;经常会遇到需要给ubuntu打显卡驱动的问题&#xff0c;虽然说不难吧&#xff0c;但是耐不住机器多&#xff0c;重复多次也就烦了&#xff0c;于是抽出了一点时间&#xff0c;并且在deepseek的帮助之下&#xff0c;写了一个自动安装驱动的脚本&#…...

fiddler+雷电模拟器(安卓9)+https配置

一、fiddler配置 1、开启https代理 2、根证书安装&#xff1a;导出证书系统安装 二、模拟器设置 1、设置网络桥接模式 【点击安装】提示安装成功后保存即可 2、开启root&#xff08;开启adb远程调试&#xff09; 3、开启磁盘写入 4、设置WLAN代理 5、证书安装&#xff1a;物…...

STM32-SPI通信协议

目录 一&#xff1a;什么是通信协议&#xff1f; 二&#xff1a;电路结构 1.硬件电路 2&#xff1a;移位 3&#xff1a;时序图 4.交换字节 三&#xff1a;W25Q64简介 硬件电路&#xff1a;​编辑 存储器地址划分 Dlash操作注意事项 状态寄存器 SPI指令集 四&am…...

【CentOS】搭建Radius服务器

目录 背景简介&#xff1a;Radius是什么&#xff1f;Radius服务器验证原理搭建Radius服务器环境信息yum在线安装配置FreeRADIUS相关文件clients.conf文件users文件重启服务 验证 参考链接 背景 在项目中需要用到Radius服务器作为数据库代理用户的外部验证服务器&#xff0c;做…...

Vue中自定义指令:ClickOutside(点击当前模块外的位置)

应用场景 假设我们有一个下拉框组件&#xff0c;当下拉框展开的时候&#xff0c;点击下拉框之外的元素可以自动关闭下拉框。 一 ClickOutside代码示例 在vue3中使用ClickOutside // 导入自定义指令 import { ClickOutside as vClickOutside } from element-plus;// 绑定指令…...

如何在rust中解析 windows 的 lnk文件(快捷方式)

一、从标题二开始看&#x1f601; 这些天在使用rust写一个pc端应用程序&#xff0c;需要解析lnk文件获取lnk的图标以及原程序地址&#xff0c;之前并没有过pc端应用程序开发的经验&#xff0c; 所以在广大的互联网上游荡了两天。额&#x1f97a; 今天找到了这个库 lnk_parse很…...

2019年蓝桥杯第十届CC++大学B组真题及代码

目录 1A&#xff1a;组队&#xff08;填空5分_手算&#xff09; 2B&#xff1a;年号字符&#xff08;填空5分_进制&#xff09; 3C&#xff1a;数列求值&#xff08;填空10分_枚举&#xff09; 4D&#xff1a;数的分解&#xff08;填空10分&#xff09; 5E&#xff1a;迷宫…...

奇安信 2025 年护网蓝队初选笔试题(附答案解析)

&#x1f525; 爆款 CSDN 题库 | 超全护网蓝队笔试真题 | 含详细答案解析 &#x1f525; 熬夜为大家整理了 奇安信 2025 年护网蓝队初选笔试题&#xff0c;&#xff08;关注我我会持续更新&#xff09;涵盖 SQL 注入、Web 安全、渗透测试、二进制安全 等核心知识点&#xff0c;…...

jdk-21_linux-x64_bin.tar.gz Linux jdk21压缩包安装保姆级(详细安装教程)

jdk-21_linux-x64_bin.tar.gz 解压版详细安装教程 一、简洁版&#xff08;需要对 Linux 操作有一定基础&#xff09;二、图文详细教程1、前置准备2、解压安装3、配置环境变量4、验证成功 官网下载地址&#xff1a; https://www.oracle.com/java/technologies/downloads/#java2…...

第6章 定时器计数器

目录 6.1 定时计数器的结构框图 6.2 定时器的控制字 6.2.1 TMOD&#xff1a;工作方式控制寄存器 6.2.2 定时/计数器控制寄存器TCON 6.3 定时/计数器的4种工作方式 6.3.1 方式0、方式1&#xff08;13位、16位定时计数方式&#xff09; 6.3.2 方式2(常数自动重装入) 6.3.3 方…...

回归预测 | Matlab实现GWO-BP-Adaboost基于灰狼算法优化BP神经网络结合Adaboost思想的回归预测

回归预测 | Matlab实现GWO-BP-Adaboost基于灰狼算法优化BP神经网络结合Adaboost思想的回归预测 目录 回归预测 | Matlab实现GWO-BP-Adaboost基于灰狼算法优化BP神经网络结合Adaboost思想的回归预测回归效果基本介绍GWO-BP-Adaboost:基于灰狼算法优化BP神经网络结合Adaboost思想…...

蓝桥杯真题0团建dfs+哈希表/邻接表

dfs邻接表储存或者哈希表的运用&#xff0c;考察我们对数据的存储 本题核心就是在求从根节点开始的两棵树相同的最长序列&#xff0c;首先确定用dfs进行深搜&#xff0c;对于节点的形式可以用邻接表&#xff0c;邻接矩阵&#xff0c;哈希表来进行存储数据。下面看代码 邻接表 …...

系统架构的评估的系统的质量属性

体系结构苹果可以针对一个体系结构&#xff0c;也可以针对一组体系结构。 体系结构评估过程中&#xff0c;评估人员所关注的是系统的质量属性&#xff0c;所有评估方法所普遍关注的质量属性有以下几个&#xff1a;性能、可靠性&#xff08;容错&#xff0c;健壮性&#xff09;…...

论文阅读:基于超图高阶表示的WSI生存预测

Generating Hypergraph-Based High-Order Representations of Whole-Slide Histopathological Images for Survival Prediction 文章目录 论文介绍快速阅读摘要1 引言2 相关工作2.1 生存分析2.2 超图学习的准备工作 3 方法3.1 patch采样和低级特征提取3.2 多超图学习3.2.1 多超…...

NLP常见任务专题介绍(1)-关系抽取(Relation Extraction, RE)任务训练模板

📌 关系抽取(Relation Extraction, RE)任务训练示例 本示例展示如何训练一个关系抽取模型,以识别两个实体之间的关系。 1️⃣ 任务描述 目标:从文本中提取两个实体之间的语义关系,例如 “人物 - 组织”、“药物 - 疾病”、“公司 - 创始人” 等。输入:句子 + 标注的实…...

27. Harmonyos Next仿uv-ui 组件NumberBox 步进器组件禁用状态

温馨提示&#xff1a;本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦&#xff01; 文章目录 1. 组件介绍2. 效果展示3. 禁用状态设置3.1 整体禁用3.2 输入框禁用3.3 长按禁用 4. 完整示例代码5. 知识点讲解5.1 禁用状态属性5.2 禁用…...

docker无法pull镜像问题解决for win10

docker无法pull镜像问题解决for win10 问题原因分析解决方法 问题 在win10系统上安装好doker-desktop后ping registry-1.docker.io不同&#xff0c;并且也无法登陆hub.docker.com, 使用docker pull xx也无法正常下载 原因分析 hub.docker.com在2024年5月之后&#xff0c;国内…...

批量将 Excel 转换 PDF/Word/CSV以及图片等其它格式

Excel 格式转换是我们工作过程当中非常常见的一个需求&#xff0c;我们通常需要将 Excel 转换为其他各种各样的格式。比如将 Excel 转换为 PDF、比如说将 Excel 转换为 Word、再比如说将 Excel文档转换为图片等等。 这些操作对我们来讲都不难&#xff0c;因为我们通过 Office 都…...

Node.js调用DeepSeek Api 实现本地智能聊天的简单应用

在人工智能快速发展的今天&#xff0c;如何快速构建一个智能对话应用成为了开发者们普遍关注的话题。本文将为大家介绍一个基于Node.js的命令行聊天应用&#xff0c;它通过调用硅基流动&#xff08;SiliconFlow&#xff09;的API接口&#xff0c;实现了与DeepSeek模型的智能对话…...

Mac同时安装jdk8和jdk17,默认选择jdk8

在Mac上同时安装JDK 8和JDK 17&#xff0c;并设置默认版本为JDK 8&#xff0c;可以按照以下步骤操作&#xff1a; 一、下载并安装JDK 8和JDK 17 下载JDK 8 访问Oracle JDK下载页面。在“Java SE Archive Downloads”部分&#xff0c;找到JDK 8的下载链接。选择适合您Mac芯片类…...

【Zinx】Day5-Part3:Zinx 的连接管理

目录 Day5-Part3&#xff1a;Zinx 的连接管理创建连接管理模块将连接管理模块集成到 Zinx 当中将 ConnManager 集成到 Server 当中在 Connection 的工厂函数中将连接添加到 ConnManagerServer 中连接数量的判断连接的删除 补充&#xff1a;连接的带缓冲发包方式补充&#xff1a…...

网络安全之RSA算法

1978年就出现了这种算法&#xff0c;它是第一个既能用于数据加密也能用于数字签名的算法。它易于理解和操作&#xff0c;也很流行。算法的名字以发明者的名字&#xff08;RonRivest&#xff0c;AdiShamir和LeonardAdleman&#xff09;命名。但RSA的安全性一直未能得到理论上的证…...

Unity Dots

文章目录 什么是DotsDOTS的优势ECS&#xff08;实体组件系统&#xff09;Job System作业系统Burst编译器最后 什么是Dots DOTS&#xff08;Data-Oriented Technology Stack&#xff09;是Unity推出的一种用于开发高性能游戏和应用的数据导向技术栈&#xff0c;包含三大核心组件…...

设计模式-结构型模式-桥接模式

概述 桥接模式 &#xff1a;Bridge Pattern&#xff1a; 是一种结构型设计模式。 旨在将抽象部分与实现部分分离&#xff0c;使它们可以独立变化。 它通过组合代替继承&#xff0c;解决类爆炸问题&#xff0c;并提高系统的灵活性和可扩展性。 组成部分 【抽象部分】&#xff08…...