再聊 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 還是你狀態管理的首選嗎

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/71914.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/71914.shtml
英文地址,請注明出處:http://en.pswp.cn/web/71914.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

前端項目Axios封裝Vue3詳細教程(附源碼)

前端項目Axios封裝Vue3詳細教程&#xff08;附源碼&#xff09; 一、引言 在前端項目開發中&#xff0c;HTTP請求是不可或缺的一部分。Axios作為一個基于Promise的HTTP客戶端&#xff0c;因其易用性和豐富的功能而廣受歡迎。在Vue3項目中&#xff0c;合理地封裝Axios不僅可以提…

手寫一個Tomcat

Tomcat 是一個廣泛使用的開源 Java Servlet 容器&#xff0c;用于運行 Java Web 應用程序。雖然 Tomcat 本身功能強大且復雜&#xff0c;但通過手寫一個簡易版的 Tomcat&#xff0c;我們可以更好地理解其核心工作原理。本文將帶你一步步實現一個簡易版的 Tomcat&#xff0c;并深…

在 UniApp 開發的網站中使圖片能夠緩存,不一直刷新

在 UniApp 開發的網站中&#xff0c;要使圖片能夠緩存&#xff0c;不一直刷新&#xff0c;可以考慮以下幾種方法&#xff1a; 1. 使用適當的 HTTP 緩存頭 確保你的服務器在響應圖片時&#xff0c;返回合適的緩存控制 HTTP 頭。以下是一些常用的 HTTP 頭來控制緩存&#xff1a…

Makefile——make工具編譯STM32工程

一、Makefile相關指令 1.1、變量 符號含義替換追加:恒等于 1.2、隱含規則 符號含義%.o任意的.o文件*.o所有的.o文件 1.3、通配符 符號含義$^所有依賴文件$所有目標文件$<所有依賴文件的第一個文件 1.4、編譯器指令常用參數功能說明 符號含義舉例-E預處理&#xff0c;…

深入理解Linux文件系統權限:從基礎到高級應用全解析

1. 什么是文件系統權限&#xff1f;它是如何工作的&#xff1f; 文件權限的本質 想象你的電腦是一個大房子&#xff0c;每個文件和目錄都是房間里的物品。文件系統權限就像是一把鑰匙&#xff0c;決定誰能進房間、能看什么、能修改什么。 權限三要素&#xff1a; 讀&#xff…

C語言:6.22練習題數組解答

#include <stdio.h> #include <string.h> // 用于 strlen() int main() {char a[100];int j 0;// 從用戶輸入讀取字符串printf("請輸入一個字符串: ");fgets(a, sizeof(a), stdin);// 遍歷字符串中的每個字符for (int i 0; i < strlen(a); i) {if (…

一、docker的安裝

一、docker桌面 二、docker的配置文件 1、docker配置文件位置/etc/docker/daemon.json 使用json格式&#xff0c;graphdata-root {"graph":"/deploy/docker","registry-mirrors": ["https://8auvmfwy.mirror.aliyuncs.com"],"…

Matlab 多項式擬合點法線(二維)

文章目錄 一、簡介二、實現代碼三、實現效果一、簡介 這個思路其實很簡單,假設我們有一組曲線點,我們可以對其擬合曲線并計算其導數來獲取每個點的法向量,當然這一思路也可以擴展至三維。具體過程如下所示: 二、實現代碼 %% *********

DeepSeek-R1 論文閱讀總結

1. QA問答&#xff08;我的筆記&#xff09; Q1: DeepSeek如何處理可讀性問題&#xff1f; 通過構建冷啟動數據&#xff08;數千條長CoT數據&#xff09;微調基礎模型&#xff0c;結合多階段訓練流程&#xff08;RL訓練、拒絕采樣生成SFT數據&#xff09;&#xff0c;并優化輸…

Manus AI:多語言手寫識別的技術革命與未來圖景

摘要&#xff1a;在全球化浪潮下&#xff0c;跨語言溝通的需求日益迫切&#xff0c;但手寫文字的多樣性卻成為技術突破的難點。Manus AI憑借其多語言手寫識別技術&#xff0c;將潦草筆跡轉化為精準數字文本&#xff0c;覆蓋全球超百種語言。本文從技術原理、應用場景、行業價值…

