ViewModel进阶 | 使用SavedState实现数据复用的另一种方式

news2024/11/18 7:32:19

前言:金风玉露一相逢,便胜人间无数。

前言

本文需要有上一篇文章基础,如果不了解的可以先看看《由浅入深,ViewModel配置变更的复用详解》

前面我们了解到,ViewModel 它只能做到因为配置变更页面被销毁而导致重建数据才能复用
这样的话它的使用场景就不是很大了,因为配置变更的情况是比较少的。但是如果因为内存不足,电量不足等系统原因导致的页面被回收,那么被重建之后的 ViewModel 还能不能被复用呢?

答案是可以的。这需要用到 SavedState 能力,这种方式即便页面被重建之后,ViewModel 不是同一个实例了,都没有问题。但是它们这两种方式复用、存储以及实现原理是不一样的。ViewModel 远没有我们想象中的简单。

一、SavedState的进阶用法

先来了解一下如何使用 ViewModel 无论是因为配置变更,还是内存不足,电量不足等非配置变更导致的页面被回收再重建,都可以复用存储的数据。即便 ViewModel 不是同一个实例,它存储的数据也能做到复用。

需要在 build.gradle 引入依赖组件 savedstate

api 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.1'

如果页面被正常关闭,这里的数据会被正常清理释放,打开 开发者模式-开启"不保留活动"的选项,以此来模拟 Activity 由于系统内存不足被销毁的情况。

//SavedStateHandle里面就是包装了一个HashMap,可以使用它去存储数据
class MainSaveViewModel(val savedState: SavedStateHandle) : ViewModel() {
    private val KEY_USER_INFO = "key_user_info"
    val savedStateLiveData = MutableLiveData<String?>()

    fun getUserInfo() {
        if (savedStateLiveData.value != null) return
        //1.从savedState中获取数据
        val memoryData = savedState.get<String>(KEY_USER_INFO)
        if (!memoryData.isNullOrEmpty()) {
            savedStateLiveData.postValue("缓存数据-$memoryData")
        } else {
            //2.从网络获取数据
            // 模拟请求接口返回数据
            viewModelScope.launch {
                delay(1000)
                TipsToast.showTips("请求网络获取数据")
                val data = "SavedStateHandle-苏火火 苏火火 苏火火 苏火火 苏火火"
                savedState.set(KEY_USER_INFO, data)
                savedStateLiveData.postValue(data)
            }
        }
    }
}

class DemoViewModelActivity : BaseDataBindActivity<ActivityViewmodelBinding>() {
    override fun initView(savedInstanceState: Bundle?) {
        LogUtil.d("onCreate()")
        // 获取 SavedState 保存的数据
        val saveViewModel = ViewModelProvider(this).get(MainSaveViewModel::class.java)
        saveViewModel.savedStateLiveData.observe(this) {
            mBinding.tvUserInfo.text = it
        }
        mBinding.tvRequestSavedStateInfo.onClick {
            saveViewModel.getUserInfo()
        }
    }
}

首先模拟从网络获取数据,然后按 Home 键进入后台,此时 Activity 在后台就会被销毁。再将应用从后台回到前台,Activity 恢复重建,再次获取数据:

在这里插入图片描述

可以看到,从内存中取出的数据是非常快的,是缓存中的数据。生命周期打印数据如下:

5074-5074/com.sum.tea D/LogUtil: onCreate()
5074-5074/com.sum.tea D/LogUtil: onStop()
5074-5074/com.sum.tea D/LogUtil: onDestroy()
5074-5074/com.sum.tea D/LogUtil: onCreate()

源码地址:https://github.com/suming77/SumTea_Android

二、SavedState架构

SaveState 的数据存储与恢复,有几个核心类:

  • SaveStateRegistryOwner:  核心接口,用来声明宿主,Activity 和 Fragment 都实现了这个接口,实现接口的同时必须返回一个 SaveStateRegistry,SaveStateRegistry 的创建委托给 SaveStateRegistryController 来完成。

  • SaveStateRegistryController:控制器,用于创建 SaveStateRegistry,与 Activity、Fragment 建立连接,为了剥离 SaveStateRegistry 与 Activity 的耦合关系。

  • SaveStateRegistry:核心类,数据存储恢复中心,用于存储、恢复一个 ViewModel 中的 bundle 数据,与宿主生命周期绑定。

  • SaveStateHandle: 核心类,一个 ViewModel 对应一个 SaveStateHandle,用于存储和恢复数据

  • SaveStateRegistry模型:一个总 Bundle,key-value 存储着每个 ViewModel 对应的子 bundle。

