彻底掌握Android中的ViewModel

彻底掌握Android中的ViewModel

ViewModel 属于Android Jetpack库的一部分,是一种业务逻辑或屏幕状态容器。它提供了在配置更改(如屏幕旋转)后依旧保留相应状态的特性,帮助开发者以更加清晰和可维护的方式处理UI相关的数据,从而避免了在 Activity 或 Fragment 中直接处理数据持久化的问题。

ViewModel的使用

创建

日常开发中,ViewModel 经常充当 MVVM 架构的 VM 层,分担 Activity/Fragment 的部分逻辑,充当页面的数据存储容器。ViewModel 的创建方式有好几种,官方的 API 也改了几版,ViewModelProviders 已标为废弃,目前创建 ViewModel 统一使用 ViewModelProvider,代码实现有如下几种方式:

//无参构造函数ViewModel
class MyViewModel() : ViewModel() {//...
}

1.通过ViewModelProdiver

Activity 中:

private val viewModel by lazy {ViewModelProvider(this).get(MyViewModel::class.java)
}

Fragment 中:

private val viewModel by lazy {ViewModelProvider(this).get(MyViewModel::class.java)	//关联的是Fragment
}private val viewModel by lazy {ViewModelProvider(requireActivity()).get(MyViewModel::class.java)	//关联的是Activity
}

2.通过Android KTX

KTX 扩展库提供了很多常用功能的简洁实现,KTX 分为若干模块,开发者需要按需引用,这里需要用到 Fragment KTX 模块,首先将该模块代码依赖到工程:

implementation "androidx.fragment:fragment-ktx:1.6.2"

然后就可以用以下方式进行 ViewModel 的创建了,代码非常简洁:

Activity 中:

private val viewModel by viewModels<MyViewModel>()

Fragment 中:

private val viewModel1 by viewModels<MyViewModel>()	 //关联的是Fragment
private val viewModel2 by activityViewModels<MyViewModel>()  //关联的是Activity

3.有参数的ViewModel创建方式

上面两种创建的 ViewModel 构造器都是无参数的,但 ViewModel 有时候也需要依赖注入外部对象,这时 ViewModel 就需要提供有参数的构造器,重点是创建自定义的 ViewModel 的创建工厂。

先看下 UserViewModel 的定义:

//数据仓库层
object UserRepo {//网络逻辑...
}//构造器有参的ViewModel
class UserViewModel(val repo: UserRepo) : ViewModel() {//...
}

一般情况下,重写 ViewModelProvider.Factory 一个参数的 create 方法即可:

class Factory1(val repo: UserRepo): ViewModelProvider.Factory {override fun <T : ViewModel> create(modelClass: Class<T>): T {return UserViewModel(repo) as T}
}

创建代码:

//通过ViewModelProvider方式:
private val viewModel by lazy {ViewModelProvider(this, UserViewModel.Factory1(UserRepo)).get(UserViewModel::class.java)
}//通过KTX方式:
private val viewModel by viewModels<UserViewModel>(factoryProducer = { UserViewModel.Factory1(UserRepo) })

ViewModelProvider 中还提供了几个默认工厂:

  1. NewInstanceFactory:用来创建无参的 ViewModel,也是 ViewModelProvider 的默认工厂。
  2. AndroidViewModelFactory:继承自 NewInstanceFactory ,用来创建构造函数需要 Application 参数的 ViewModel 实例,特殊情况下会调用 NewInstanceFactory 创建无参的 ViewModel。

其实 KTX 最后也是通过 ViewModelProdiver 进行创建的,只不过通过 Kotlin 的属性委托机制将语法简化了,源码如下:

//ViewModel会通过by关键字委托到该类,每次使用该属性时,都会走到get方法中
public class ViewModelLazy<VM : ViewModel> @JvmOverloads constructor(private val viewModelClass: KClass<VM>,private val storeProducer: () -> ViewModelStore,private val factoryProducer: () -> ViewModelProvider.Factory,private val extrasProducer: () -> CreationExtras = { CreationExtras.Empty }
) : Lazy<VM> {private var cached: VM? = nulloverride val value: VMget() {val viewModel = cachedreturn if (viewModel == null) {val factory = factoryProducer()val store = storeProducer()//最终还是通过ViewModelProvider进行创建的ViewModelProvider(store,factory,extrasProducer()).get(viewModelClass.java).also {cached = it}} else {viewModel}}override fun isInitialized(): Boolean = cached != null
}
使用

ViewModel 一般作为 MVVM 架构的 VM 层,可以将 Activity/Fragment 的业务逻辑都封装到 ViewModel 中,比较常见的就是网络请求了。Google 推荐如下方式实现:

class MyViewModel : ViewModel() {private val _userLiveData: MutableLiveData<User> = MutableLiveData<User>()val userData: LiveData<User>    //外部获取的类型是LiveData,不可变的,防止外部随意修改get() = _userLiveDatafun doAction() {//...比如请求网络,并更新user_userLiveData.postValue(User("白泽..."))}
}
class ViewModelActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {val viewModel = ViewModelProvider(this).get(MyViewModel::class.java)viewModel.userData.observe(this) {  //1.订阅数据变化//update UI}findViewById<Button>(R.id.button).setOnClickListener {viewModel.doAction()	//2.触发数据更新}}
}

ViewModel是如何存储的

首先来看看 ViewModel 的几个重要类,分别是 ViewMdoel、ViewModelProvider、ViewModelStore 和 ViewModelStoreOwner,关系类图如下:
在这里插入图片描述

这不是严格的UML图,只需大概理解即可,下面介绍下每个类的职责:

  • ViewModelProvider:只负责 ViewModel 的创建。无参构造器的 ViewModel 可以直接用其内部提供的 NewInstanceFactory 工厂创建,如果ViewModel 需要构造器参数,则需要实现 ViewModelProvider.Factory 接口并完善创建逻辑。

  • ViewModelStore:负责 ViewModel 实例的存储,内部通过 HashMap 实现,map 的 value 就是 ViewMode 的实例,key 的生成规则如下:

    private static final String DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"//...String canonicalName = modelClass.getCanonicalName(); //返回此类的规范名称,如com.xx.x.MyViewModelget(DEFAULT_KEY + ":" + canonicalName, modelClass);//...
    

    所以同一 ViewModelStore 中,同一个类型的 ViewModel 并不会重复创建。

  • ViewModelStoreOwner:负责提供 ViewModelStore,常见的 ViewModelStoreOwner 有 ComponentActivity、Fragment 等,它们的内部会对 ViewModelStore 进行管理,在适当的时机进行创建和回收。以 ComponentActivity 为例,其内部会监听生命周期,并在生命周期变动时调用如下代码,确保 mViewModelStore 的存在:

    //1.Activity销毁时调用该方法临时保存ViewModelStore
    public final Object onRetainNonConfigurationInstance() {// Maintain backward compatibility.Object custom = onRetainCustomNonConfigurationInstance();ViewModelStore viewModelStore = mViewModelStore;if (viewModelStore == null) {// No one called getViewModelStore(), so see if there was an existing// ViewModelStore from our last NonConfigurationInstanceNonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();if (nc != null) {viewModelStore = nc.viewModelStore;}}if (viewModelStore == null && custom == null) {return null;}NonConfigurationInstances nci = new NonConfigurationInstances();nci.custom = custom;nci.viewModelStore = viewModelStore;return nci;
    }//2.Activity创建时恢复上次保存的ViewModelStore
    void ensureViewModelStore() {if (mViewModelStore == null) {NonConfigurationInstances nc =(NonConfigurationInstances) getLastNonConfigurationInstance();if (nc != null) {// Restore the ViewModelStore from NonConfigurationInstancesmViewModelStore = nc.viewModelStore;  //拿到上次保存的ViewModelStore}if (mViewModelStore == null) {mViewModelStore = new ViewModelStore();  //创建新的ViewModelStore}}
    }

    ViewModelStore 的销毁时机:Activity 走到 Destroy 并且使非配置更改(如正常finish)。

    getLifecycle().addObserver(new LifecycleEventObserver() {@Overridepublic void onStateChanged(@NonNull LifecycleOwner source,@NonNull Lifecycle.Event event) {if (event == Lifecycle.Event.ON_DESTROY) {// Clear out the available contextmContextAwareHelper.clearAvailableContext();// And clear the ViewModelStoreif (!isChangingConfigurations()) {getViewModelStore().clear();}}}
    });
    

    面试常问的问题:

    为什么 Activity 在旋转屏幕时,Activity 对象都发生重建了,但 ViewModel 却还是原来的对象?

    ViewModel 是怎么保存和恢复的?

    上面两个问题问的其实是 mViewModelStore 的保存和恢复,因为它是持有 ViewModel 实例的仓库。而 mViewModelStore 的存储和恢复是通过 onRetainNonConfigurationInstancegetLastNonConfigurationInstance来实现的。

    在配置更改时会调用 Activity#onRetainNonConfigurationInstance() 保存 mViewModelStore 对象,并在 Activity 重建后通过 getLastNonConfigurationInstance 方法获取上次保存的 ViewModelStore 对象,如果有则直接使用,否则创建新的实例对象。

    状态保存:onRetainNonCongigurationInstance

    该方法是 Android 提供的在配置更改时,临时保存 Activity 数据的 API。onRetainNonConfigurationInstance() 允许 Activity 在配置改变之前返回一个对象,这个对象随后可以在 Activity 重新创建后的getLastNonConfigurationInstance()方法中被检索到。

    下面是该机制的源码,在设备配置发生更改时(如旋转屏幕),会调用到 ActivityThread#handleRelaunchActivity 方法:

    //看方法名字可以知道是处理Activity重建逻辑的
    public void handleRelaunchActivity(ActivityClientRecord tmp, PendingTransactionActions pendingActions) {//...handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");//...
    }private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,PendingTransactionActions pendingActions, boolean startsNotResumed,Configuration overrideConfig, String reason) {//1. 处理旧Activity的销毁,注意第三个参数是getNonConfigInstance,传入的是truehandleDestroyActivity(r, false, configChanges, true, reason);//2. 处理Activity新建逻辑handleLaunchActivity(r, pendingActions, customIntent);
    }
    

    这个方法处理了两件事,一是旧 Activity 的回收,二是 Activity 的新建。先从 Activity 销毁开始看:

    @Override
    public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges,boolean getNonConfigInstance, String reason) {performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason);//...
    }void performDestroyActivity(ActivityClientRecord r, boolean finishing,int configChanges, boolean getNonConfigInstance, String reason) {//...注意:getNonConfigInstance为trueif (getNonConfigInstance) {try {//调用旧activity的方法,并保存到ActivityClientRecord对象中r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();} catch (Exception e) {//...}}//...
    }
    
    NonConfigurationInstances retainNonConfigurationInstances() {Object activity = onRetainNonConfigurationInstance();  //1.调用了onRetainNonConfigurationInstance方法HashMap<String, Object> children = onRetainNonConfigurationChildInstances(); //2.可以缓存一些自定义数据FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();  //3.Fragment状态//...NonConfigurationInstances nci = new NonConfigurationInstances();nci.activity = activity;nci.children = children;nci.fragments = fragments;nci.loaders = loaders;if (mVoiceInteractor != null) {mVoiceInteractor.retainInstance();nci.voiceInteractor = mVoiceInteractor;}return nci;
    }
    

    可以看到,Activity 销毁做了两件事:

    1. 调用了 Activity 对象的 onRetainNonConfigurationInstance 方法拿到临时对象,并赋值给 ActivityClientRecord#lastNonConfigurationInstances变量。
    2. 调用 Activity 的 pause、stop、destroy 等生命周期方法。

    接下来看 Activity 新建逻辑:

    public Activity handleLaunchActivity(ActivityClientRecord r,PendingTransactionActions pendingActions, Intent customIntent) {//...final Activity a = performLaunchActivity(r, customIntent);//...
    }private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {//...Activity activity = null;try {java.lang.ClassLoader cl = appContext.getClassLoader();//1.通过反射创建Activity对象activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);}//...try {if (activity != null) {//2.调用 attach 方法,将缓存信息传入activity.attach(appContext, this, getInstrumentation(), r.token,r.ident, app, r.intent, r.activityInfo, title, r.parent,r.embeddedID, r.lastNonConfigurationInstances, config,r.referrer, r.voiceInteractor, window, r.activityConfigCallback,r.assistToken, r.shareableActivityToken);//...return activity;
    }
    

    创建 Activity 流程同样做了两件事:

    1. 通过反射创建 Activity 实例对象
    2. 调用 attach 方法,将销毁时缓存在 ActivityClientRecord#lastNonConfigurationInstances 变量中的临时变量关联到新的 Activity 对象
    final void attach(Context context, ActivityThread aThread,Instrumentation instr, IBinder token, int ident,Application application, Intent intent, ActivityInfo info,CharSequence title, Activity parent, String id,NonConfigurationInstances lastNonConfigurationInstances,Configuration config, String referrer, IVoiceInteractor voiceInteractor,Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,IBinder shareableActivityToken) {//赋值给了mLastNonConfigurationInstancesmLastNonConfigurationInstances = lastNonConfigurationInstances;
    }public Object getLastNonConfigurationInstance() {return mLastNonConfigurationInstances != null? mLastNonConfigurationInstances.activity : null;
    }
    

    执行完 Activity 的 attach 方法后,就可以通过 getLastNonConfigurationInstance 方法获取之前 Activity 销毁时保存的状态数据了,到此 Activity 保存和恢复数据的链路就通了。

    onRetainNonConfigurationInstance 方法用 final 修饰,并已标为废弃了,其实改保存数据方案在 Android 3.0 就已经废弃了,Google 不希望我们用这个机制去进行 Activity 状态保存,而是推荐用基于该机制上衍生的 ViewModel 进行状态保存,使用起来更简单、更安全。

    onRetainNonCongigurationInstance 和 onSaveInstanceState区别:

    onSaveInstanceState 同样是用于处理 Activity 状态保存和恢复的方法,它与 onRetainNonCongigurationInstance 方式区别如下:

    1. 使用场景不同
      • onRetainNonCongigurationInstance 用于在设备配置更改时(如屏幕旋转)临时保存 Activity 的状态或数据。
      • onSaveInstanceState 用于Activity 即将被销毁时(无论是由于用户离开、配置更改还是系统回收资源),保存 Activity 的状态或数据,是一个更通用、更灵活的状态保存机制。。
    2. 支持数据类型不同
      • onRetainNonCongigurationInstance 返回 Object 类型对象,可以是任何类型,包括 Activity 实例本身或大型数据结构,如果使用不当容易造成内存泄漏。
      • onSaveInstanceState 通过 Bundle 对象来保存状态,只能存储基本数据类型、可序列化的对象或实现了 Parcelable 接口的对象,确保数据的安全性和可恢复性。
    3. 恢复数据方式不同
      • onRetainNonConfigurationInstance 在Activity重新创建后,可以通过调用getLastNonConfigurationInstance()方法来检索之前保存的数据。
      • onSaveInstanceState 在Activity重新创建时,系统会将之前保存的Bundle对象传递给onCreate(Bundle savedInstanceState)onRestoreInstanceState(Bundle savedInstanceState)方法。

ViewModel的协程作用域

协程是 Kotlin 的又一高效编程利器,使用协程可以非常简单的进行多线程协作。ViewModel 提供了和其生命周期一致协程作用域,可以引入 KTX 的 ViewModel 模块,让使用更简单:

dependencies {implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6"
}

使用时:

viewModel.viewModelScope.launch {if (isActive) {  //判断协程是否被取消了//doAction...}
}

下面来探索一下 ViewModel 协程作用域是如何管理的,首先看 viewModelScope 源码:

public val ViewModel.viewModelScope: CoroutineScopeget() {val scope: CoroutineScope? = this.getTag(JOB_KEY)  //1.缓存的协程作用域对象if (scope != null) {return scope}//2.创建协程return setTagIfAbsent(JOB_KEY,CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))}

可以看到,如果没从缓存获取到则直接通过 setTagIfAbsent 方法创建,协程上下文类型是 SupervisorJobDispatchers.Main,可以让发生异常时不影响其他协程,并且代码运行在主线程。

private final Map<String, Object> mBagOfTags = new HashMap<>();//1.setIfAbsend 系列方法一般表示,如果存在旧值就不进行赋值了,防止多次创建
<T> T setTagIfAbsent(String key, T newValue) {T previous;//2.通过同步锁,防止多线程场景错误synchronized (mBagOfTags) {previous = (T) mBagOfTags.get(key);if (previous == null) {mBagOfTags.put(key, newValue);}}T result = previous == null ? newValue : previous;if (mCleared) {closeWithRuntimeException(result);}return result;
}

通过该方法可以看到,ViewModel 的协程作用域创建后会缓存在 ViewModel 中的 mBagOfTags 中,这是一个 map 结构,key 为 JOB_KEY ,value 为协程作用域对象。

知道了 ViewModel 的协程作用域是如何创建和保存的,下面看协程是如何取消的。

还记得上面 ViewModel 对象的保存逻辑吗,ComponentActivity 会监听 DESTROY 生命周期,Activity 正常销毁时,会执行 ViewModelStore 的 clear 方法,ViewModelStore 又会遍历所有保存的 ViewModel 对象,并调用其 clear 方法。

final void clear() {mCleared = true;//1.加锁,进行协程作用域的取消if (mBagOfTags != null) {synchronized (mBagOfTags) {for (Object value : mBagOfTags.values()) {//2.取消协程closeWithRuntimeException(value);}}}//2.ViewModel销毁时会调用该方法,可以重写它进行长时间任务的清理工作onCleared();
}

可见最终会调用到 closeWithRuntimeException 方法进行协程取消,内部其实是 coroutineContext.cancel()

ViewModel 的协程作用域会在获取时进行创建,并缓存在 ViewModel 的 mBagOfTags 映射表内部,在 ViewModel 销毁时取消。协程取消并不会强制终端代码逻辑,使用 ViewModel 协程作用域进行长时间任务时,注意使用 isActive 方法适时判断协程是否被取消了。

ViewModel 的协程作用域和 Lifecycle 的协程作用域有何区别?

Android 中除了 ViewModel 提供了协程作用域外,Lifecycle 也提供了 lifecycleScope 协程作用域,首先看下 Lifecycle 的协程作用域是如何创建、保存和销毁的:

//Lifecycle:
//1.AtomicReference让对象读,写都是原子操作,保证修改对象引用时的线程安全
public var internalScopeRef: AtomicReference<Any> = AtomicReference<Any>()public val Lifecycle.coroutineScope: LifecycleCoroutineScopeget() {while (true) {val existing = internalScopeRef.get() as LifecycleCoroutineScopeImpl?if (existing != null) {return existing}//2.创建协程作用域val newScope = LifecycleCoroutineScopeImpl(this,SupervisorJob() + Dispatchers.Main.immediate)//3.通过CAS机制设置对象if (internalScopeRef.compareAndSet(null, newScope)) {newScope.register()return newScope}}}
internal class LifecycleCoroutineScopeImpl(override val lifecycle: Lifecycle,override val coroutineContext: CoroutineContext
) : LifecycleCoroutineScope(), LifecycleEventObserver {init {if (lifecycle.currentState == Lifecycle.State.DESTROYED) {coroutineContext.cancel() //1.取消协程}}fun register() {launch(Dispatchers.Main.immediate) {if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) {lifecycle.addObserver(this@LifecycleCoroutineScopeImpl)} else {coroutineContext.cancel()}}}override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {lifecycle.removeObserver(this)coroutineContext.cancel()  //2.取消协程}}
}

Lifecycle 的代码非常简单,同样在获取时创建协程作用域,并通过CAS机制保存到 internalScopeRef 对象引用,在 DESTROY 时进行协程的销毁操作。

通过上面分析 ViewModel 和 Lifecycle 的协程作用域相关代码,可以分析出以下几点区别:

  • ViewModel 协程作用域创建时通过 synchronized 同步锁保证线程安全;Lifecycle 协程作用域创建时通过 while 循环 + CAS 机制保证线程安全。相对来说 CAS 机制更能保证效率,ViewModel 使用 synchronized,主要还是因为其中的 mBagOfTags,它是一个Map,Android 官方因为一些旧系统的限制,导致无法使用ConcurrentHashMap,所以才出此下策。
  • ViewModel 和 Lifecycle 的协程作用域生命周期不同,因为销毁时机不一样,就像刚开始 ViewModel 的生命周期和 Activity 生命周期一样。

总结

Android 的 ViewModel 是一个强大的架构组件,它通过提供数据、管理状态以及生命周期感知等能力,帮助开发者构建更加健壮、易于维护和测试的应用。在 MVVM 代码架构中,ViewModel 是视图(View)与数据(Model)之间的桥梁,它负责为 UI组件提供数据,并管理 UI组件的状态,UI状态与业务逻辑分离,使得代码耦合性更低,更易于测试。

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

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

相关文章

【中级通信工程师】终端与业务(二):终端产品

【零基础3天通关中级通信工程师】 终端与业务(二)&#xff1a;终端产品 本文是中级通信工程师考试《终端与业务》科目第二章《终端产品》的复习资料和真题汇总。终端与业务是通信考试里最简单的科目&#xff0c;有效复习通过率可达90%以上&#xff0c;本文结合了高频考点和近几…

产教专家共议数字时代下的数据思维人才培养

8 月 25 日至 26 日&#xff0c;"数据思维人才培养论坛"在大湾区大学松山湖校区圆满落幕。此次论坛作为对两个月前新时代计算机本科教育论坛上相关议题的深化&#xff0c;由中国人民大学杜小勇教授与大湾区大学李晓明教授携手发起。和鲸科技创始人、CEO 范向伟先生受…

【常见框架漏洞】ThinkPHP、struts2、Spring、Shiro

一、ThinkPHP 1.环境配置 靶场:vulhub/thinkphp/5-rcedocker-compose up -d #启动环境 访问靶场:http://ip:8080/index.php2.远程命令执行 执行whoami命令 poc: http://47.121.211.205:8080/index.php?sindex/think\app/invokefunction&functioncall_user_func_array&…

算法【Java】—— 位运算

位运算总结 位运算的运算符&#xff1a;按位与&#xff08;&&#xff09;&#xff0c;按位或&#xff08;|&#xff09;&#xff0c;按位异或&#xff08;^&#xff09;&#xff0c;按位取反&#xff08;~&#xff09;&#xff0c;还有移位操作符 <<&#xff0c;>…

加密与解密-PEiD查壳工具的下载及详细安装过程(附有下载文件)

下载链接在文末 下载压缩包后解压 &#xff01;&#xff01;安装路径不要有中文 解压后得到PEiD文件 双击任意一个即可运行使用 夸克网盘打开&#xff1a; 链接&#xff1a;https://pan.quark.cn/s/1216d81f1af5 提取码&#xff1a;pJ1W 动态分析工具 加密与解密-x32dbg…

人工智能时代:程序员的核心竞争力提升指南

在人工智能时代&#xff0c;程序员的核心竞争力不仅体现在编码技能上&#xff0c;更在于如何利用AI技术提升工作效率和创新能力。以下是一些关键点&#xff0c;可以帮助程序员在AI时代保持并提升自身的核心竞争力。 AI辅助编程正在逐渐改变程序员的工作方式&#xff0c;带来了一…

【人工智能】Transformers之Pipeline(十九):文生文(text2text-generation)

目录 一、引言 二、文生文&#xff08;text2text-generation&#xff09; 2.1 概述 2.2 Flan-T5: One Model for ALL Tasks 2.3 pipeline参数 2.3.1 pipeline对象实例化参数 2.3.2 pipeline对象使用参数 ​​​​​​​ 2.3.3 pipeline返回参数 ​​​​​​​​​​​…

模拟实现(优先级队列)priority_queue:优先级队列、仿函数、 反向迭代器等的介绍

文章目录 前言一、优先级队列二、仿函数三、 反向迭代器总结 前言 模拟实现&#xff08;优先级队列&#xff09;priority_queue&#xff1a;优先级队列、仿函数、 反向迭代器等的介绍 一、优先级队列 优先级队列本质是一个堆&#xff0c;使用vector容器进一步改进进行实现&am…

面向对象 vs 面向过程

Java 和 C 语言的区别&#xff1a;面向对象 vs 面向过程 在编程世界中&#xff0c;不同的编程语言承载着不同的编程范式。C 语言作为一门经典的面向过程编程语言&#xff0c;注重函数的调用和操作&#xff1b;而Java则是典型的面向对象编程语言&#xff0c;重视对象与类的设计…

【计算机网络】传输层协议TCP

目录 一、重新理解封装和解包二、TCP协议段格式三、确认应答(ACK)机制四、超时重传机制五、连接管理机制六、理解TIME_WAIT状态和CLOSE_WAIT状态七、流量控制八、滑动窗口九、拥塞控制十、延迟应答十一、面向字节流十二、粘包问题 一、重新理解封装和解包 在网络协议栈中&…

【LeetCode】动态规划—第 N 个泰波那契数(附完整Python/C++代码)

动态规划—#1137. 第 N 个泰波那契数 前言题目描述基本思路1. 泰波那契数列的定义:2. 理解递推关系:3. 解决方法:4. 进一步优化:5. 小总结: 代码实现Python3代码实现Python 代码解释C代码实现C 代码解释 总结: 前言 泰波那契数列是斐波那契数列的扩展版本。在斐波那契数列中&a…

三款远控工具大比拼,哪款更胜一筹?

当我们处在日益便捷的数字化生活中&#xff0c;我们不仅需要在实体空间与物理环境间活动&#xff0c;我们更可以通过科技的力量在屏幕间自由穿梭&#xff1b;向日葵远程控制工具&#xff0c;就是这样一款能让你在指尖上体验到操作乐趣的神奇工具&#xff1b;今天&#xff0c;就…

着色器(Vertex Shader)基础

什么是顶点着色器 顶点着色器处理顶点并告知它们在“剪辑空间”中的坐标,该空间使计算机可以轻松了解哪些顶点对摄像机可见,哪些顶点不可见,必须剪切或“剪切”掉。 这使得 GPU 在后期阶段的速度更快,因为它们需要处理的数据较少。 它们通过接收来自顶点列表中的单个顶…

优可测一键闪测仪:实现冲压端子的快速精准尺寸检测

上期&#xff0c;小优博士讲述了和白光干涉仪在红外探测行业的应用与优势&#xff0c;今天&#xff0c;小优博士为大家继续带来&#xff1a; 《优可测一键式影像测量仪&#xff1a;实现冲压端子的快速精准尺寸检测》 冲压端子是通过金属冲压工艺制成&#xff0c;用于电气导线与…

排序题目:将矩阵按对角线排序

文章目录 题目标题和出处难度题目描述要求示例数据范围 前言解法思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;将矩阵按对角线排序 出处&#xff1a;1329. 将矩阵按对角线排序 难度 5 级 题目描述 要求 矩阵对角线是一条从矩阵最上面行或者最左侧列中的某…

【C++代码运行结果测试】基类与派生类的成员变量值的调用结果

【铺垫】派生类对象可被基类指针所指向&#xff0c;效果与被派生类指针指向等效 【代码测试1】15浙工大卷一读程序5题代码改 【代码测试2】C教辅p206例7.21 【代码1】15浙工大卷一读程序5题代码改 #include "bits/stdc.h" #include<iostream> using namesp…

谷歌发布新 RL 方法,性能提升巨大;苹果前设计总监正与 OpenAI 合作开发 AI 设备丨 RTE 开发者日报

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE&#xff08;Real-Time Engagement&#xff09; 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文…

机器人顶刊IEEE T-RO发布无人机动态环境高效表征成果:基于粒子的动态环境连续占有地图

摘要&#xff1a;本研究有效提高了动态环境中障碍物建模的精度和效率。NOKOV度量动作捕捉系统助力评估动态占用地图在速度估计方面的性能。 近日&#xff0c;上海交通大学、荷兰代尔夫特理工研究团队在机器人顶刊IEEE T-RO上发表题为Continuous Occupancy Mapping in Dynamic …

数据加密和数字证书

1 什么是数据加密 数据加密的基本过程就是对原来为明文的文件或数据按某种算法进行处理,使其成为不可读的一段代码,通常称为"密文",使其只能在输入相应的密钥之后才能显示出本来内容,通过这样的途径来达到保护数据不被非法人窃取、阅读的目的。 该过程的逆过程…

人工智能课程实训方案

第一章 发展背景 当今&#xff0c;世界无时无刻不在发生着变化。对于技术领域而言&#xff0c;普遍存在的一个巨大变化就是为大数据&#xff08;Big data&#xff09;打开了大门。随着国家大数据战略推进实施以及配套政策的贯彻落实&#xff0c;大数据产业发展环境进一步优化&a…