Flutter——最詳細原生交互(MethodChannel、EventChannel、BasicMessageChannel)使用教程

MethodChannel&#xff08;方法通道&#xff09; 用途&#xff1a;實現 雙向通信&#xff0c;用于調用原生平臺提供的 API 并獲取返回結果。 場景&#xff1a;適合一次性操作&#xff0c;如調用相機、獲取設備信息等。 使用步驟&#xff1a; Flutter 端&#xff1a;通過 Meth…

Python控制語句-循環語句-while

1.若k為整形,下述while循環執行的次數為()。 k=1000 while k>1: print(k) k=k/2 A、9 B、10 C、11 D、100 答案:A。k=k/2意味著每循環一次,k的值就會變為原來的一半,直到k的值不大于1。 2.下面的代碼,哪些會輸出1,2,3三個數字( )。 A、 for i in range(3): print(i) …

十二天-雙指針技術:鏈表問題的高效解法

一、雙指針技術分類 1. 同速雙指針&#xff08;同向移動&#xff09; 特點&#xff1a;兩個指針以相同速度移動適用場景&#xff1a; 鏈表逆序查找倒數第 k 個元素刪除倒數第 n 個節點 2. 快慢雙指針&#xff08;異速移動&#xff09; 特點&#xff1a;一個指針每次移動 1 步…

【vllm】Qwen2.5-VL-72B-AWQ 部署記錄

版本&#xff1a;0.7.2 注意事項&#xff1a; export LD_LIBRARY_PATH/home/xxxxx/anaconda3/envs/xxxxx/lib/python3.10/site-packages/nvidia/nvjitlink/lib:$LD_LIBRARY_PATH # 如果報錯可能需要Also pip install --force-reinstall githttps://github.com/huggingface/tra…

深度學習與大模型-張量

大家好&#xff01;今天我們來聊聊張量&#xff08;Tensor&#xff09;。別被這個詞嚇到&#xff0c;其實它沒那么復雜。 什么是張量&#xff1f; 簡單來說&#xff0c;張量就是一個多維數組。你可以把它看作是一個裝數據的容器&#xff0c;數據的維度可以是一維、二維&#…

【前端面試題】Vu3常見的面試題

1.Vue3與 Vue2的核心區別有哪些&#xff1f; ? 響應式系統 ?&#xff1a; ? Vue2&#xff1a;通過Object.defineProperty 實現響應式。這種方式在處理對象屬性的添加和刪除時存在局限性&#xff0c;且無法直接監控數組的變化 ?;?Vue3&#xff1a;采用Proxy 實現響應式&…

Android 粘包與丟包處理工具類:支持多種粘包策略的 Helper 實現

在Android開發中&#xff0c;處理TCP/UDP通信時&#xff0c;粘包和丟包是常見的問題。粘包是指多個數據包被接收方一次性接收&#xff0c;導致數據包之間的界限不清晰&#xff1b;丟包則是指數據包在傳輸過程中丟失。為了處理這些問題&#xff0c;我們可以編寫一個幫助類 Packe…

【C++11】移動語義

回顧 const int c的c是可以被取地址的&#xff0c;盡管是常量。所以以是否為常量來判斷是否為右值是錯誤的。 左值與右值正確的區分方法是是否能夠被取地址。&#xff08;能被取地址也就代表著是一個持久狀態&#xff0c;即有持久的存儲空間的值&#xff09; 常見的左值有我們…

LangChain教程 - Agent -之 ZERO_SHOT_REACT_DESCRIPTION

在構建智能 AI 助手時&#xff0c;我們希望模型能夠智能地調用工具&#xff0c;以便提供準確的信息。LangChain 提供了 AgentType.ZERO_SHOT_REACT_DESCRIPTION&#xff0c;它結合了 ReAct&#xff08;Reasoning Acting&#xff09;策略&#xff0c;使得 LLM 可以基于工具的描…

移動Android和IOS自動化中常見問題

APP測試邏輯 在app編寫自動化測試用例時&#xff0c;通常會出現只是簡單的點點點過程&#xff0c;然而卻忽略了在實際的自動化實現過程中&#xff0c;軟件是對app元素的判斷來執行測試腳本。所以會出現在后期已經寫好自動化腳本之后還會對測試用例的更新。 App在測試時&#…