在这里插入图片描述

由于页面有可能存在多个 ViewModel,那么每个 ViewModel 当中的数据都会通过 SaveStateHandle 来存储,所以 SaveStateRegistry 的数据结构是一个总的 Bundle,key 对应着 ViewModel 的名称,value 就是每个 SaveStateHandle 保存的数据,这样做的目的是为整存整取。

因为 ViewModel 在创建的时候需要传递一个 SaveStateHandle,SaveStateHandle 又需要一个 Bundle 对象,这个 Bundle 可以从 Bundle mRestoredState 里面获取。它里面存储的 ViewModel 即便被销毁了,那么在 Activity 重建的时候也会复用的。

三、ViewModel数据复用进阶SavedState

1.SavedState数据存储

下面进入源码,以 Activity 为例,Fragment 也是类似的。ComponentActivity 实现了 SavedStateRegistryOwner 接口,它是一个宿主,用来提供 SavedStateRegistry 这个对象的,这个对象就是存储中心:

// 实现了ViewModelStoreOwner和HasDefaultViewModelProviderFactory接口
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory {

    @Override
    public final SavedStateRegistry getSavedStateRegistry() {
        // 委托给SavedStateRegistryController创建数据存储中心SavedStateRegistry
        return mSavedStateRegistryController.getSavedStateRegistry();
    }
}

实例的创建委托给 mSavedStateRegistryController,它是一个被 Activity 委托的对象,用来创建 SavedStateRegistry 的,目的是为了剥离 Activity/Fragment 这种宿主与数据存储中心的关系。

数据存储是在哪里呢,其实是在 onSaveInstanceState() 里面:

@Override
protected void onSaveInstanceState(Bundle outState) {
    Lifecycle lifecycle = getLifecycle();
    if (lifecycle instanceof LifecycleRegistry) {
        ((LifecycleRegistry) lifecycle).setCurrentState(Lifecycle.State.CREATED);
    }
    super.onSaveInstanceState(outState);
    // 通过Controller直接将Bundle数据转发
    mSavedStateRegistryController.performSave(outState);
}

通过 mSavedStateRegistryController.performSave(outState) 直接转发给SavedStateRegistry:

#SavedStateRegistry.java
@MainThread
// 数据保存
void performSave(Bundle outBundle) {
    Bundle components = new Bundle();
    if (mRestoredState != null) {
        components.putAll(mRestoredState);
    }
    // 将ViewModel中的数据存储到Bundle中
    for (Iterator<Map.Entry<String, SavedStateProvider>> it =
            mComponents.iteratorWithAdditions(); it.hasNext(); ) {
        Map.Entry<String, SavedStateProvider> entry1 = it.next();
        components.putBundle(entry1.getKey(), entry1.getValue().saveState());
    }
    outBundle.putBundle(SAVED_COMPONENTS_KEY, components);
}

真正用来存储每个 ViewModel 数据的地方,遍历了 mComponents:

// 以键值对的形式保存着SavedStateProvider
private SafeIterableMap<String, SavedStateProvider> mComponents = new SafeIterableMap<>();

SavedStateProvider 是一个接口,用于保存状态对象。
那么 components 中的元素什么时候被添加进来的呢?实际上就是 SavedStateHandle 对象被创建的时候,调用 registerSavedStateProvider() 注册进来。

//SavedStateHandle 对象创建时调用
@MainThread
public void registerSavedStateProvider(String key, SavedStateProvider provider) {
    // 保存SavedStateProvider
    SavedStateProvider previous = mComponents.putIfAbsent(key, provider);
}

来看看 SavedStateHandle 是如何完成数据存储工作的:

public final class SavedStateHandle {
    final Map<String, Object> mRegular;
    final Map<String, SavedStateProvider> mSavedStateProviders = new HashMap<>();
    private final Map<String, SavingStateLiveData<?>> mLiveDatas = new HashMap<>();

    // 每个SavedStateHandle对象中都有一个SavedStateProvider对象
    private final SavedStateProvider mSavedStateProvider = new SavedStateProvider() {
        // SavedStateRegistry保存数据时调用,将数据转为Bundel返回
        @Override 
        public Bundle saveState() {
            Map<String, SavedStateProvider> map = new HashMap<>(mSavedStateProviders);
            for (Map.Entry<String, SavedStateProvider> entry : map.entrySet()) {
                Bundle savedState = entry.getValue().saveState();
                set(entry.getKey(), savedState);
            }
            // 遍历mRegular集合,将当前缓存的Map数据转换为Bundle
            Set<String> keySet = mRegular.keySet();
            ArrayList keys = new ArrayList(keySet.size());
            ArrayList value = new ArrayList(keys.size());
            for (String key : keySet) {
                keys.add(key);
                value.add(mRegular.get(key));
            }

            Bundle res = new Bundle();
            // 序列化数据
            res.putParcelableArrayList("keys", keys);
            res.putParcelableArrayList("values", value);
            return res;
        }
    };

重点,每个 SavedStateHandle 对象中都有一个 SavedStateProvider 对象,并且实现了 saveState() 方法,遍历 mRegular 集合,里面放的就是要缓存的键值对数据,然后打包成一个 Bundle 对象返回:

#SavedStateRegistry.java
@MainThread
// 数据保存时调用
void performSave(Bundle outBundle) {
    Bundle components = new Bundle();
    if (mRestoredState != null) {
        // 将SavedStateHandle存储到components中
        components.putAll(mRestoredState);
    }

    for (Iterator<Map.Entry<String, SavedStateProvider>> it =
            mComponents.iteratorWithAdditions(); it.hasNext(); ) {
        Map.Entry<String, SavedStateProvider> entry1 = it.next();
        components.putBundle(entry1.getKey(), entry1.getValue().saveState());
    }
    // 将components存储到outBundle中
    outBundle.putBundle(SAVED_COMPONENTS_KEY, components);
}

把每个 SavedStateHandle 存储到 components 对象当中,然后又把 components 存储到 outBundle 中,这样它就能完成所有 ViewModel 的数据存储,并且每个 ViewModel 中的数据以独立的 Bundle 数据进行存储,这样做的目的就是为整存整取。

SavedState数据存储流程小结:

SavedState 数据存储流程,逐一调用每个 SavedStateHandle 保存自己的数据,汇总成一个总 Bundle,在存储到 Activity 的 SavedState 对象中。

在这里插入图片描述

Activity 在内存不足电量不足等系统原因,被回收的情况下,肯定会执行 onSaveInstanceState() 方法,Activity 紧接着就利用 SaveStateRegistryController 转发给 SaveStateRegistry,让它去完成数据存储的工作,SaveStateRegistry 在存储数据的时候会遍历所有注册的 SaveStateProvider 去存储自己的数据,并且返回一个 Bundle 对象,最后合并成一个总的 Bundle,存储到 Activity 的 savedSate 对象当中。

2.SavedState数据的恢复

SavedState 数据复用流程分为两步:第一步先从 Activity 的 savedState 恢复所有 ViewModel 的数据到 SaveStateRegistry。

需要到 ComponentActivity 的 onCreate() 方法中:

#ComponentActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 通过SavedStateRegistryController将bundle数据传递给SavedStateRegistry
    mSavedStateRegistryController.performRestore(savedInstanceState);
    //·····
}

然后通过 SavedStateRegistryController 转发到 SavedStateRegistry 的performRestore()

#SavedStateRegistry.java
// 数据恢复会调用这个方法
void performRestore(Lifecycle lifecycle, Bundle savedState) {
    if (savedState != null) {
        // savedState中根据key获取数据Bundle数据,components对象
        mRestoredState = savedState.getBundle(SAVED_COMPONENTS_KEY);
    }

    lifecycle.addObserver(new GenericLifecycleObserver() {
        @Override
        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
            if (event == Lifecycle.Event.ON_START) {
                mAllowingSavingState = true;
            } else if (event == Lifecycle.Event.ON_STOP) {
                mAllowingSavingState = false;
            }
        }
    });

    mRestored = true;
}

通过 savedState 取出刚才存储的 components 对象,并且赋值给 mRestoredState,数据的恢复是非常简单的,就是从 Activity 的 savedState 对象中取出前面存储的 Bundle 数据,并且赋值给 mRestoredState

SavedState数据的恢复小结

从 Activity 的 savedState 恢复所有 ViewModel 的数据到 SaveStateRegistry:

在这里插入图片描述

在 Activity 创建的时候会执行 onCreate() 方法,也有一个 savedState 的 Bundle 对象,在 ComponentActivity 里面 onCreate() 里面它又会调用这里的 SaveStateRegistryController,把 Bundle 转发给 SaveStateRegistry,它实际上就是从这个 performRestore(Bundle) 的 savedState 取出它刚才存储的 storeBundle 数据对象,并且保存它。这一步仅仅是取出数据,叫做数据的恢复。

3.SavedState数据复用

第二步:上面仅仅是在 Activity 重新创建时完成数据的恢复,但是这时数据还没有被复用,复用需要去到 Activity 中:

class DemoViewModelActivity : BaseDataBindActivity<ActivityViewmodelBinding>() {
    override fun initView(savedInstanceState: Bundle?) {
        // 获取 SavedState 保存的数据
        val saveViewModel = ViewModelProvider(this).get(MainSaveViewModel::class.java)
    }
}

假设 onCreate() 是因为被系统原因销毁了重建,才执行过来的:

// ViewModelProvider的构造方法
public ViewModelProvider(ViewModelStoreOwner owner) {
    this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
            ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
            : NewInstanceFactory.getInstance());
}

owner.getViewModelStore() 这里不仅仅会获取 Activity 缓存里面的 ViewModelStore,还会判断宿主是否实现了 HasDefaultViewModelProviderFactory 接口,ComponentActivity 中是已经实现了该接口的:

// 实现了ViewModelStoreOwner和HasDefaultViewModelProviderFactory接口
public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner,
        ViewModelStoreOwner,
        HasDefaultViewModelProviderFactory {

    // DefaultViewModelProviderFactory工厂实现
    @Override
    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
        if (mDefaultFactory == null) {
            // 创建一个SavedStateViewModelFactory返回
            mDefaultFactory = new SavedStateViewModelFactory(
                    getApplication(),
                    this,
                    getIntent() != null ? getIntent().getExtras() : null);
        }
        return mDefaultFactory;
    }
}

这里返回的是一个 SavedStateViewModelFactory,那就是说 SavedStateViewModel 实例都是默认使用这个 Factory 来创建,这个 Factory 有什么不同呢,它的不同之处在于,定义一个 ViewModel 的时候,可以在构造函数里面指定一个参数

// 指定一个SavedStateHandle参数
class MainSaveViewModel(val savedState: SavedStateHandle) : ViewModel() {}

//指定两个参数Application和SavedStateHandle
class HomeAndroidViewModel(val application: Application, val savedStateHandle: SavedStateHandle) : AndroidViewModel(appInstance) {}

SavedStateViewModelFactory 在新建 ViewModel 的时候就会判断你的构造函数有没有参数,如果没有参数就以普通的形式进行反射创建它的实例对象,如果有参数就会判断是不是 SavedStateHandle 类型的,如果是则会从刚才 SavedStateRegistry 当中去取出它所缓存的数据,并构建一个 SavedStateHandle 对象,传递进来

#ViewModelProviderpublic ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
    this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
            ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
            : NewInstanceFactory.getInstance());
}

getDefaultViewModelProviderFactory() 实际是 SavedStateViewModelProviderFactory:

SavedStateViewModelFactory类:
@Override
public <T extends ViewModel> T create(String key, Class<T> modelClass) {
    // 判断是否为AndroidViewModel
    boolean isAndroidViewModel = AndroidViewModel.class.isAssignableFrom(modelClass);
    Constructor<T> constructor;
    // 获取构造器
    if (isAndroidViewModel && mApplication != null) {
        constructor = findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE);
    } else {
        constructor = findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE);
    }
    // 普通方式创建ViewModel实例
    if (constructor == null) {
        return mFactory.create(modelClass);
    }
    
    // 创建SavedStateHandleController
    SavedStateHandleController controller = SavedStateHandleController.create(
            mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
    try {
        T viewmodel;
        // 根据构造器参数创建viewmodel
        if (isAndroidViewModel && mApplication != null) {
            viewmodel = constructor.newInstance(mApplication, controller.getHandle());
        } else {
            viewmodel = constructor.newInstance(controller.getHandle());
        }
        return viewmodel;
    }
}

创建的时候判断 modelClass 是否拥有两种构造函数:

//第一种:有两个参数
private static final Class<?>[] ANDROID_VIEWMODEL_SIGNATURE = new Class[]{Application.class,
        SavedStateHandle.class};
//第二种:只有一个参数
private static final Class<?>[] VIEWMODEL_SIGNATURE = new Class[]{SavedStateHandle.class};

如果上面两种都没有,那么在构造实例的时候,就会以普通的形式构造实例 AndroidViewModelFactory,实际上是通过反射

if (constructor == null) {
    return mFactory.create(modelClass);
}

如果上的构造函数 constructor != null,都会走到下面,并且通过刚才获取到的 constructor 构造函数去创建具体的实例对象,并且传递指定的参数:

SavedStateViewModelFactory类:
@Override
public <T extends ViewModel> T create(String key, Class<T> modelClass) {
    //······
    // 创建SavedStateHandleController
    SavedStateHandleController controller = SavedStateHandleController.create(
            mSavedStateRegistry, mLifecycle, key, mDefaultArgs);
    try {
        T viewmodel;
        // 根据构造器参数创建viewmodel
        if (isAndroidViewModel && mApplication != null) {
            viewmodel = constructor.newInstance(mApplication, controller.getHandle());
        } else {
            viewmodel = constructor.newInstance(controller.getHandle());
        }
    }
}

controller.getHandle() 实际上得到的是 SavedStateHandle,controller 是通过SavedStateHandleController.create() 创建,这个类有三个作用:

static SavedStateHandleController create(SavedStateRegistry registry, Lifecycle lifecycle,
        String key, Bundle defaultArgs) {
    // 1.通过key获取到先前保存起来的数据 得到bundle对象
    Bundle restoredState = registry.consumeRestoredStateForKey(key);
    // 2.传递restoredState,创建SavedStateHandle
    SavedStateHandle handle = SavedStateHandle.createHandle(restoredState, defaultArgs);
    // 3.通过key和handle创建SavedStateHandleController对象
    SavedStateHandleController controller = new SavedStateHandleController(key, handle);
    // 4.添加生命周期监听,向SavedStateRegistry数据存中心注册一个SavedStateProvider
    controller.attachToLifecycle(registry, lifecycle);
    tryToAddRecreator(registry, lifecycle);
    return controller;
}

在 registry 中获取先前保存的数据,通过 key(ViewModel的名称)得到一个 bundle 对象,接着创建一个 SavedStateHandle 对象,并且把 bundle 数据传递了进去,还会调用controller.attachToLifecycle(registry, lifecycle):

void attachToLifecycle(SavedStateRegistry registry, Lifecycle lifecycle) {
    mIsAttached = true;
    lifecycle.addObserver(this);
    //注册一个 SavedStateProvider,用于实现数据存储的工作
    registry.registerSavedStateProvider(mKey, mHandle.savedStateProvider());
}

向 SavedStateRegistry 数据存中心注册一个 SavedStateProvider,用于实现数据存储的工作,那么 SavedStateHandle 被创建完之后,之前存储的数据就被恢复了,然后传递到了 SavedStateViewModelFactory 中的 controller.getHandle() 然后完成了数据的复用。

SavedState数据复用流程小结

创建 ViewModel 并传递恢复的 SavedStateHandle:

在这里插入图片描述

这个数据的复用它发生在 ViewModel 的创建,要复用之前的数据,它就需要通过 SavedStateViewModelFactory 完成实例的创建,因为它实例化这个 ViewModel 的时候他会从 SaveStateRegistry 当中查询这个 ViewModel 之前所存储的 Bundle 数据,并且创建一个 SaveStateHandel,在创建 SaveStateHandel 的时候就会把 Bundle 传递进去,然后传递到 ViewModel 的构造函数里面,从而完成数据的复用工作。

四、总结

  1. SavedState 本质是利用了 onSaveIntanceState 的时机,每个 ViewModel 的数据单独存储在一个 Bundle,逐一调用每个 SavedStateHandle 保存自己的数据,汇总成一个总 Bundle,存放在 Activity 的 outBundle 中。

  2. 从 Activity 的 onCreate() 方法中 savedState 恢复所有 ViewModel 的数据到 SaveStateRegistry,并保存数据。

  3. 通过 SavedStateViewModelFactory 创建 ViewModel,并根据存储的 Bundle 数据创建 SaveStateHand,然后传递到 ViewModel 的构造函数里面,从而完成数据的复用工作。

这就是 SavedState 实现数据复用的原理。

一个大型的 Android 项目架构最佳实践,基于Jetpack组件 + MVVM架构模式,加入 组件化模块化协程Flow短视频。项目地址:https://github.com/suming77/SumTea_Android

点关注,不迷路


好了各位,以上就是这篇文章的全部内容了,很感谢您阅读这篇文章。我是suming,感谢各位的支持和认可,您的点赞就是我创作的最大动力。山水有相逢,我们下篇文章见!

本人水平有限,文章难免会有错误,请批评指正,不胜感激 !

参考链接:

  • ViewModel官网

希望我们能成为朋友,在 Github博客 上一起分享知识,一起共勉!Keep Moving!

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

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

相关文章

ModaHub AI模型开源社区:向量数据库Milvus向量索引是什么?

目录 向量索引 索引创建机制 数据段建索引 用户主动创建索引 闲时建索引 索引概览 FLAT IVF_FLAT IVF_SQ8 IVF_SQ8H IVF_PQ RNSG HNSW ANNOY 选择索引 常见问题 参考文献 向量索引 向量索引&#xff08;vector index&#xff09;是指通过某种数学模型&#xf…

软路由系统 --- OpenWrt网络配置(LAN口、WAN口)

这里使用的 OpenWrt 是一台虚拟机安装的. 配置LAN口IP等信息 编辑修改网络配置文件 rootOpenWrt:~# vi /etc/config/network config interface lanoption type bridgeoption ifname eth0option proto staticoption ipaddr 192.168.100.4option netmask 255.255.255.0option i…

Servlet(下篇)

哥几个来学 Servlet 啦 ~~ 这个是 Servlet&#xff08;上篇&#xff09;的链接&#xff0c; (2条消息) Servlet &#xff08;上篇&#xff09;_小枫 ~的博客-CSDN博客https://blog.csdn.net/m0_64247824/article/details/131229873主要讲了 Servlet的定义、Servlet的部署方式、…

【总结】网页状态码——200正常、302重定向、304客户端有缓存、400浏览器请求传参异常、404未找到、405方法不允许、500服务器异常

目录 200正常500异常--服务器异常Java代码400异常----传参相关的异常get方法长度限制400异常&#xff0c;加了RequestParam(value "name") 必须传值400异常&#xff0c;后端类型是Integer&#xff0c;前端传的是string&#xff0c;转换失败400异常&#xff0c;日期格…

一文带你学习“国密算法”

博主介绍&#xff1a; ✌博主从事应用安全和大数据领域&#xff0c;有8年研发经验&#xff0c;5年面试官经验&#xff0c;Java技术专家✌ Java知识图谱点击链接&#xff1a;体系化学习Java&#xff08;Java面试专题&#xff09; &#x1f495;&#x1f495; 感兴趣的同学可以收…

ES6--一

1、ES6之includes()方法 Array.prototype.includes方法返回一个布尔值&#xff0c;表示某个数组是否包含给定的值&#xff0c;与字符串的includes方法类似。 一般常用这种写法 : [1,2,3].includes(2) 表示2在不在 前面的数组里 项目里 参数 1&#xff09;第一个参数是要…

VUE L 计算监视属性 ⑥

目录 文章有误请指正&#xff0c;如果觉得对你有用&#xff0c;请点三连一波&#xff0c;蟹蟹支持✨ V u e j s Vuejs Vuejs计算属性 C o m p u t e d Computed Computed监视属性 W a t c h Watch Watch总结 文章有误请指正&#xff0c;如果觉得对你有用&#xff0c;…

SpringBoot 实现 elasticsearch 索引操作(RestHighLevelClient 的应用)

文章目录 0. 引入依赖1. 实例创建与关闭2. 创建索引3. 测试索引库存在不存在4. 删除索引库5. 遍历导入数据6. 批量导入数据&#xff08;推荐&#xff09; RestHighLevelClient 是 Elasticsearch 官方提供的Java高级客户端&#xff0c;用于与Elasticsearch集群进行交互和执行各种…

基于Java+Swing实现中国象棋游戏

基于JavaSwing实现中国象棋游戏 一、系统介绍二、功能展示三、其他系统四、获取源码 前言 中国象棋是起源于中国的一种棋&#xff0c;属于二人对抗性游戏的一种&#xff0c;在中国有着悠久的历史。由于用具简单&#xff0c;趣味性强&#xff0c;成为流行极为广泛的棋艺活动。 …

操作系统之死锁详解

本文已收录于专栏 《自考》 目录 背景介绍死锁的前提死锁的概念死锁的分类死锁的产生原因条件 死锁的解决预防避免检测与恢复 死锁的实现总结提升 背景介绍 最近一直在做操作系统的测试题&#xff0c;在做题的过程中发现有很多地方涉及到了关于死锁的知识点。今天就回归课本来自…

机器学习——手写数据集的介绍以及案例讲解

系列文章目录 机器学习聚类——DBSCAN&#xff08;Density-based spatial clustering of applications with noise&#xff0c;基于密度的聚类算法&#xff09; 机器学习集成学习——Adaboost分离器算法 机器学习聚类算法——BIRCH算法、DBSCAN算法、OPTICS算法 机器学习的…

“交通·未来”第27期:基于随机效应机器学习的多区域居民出行模式选择分析...

2020年6月份&#xff0c;公众号正式推出了“交通未来”系列线上公益学术活动等你来~&#xff0c; 2023年&#xff0c;新起航新征程&#xff0c;我们继续前行~ 6月24日下午15:30&#xff0c;我们将迎来活动的第27期。 1、讲座主题 基于随机效应机器学习的多区域居民出行模式选择…

工作三--知识点

1、split 切割字符串 2、includes 而不是写成 if&#xff08;useContext pm_global_teamrole_project_manager || pm_global_teamrole_task_manager || pm_global_teamrole_task_parent_manager&#xff09; 因为 的 优先级 高于 ||&#xff0c;这样写 只能 前面的&…

大数据学习(3)

大数据学习&#xff08;3&#xff09; 1 Hive-SQL-DML语句1.1 Hive SQL Load 加载数据语句1.1.1 Load功能1.1.2 Load语法规则1.1.3 Load 语法实验1.1.3.1 Load Data From Local FS1.1.3.2 Load Data From HDFS1.1.3.3 Overwrite选项 1.2 Hive SQL Insert 插入数据语句1.3 Hive …

河道垃圾自动识别监测算法 opencv

河道垃圾自动识别监测系统通过pythonopencv网络模型技术&#xff0c;河道垃圾自动识别监测算法对水面上的垃圾进行自动识别&#xff0c;一旦发现垃圾污染将自动发出警报。OpenCV基于C实现&#xff0c;同时提供python, Ruby, Matlab等语言的接口。OpenCV-Python是OpenCV的Python…

Win10文件夹选项在哪里打开?Win10文件夹选项打开方法

Win10文件夹选项在哪里打开&#xff1f;Win10电脑中用户不知道在哪里才能打开文件夹选项&#xff0c;这时候用户随意打开Win10电脑上的一个文件夹&#xff0c;然后点击右上角的选项&#xff0c;打开之后就能打开文件夹选项了&#xff0c;也可以打开电脑的运行窗口&#xff0c;在…

自动化漏洞猎人代码分析

0x00 前言 安全人员可以扫描&#xff0c;网络上悬赏网站等的漏洞&#xff0c;如果能够发现其存在着安全漏洞&#xff0c;则可以通过提交漏洞的方式来获得一定的赏金&#xff0c;国外的这类悬赏的网站比较多&#xff0c;比如hackone&#xff0c;这上面列出了大量的资产信息&…

你不得不知道的箭头函数和普通函数使用区别!

前言 箭头函数是 ES6 新增的一种函数类型&#xff0c;它采用箭头 > 定义函数&#xff0c;也称为 lambda 函数。箭头函数语法更为简洁&#xff0c;用起来很是方便顺手。 但它存在一些需要注意的问题和局限性。在实际使用时&#xff0c;我们需要根据具体情况来选择合适的函数…

【夜深人静学数据结构与算法 | 第九篇】栈与队列

目录 ​前言&#xff1a; 栈&#xff1a; 栈的实际应用&#xff1a; 队列&#xff1a; 队列的实际应用&#xff1a; 总结&#xff1a; 前言&#xff1a; 栈与队列是我们学习的两个经典的数据结构&#xff0c;这两个数据结构应用广泛&#xff0c;在计算机内有很多底层应用…

操作系统2——进程的描述与控制

本系列博客重点在深圳大学操作系统课程的核心内容梳理&#xff0c;参考书目《计算机操作系统》&#xff08;有问题欢迎在评论区讨论指出&#xff0c;或直接私信联系我&#xff09;。 梗概 本篇博客主要介绍操作系统第二章进程的描述与控制的相关知识。 目录 一、前驱图与程序…