安卓 sharedpreferences可以被其它activity读取_Google|再见 SharedPreferences 拥抱 Jetpack DataStore...

e6ded48003ec3a9cd4102f6abfe7ccf1.png

Google 新增加了一个新 Jetpack 的成员 DataStore,主要用来替换 SharedPreferences, DataStore 应该是开发者期待已久的库,DataStore 是基于 Flow 实现的,一种新的数据存储方案,它提供了两种实现方式:

  • Proto DataStore:存储类的对象(typed objects ),通过 protocol buffers 将对象序列化存储在本地,protocol buffers 现在已经应用的非常广泛,无论是微信还是阿里等等大厂都在使用,我们在部分业务场景中也用到了 protocol buffers,会在后续的文章详细分析
  • Preferences DataStore:以键值对的形式存储在本地和 SharedPreferences 类似,但是 DataStore 是基于 Flow 实现的,不会阻塞主线程,并且保证类型安全

Jetpack DataStore 将会分为至少 2 篇文章来分析,今天这篇文章主要来介绍 Jetpack DataStore 其中一种实现方式 Preferences DataStore,文章中的示例代码,已经上传到 GitHub 欢迎前去查看 AndroidX-Jetpack-Practice/DataStoreSimple

GitHub 地址:https://github.com/hi-dhl/AndroidX-Jetpack-Practice

这篇文章会涉及到 Koltin flow 相关内容,如果不了解可以先去看另外一篇文章 Kotlin Flow 是什么?Channel 是什么?

通过这篇文章你将学习到以下内容:

  • 那些年我们所经历的 SharedPreferences 坑?
  • 为什么需要 DataStore?它为我们解决了什么问题?
  • 如何在项目中使用 DataStore?
  • 如何迁移 SharedPreferences 到 DataStore?
  • MMKV、DataStore、SharedPreferences 的不同之处?

一个新库的出现必定为我们解决了一些问题,那么 Jetpack DataStore 为我们解决什么问题呢,在分析之前,我们需要先来了解 SharedPreferences 都有那些坑。

那些年我们所经历的 SharedPreferences 坑

SharedPreference 是一个轻量级的数据存储方式,使用起来也非常方便,以键值对的形式存储在本地,初始化 SharedPreference 的时候,会将整个文件内容加载内存中,因此会带来以下问题:

  • 通过 getXXX() 方法获取数据,可能会导致主线程阻塞
  • SharedPreference 不能保证类型安全
  • SharedPreference 加载的数据会一直留在内存中,浪费内存
  • apply() 方法虽然是异步的,可能会发生 ANR,在 8.0 之前和 8.0 之后实现各不相同
  • apply() 方法无法获取到操作成功或者失败的结果

接下来我们逐个来分析一下 SharedPreferences 带来的这些问题,在文章中 SharedPreference 简称 SP。

getXXX() 方法可能会导致主线程阻塞

所有 getXXX() 方法都是同步的,在主线程调用 get 方法,必须等待 SP 加载完毕,会导致主线程阻塞,下面的代码,我相信小伙伴们并不陌生。

val sp = getSharedPreferences("ByteCode", Context.MODE_PRIVATE) // 异步加载 SP 文件内容
sp.getString("jetpack", ""); // 等待 SP 加载完毕

调用 getSharedPreferences() 方法,最终会调用 SharedPreferencesImpl#startLoadFromDisk() 方法开启一个线程异步读取数据。 frameworks/base/core/java/android/app/SharedPreferencesImpl.java

private final Object mLock = new Object();
private boolean mLoaded = false;
private void startLoadFromDisk() {synchronized (mLock) {mLoaded = false;}new Thread("SharedPreferencesImpl-load") {public void run() {loadFromDisk();}}.start();
}

正如你所看到的,开启一个线程异步读取数据,当我们正在读取一个比较大的数据,还没读取完,接着调用 getXXX() 方法。

public String getString(String key, @Nullable String defValue) {synchronized (mLock) {awaitLoadedLocked();String v = (String)mMap.get(key);return v != null ? v : defValue;}
}private void awaitLoadedLocked() {......while (!mLoaded) {try {mLock.wait();} catch (InterruptedException unused) {}}......
}

在同步方法内调用了 wait() 方法,会一直等待 getSharedPreferences() 方法开启的线程读取完数据才能继续往下执行,如果读取几 KB 的数据还好,假设读取一个大的文件,势必会造成主线程阻塞。

SP 不能保证类型安全

调用 getXXX() 方法的时候,可能会出现 ClassCastException 异常,因为使用相同的 key 进行操作的时候,putXXX 方法可以使用不同类型的数据覆盖掉相同的 key。

val key = "jetpack"
val sp = getSharedPreferences("ByteCode", Context.MODE_PRIVATE) // 异步加载 SP 文件内容sp.edit { putInt(key, 0) } // 使用 Int 类型的数据覆盖相同的 key
sp.getString(key, ""); // 使用相同的 key 读取 Sting 类型的数据

使用 Int 类型的数据覆盖掉相同的 key,然后使用相同的 key 读取 Sting 类型的数据,编译正常,但是运行会出现以下异常。

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

SP 加载的数据会一直留在内存中

通过 getSharedPreferences() 方法加载的数据,最后会将数据存储在静态的成员变量中。

// 调用 getSharedPreferences 方法,最后会调用 getSharedPreferencesCacheLocked 方法
public SharedPreferences getSharedPreferences(File file, int mode) {......final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();return sp;
}// 通过静态的 ArrayMap 缓存 SP 加载的数据
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;// 将数据保存在 sSharedPrefsCache 中
private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {......ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);if (packagePrefs == null) {packagePrefs = new ArrayMap<>();sSharedPrefsCache.put(packageName, packagePrefs);}return packagePrefs;
}

通过静态的 ArrayMap 缓存每一个 SP 文件,而每个 SP 文件内容通过 Map 缓存键值对数据,这样数据会一直留在内存中,浪费内存。

apply() 方法是异步的,可能会发生 ANR

apply() 方法是异步的,为什么还会造成 ANR 呢?曾今的字节跳动就出现过这个问题,具体详情可以点击这里前去查看 剖析 SharedPreference apply 引起的 ANR 问题 而且 Google 也明确指出了 apply() 的问题。

a2f70a0cd245f557260142016a3a08ff.png

简单总结一下:apply() 方法是异步的,本身是不会有任何问题,但是当生命周期处于 handleStopService()handlePauseActivity()handleStopActivity() 的时候会一直等待 apply() 方法将数据保存成功,否则会一直等待,从而阻塞主线程造成 ANR,一起来分析一下为什么异步方法还会阻塞主线程,先来看看 apply() 方法的实现。 frameworks/base/core/java/android/app/SharedPreferencesImpl.java

public void apply() {final long startTime = System.currentTimeMillis();final MemoryCommitResult mcr = commitToMemory();final Runnable awaitCommit = new Runnable() {@Overridepublic void run() {mcr.writtenToDiskLatch.await(); // 等待......}};// 将 awaitCommit 添加到队列 QueuedWork 中QueuedWork.addFinisher(awaitCommit);Runnable postWriteRunnable = new Runnable() {@Overridepublic void run() {awaitCommit.run();QueuedWork.removeFinisher(awaitCommit);}};// 8.0 之前加入到一个单线程的线程池中执行// 8.0 之后加入 HandlerThread 中执行写入任务SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
}
  • 将一个 awaitCommit 的 Runnable 任务,添加到队列 QueuedWork 中,在 awaitCommit 中会调用 await() 方法等待,在 handleStopServicehandleStopActivity 等等生命周期会以这个作为判断条件,等待任务执行完毕
  • 将一个 postWriteRunnable 的 Runnable 写任务,通过 enqueueDiskWrite 方法,将写入任务加入到队列中,而写入任务在一个线程中执行

注意:在 8.0 之前和 8.0 之后 enqueueDiskWrite() 方法实现逻辑各不相同

在 8.0 之前调用 enqueueDiskWrite() 方法,将写入任务加入到 单个线程的线程池 中执行,如果 apply() 多次的话,任务将会依次执行,效率很低,android-7.0.0_r34 源码如下所示。

// android-7.0.0_r34: frameworks/base/core/java/android/app/SharedPreferencesImpl.java
private void enqueueDiskWrite(final MemoryCommitResult mcr,final Runnable postWriteRunnable) {......QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
}// android-7.0.0_r34: frameworks/base/core/java/android/app/QueuedWork.java
public static ExecutorService singleThreadExecutor() {synchronized (QueuedWork.class) {if (sSingleThreadExecutor == null) {sSingleThreadExecutor = Executors.newSingleThreadExecutor();}return sSingleThreadExecutor;}
}

通过 Executors.newSingleThreadExecutor() 方法创建了一个 单个线程的线程池,因此任务是串行的,通过 apply() 方法创建的任务,都会添加到这个线程池内。

在 8.0 之后将写入任务加入到 LinkedList 链表中,在 HandlerThread 中执行写入任务,android-10.0.0_r14 源码如下所示。

// android-10.0.0_r14: frameworks/base/core/java/android/app/SharedPreferencesImpl.java
private void enqueueDiskWrite(final MemoryCommitResult mcr,final Runnable postWriteRunnable) {......QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}// android-10.0.0_r14: frameworks/base/core/java/android/app/QueuedWork.javaprivate static final LinkedList<Runnable> sWork = new LinkedList<>();public static void queue(Runnable work, boolean shouldDelay) {Handler handler = getHandler(); // 获取 handlerThread.getLooper() 生成 Handler 对象synchronized (sLock) {sWork.add(work); // 将写入任务加入到 LinkedList 链表中if (shouldDelay && sCanDelay) {handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);} else {handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);}}
}

在 8.0 之后通过调用 handlerThread.getLooper() 方法生成 Handler,任务都会在 HandlerThread 中执行,所有通过 apply() 方法创建的任务,都会添加到 LinkedList 链表中。

当生命周期处于 handleStopService()handlePauseActivity()handleStopActivity() 的时候会调用 QueuedWork.waitToFinish() 会等待写入任务执行完毕,我们以其中 handlePauseActivity() 方法为例。

public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,int configChanges, PendingTransactionActions pendingActions, String reason) {......// 确保写任务都已经完成QueuedWork.waitToFinish();......}
}

正如你所看到的在 handlePauseActivity() 方法中,调用了 QueuedWork.waitToFinish() 方法,会等待所有的写入执行完毕,Google 在 8.0 之后对这个方法做了很大的优化,一起来看一下 8.0 之前和 8.0 之后的区别。

注意:在 8.0 之前和 8.0 之后 waitToFinish() 方法实现逻辑各不相同

在 8.0 之前 waitToFinish() 方法只做了一件事,会一直等待写入任务执行完毕,我先来看看在 android-7.0.0_r34 源码实现。

android-7.0.0_r34: frameworks/base/core/java/android/app/QueuedWork.java

private static final ConcurrentLinkedQueue<Runnable> sPendingWorkFinishers =new ConcurrentLinkedQueue<Runnable>();public static void waitToFinish() {Runnable toFinish;while ((toFinish = sPendingWorkFinishers.poll()) != null) {toFinish.run(); // 相当于调用 `mcr.writtenToDiskLatch.await()` 方法}
}
  • sPendingWorkFinishers 是 ConcurrentLinkedQueue 实例,apply 方法会将写入任务添加到 sPendingWorkFinishers 队列中,在 单个线程的线程池 中执行写入任务,线程的调度并不由程序来控制,也就是说当生命周期切换的时候,任务不一定处于执行状态
  • toFinish.run() 方法,相当于调用 mcr.writtenToDiskLatch.await() 方法,会一直等待
  • waitToFinish() 方法就做了一件事,会一直等待写入任务执行完毕,其它什么都不做,当有很多写入任务,会依次执行,当文件很大时,效率很低,造成 ANR 就不奇怪了,尤其像字节跳动这种大规模的 App

在 8.0 之后 waitToFinish() 方法做了很大的优化,当生命周期切换的时候,会主动触发任务的执行,而不是一直在等着,我们来看看 android-10.0.0_r14 源码实现。

android-10.0.0_r14: frameworks/base/core/java/android/app/QueuedWork.java

private static final LinkedList<Runnable> sFinishers = new LinkedList<>();
public static void waitToFinish() {......try {processPendingWork(); // 主动触发任务的执行} finally {StrictMode.setThreadPolicy(oldPolicy);}try {// 等待任务执行完毕while (true) {Runnable finisher;synchronized (sLock) {finisher = sFinishers.poll(); // 从 LinkedList 中取出任务}if (finisher == null) { // 当 LinkedList 中没有任务时会跳出循环break;}finisher.run(); // 相当于调用 `mcr.writtenToDiskLatch.await()`}} ......
}

waitToFinish() 方法中会主动调用 processPendingWork() 方法触发任务的执行,在 HandlerThread 中执行写入任务。

另外还做了一个很重要的优化,当调用 apply() 方法的时候,执行磁盘写入,都是全量写入,在 8.0 之前,调用 N 次 apply() 方法,就会执行 N 次磁盘写入,在 8.0 之后,apply() 方法调用了多次,只会执行最后一次写入,通过版本号来控制的。

SharedPreferences 的另外一个缺点就是 apply() 方法无法获取到操作成功或者失败的结果,而 commit() 方法是可以接收 MemoryCommitResult 里面的一个 boolean 参数作为结果,来看一下它们的方法签名。

public void apply() { ... }public boolean commit() { ... }

SP 不能用于跨进程通信

我们在创建 SP 实例的时候,需要传入一个 mode,如下所示:

val sp = getSharedPreferences("ByteCode", Context.MODE_PRIVATE) 

Context 内部还有一个 modeMODE_MULTI_PROCESS,我们来看一下这个 mode 做了什么

public SharedPreferences getSharedPreferences(File file, int mode) {if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {// 重新读取 SP 文件内容sp.startReloadIfChangedUnexpectedly();}return sp;
}

在这里就做了一件事,当遇到 MODE_MULTI_PROCESS 的时候,会重新读取 SP 文件内容,并不能用 SP 来做跨进程通信。

到这里关于 SharedPreferences 部分分析完了,接下来分析一下 DataStore 为我们解决什么问题?

DataStore 解决了什么问题

Preferences DataStore 主要用来替换 SharedPreferences,Preferences DataStore 解决了 SharedPreferences 带来的所有问题

Preferences DataStore 相比于 SharedPreferences 优点

  • DataStore 是基于 Flow 实现的,所以保证了在主线程的安全性
  • 以事务方式处理更新数据,事务有四大特性(原子性、一致性、 隔离性、持久性)
  • 没有 apply()commit() 等等数据持久的方法
  • 自动完成 SharedPreferences 迁移到 DataStore,保证数据一致性,不会造成数据损坏
  • 可以监听到操作成功或者失败结果

另外 Jetpack DataStore 提供了 Proto DataStore 方式,用于存储类的对象(typed objects ),通过 protocol buffers 将对象序列化存储在本地,protocol buffers 现在已经应用的非常广泛,无论是微信还是阿里等等大厂都在使用,我们在部分场景中也使用了 protocol buffers,在后续的文章会详细的分析。

注意:

Preferences DataStore 只支持 Int , Long , Boolean , Float , String 键值对数据,适合存储简单、小型的数据,并且不支持局部更新,如果修改了其中一个值,整个文件内容将会被重新序列化,可以运行 AndroidX-Jetpack-Practice/DataStoreSimple 体验一下,如果需要局部更新,建议使用 Room。

在项目中使用 Preferences DataStore

Preferences DataStore 主要应用在 MVVM 当中的 Repository 层,在项目中使用 Preferences DataStore 非常简单,只需要 4 步。

1. 需要添加 Preferences DataStore 依赖

implementation "androidx.datastore:datastore-preferences:1.0.0-alpha01"

2. 构建 DataStore

private val PREFERENCE_NAME = "DataStore"
var dataStore: DataStore<Preferences> = context.createDataStore(name = PREFERENCE_NAME

3. 从 Preferences DataStore 中读取数据

Preferences DataStore 以键值对的形式存储在本地,所以首先我们应该定义一个 Key.

val KEY_BYTE_CODE = preferencesKey<Boolean>("ByteCode")

这里和我们之前使用 SharedPreferences 的有点不一样,在 Preferences DataStore 中 Key 是一个 Preferences.Key<T> 类型,只支持 Int , Long , Boolean , Float , String,源码如下所示:

inline fun <reified T : Any> preferencesKey(name: String): Preferences.Key<T> {return when (T::class) {Int::class -> {Preferences.Key<T>(name)}String::class -> {Preferences.Key<T>(name)}Boolean::class -> {Preferences.Key<T>(name)}Float::class -> {Preferences.Key<T>(name)}Long::class -> {Preferences.Key<T>(name)}...... // 如果是其他类型就会抛出异常}
}

当我们定义好 Key 之后,就可以通过 dataStore.data 来获取数据

override fun readData(key: Preferences.Key<Boolean>): Flow<Boolean> =dataStore.data.catch {// 当读取数据遇到错误时,如果是 `IOException` 异常,发送一个 emptyPreferences 来重新使用// 但是如果是其他的异常,最好将它抛出去,不要隐藏问题if (it is IOException) {it.printStackTrace()emit(emptyPreferences())} else {throw it}}.map { preferences ->preferences[key] ?: false}
  • Preferences DataStore 是基于 Flow 实现的,所以通过 dataStore.data 会返回一个 Flow<T>,每当数据变化的时候都会重新发出
  • catch 用来捕获异常,当读取数据出现异常时会抛出一个异常,如果是 IOException 异常,会发送一个 emptyPreferences() 来重新使用,如果是其他异常,最好将它抛出去

4. 向 Preferences DataStore 中写入数据

在 Preferences DataStore 中是通过 DataStore.edit() 写入数据的,DataStore.edit() 是一个 suspend 函数,所以只能在协程体内使用,每当遇到 suspend 函数以挂起的方式运行,并不会阻塞主线程。

以挂起的方式运行,不会阻塞主线程 :也就是协程作用域被挂起, 当前线程中协程作用域之外的代码不会阻塞。

首先我们需要创建一个 suspend 函数,然后调用 DataStore.edit() 写入数据即可。

override suspend fun saveData(key: Preferences.Key<Boolean>) {dataStore.edit { mutablePreferences ->val value = mutablePreferences[key] ?: falsemutablePreferences[key] = !value}
}

到这里关于 Preferences DataStore 读取数据和写入数据就已经分析完了,接下来分析一下如何迁移 SharedPreferences 到 DataStore

迁移 SharedPreferences 到 DataStore

迁移 SharedPreferences 到 DataStore 只需要 2 步。

  • 在构建 DataStore 的时候,需要传入一个 SharedPreferencesMigration
dataStore = context.createDataStore(name = PREFERENCE_NAME,migrations = listOf(SharedPreferencesMigration(context,SharedPreferencesRepository.PREFERENCE_NAME))
)
  • 当 DataStore 对象构建完了之后,需要执行一次读取或者写入操作,即可完成 SharedPreferences 迁移到 DataStore,当迁移成功之后,会自动删除 SharedPreferences 使用的文件

a5291c5e154e4eb7990db2815d436aae.png

注意: 只从 SharedPreferences 迁移一次,因此一旦迁移成功之后,应该停止使用 SharedPreferences。

相比于 MMKV 有什么不同之处

最后用一张表格来对比一下 MMKV、DataStore、SharedPreferences 的不同之处,如果发现错误,或者有其他不同之处,期待你来一起完善。

a6855929c31276f3fdf405194af72294.png

另外在附上一张 Google 分析的 SharedPreferences 和 DataStore 的区别

26c8914dedf6e399a4db89c9240db0ad.png

全文到这里就结束了,这篇文章主要分析了 SharedPreferences 和 DataStore 的优缺点,以及为什么需要引入 DataStore 和如何使用 DataStore,为了节省篇幅源码分析部分会在后续的文章中分析。

关于 SharedPreferences 和 DataStore 相关的代码,已经上传到了 GitHub 欢迎前去查看 AndroidX-Jetpack-Practice/DataStoreSimple ,可以运行一下示例项目,体验一下 SharedPreferences 和 DataStore 效果。

  • GitHub 地址:https://github.com/hi-dhl/AndroidX-Jetpack-Practice

参考文献

  • Preferences DataStore codelab
  • Now in Android #25
  • Prefer Storing Data with Jetpack DataStore
  • 剖析 SharedPreference 引起的 ANR 问题
  • SharedPreferences 问题分析和解决

结语

我梳理了 LeetCode / 剑指 offer 及国内外大厂面试题解,截止到目前为止我已经在 LeetCode 上 AC 了 124+ 题,每题都会用 Java 和 kotlin 去实现,并且每题都有多种解法、解题思路、时间复杂度、空间复杂度分析,题库逐渐完善中,欢迎前去查看。

  • 剑指 offer 及国内外大厂面试题解:在线阅读
  • LeetCode 系列题解:在线阅读

4ac8292693a847e13110fb43e0c11db3.png

最后推荐我一直在更新维护的项目和网站:

  • 计划建立一个最全、最新的 AndroidX Jetpack 相关组件的实战项目 以及 相关组件原理分析文章,正在逐渐增加 Jetpack 新成员,仓库持续更新,欢迎前去查看:
hi-dhl/AndroidX-Jetpack-Practice​github.com
e782010cb9ecb3910c29b7575aae5941.png
  • LeetCode / 剑指 Offer / 国内外大厂面试题,涵盖: 多线程、数组、栈、队列、字符串、链表、树,查找算法、搜索算法、位运算、排序等等,每道题目都会用 Java 和 kotlin 去实现,仓库持续更新,欢迎前去查看
hi-dhl/Leetcode-Solutions-with-Java-And-Kotlin​github.com
e782010cb9ecb3910c29b7575aae5941.png
    • 剑指 offer 及国内外大厂面试题解:在线阅读
    • LeetCode 系列题解:在线阅读
  • 最新 Android 10 源码分析系列文章,了解系统源码,不仅有助于分析问题,在面试过程中,对我们也是非常有帮助的,仓库持续更新,欢迎前去查看 Android10-Source-Analysis
  • 整理和翻译一系列精选国外的技术文章,每篇文章都会有译者思考部分,对原文的更加深入的解读,仓库持续更新,欢迎前去查看
Technical-Article-Translation​github.com
  • 「为互联网人而设计,国内国外名站导航」涵括新闻、体育、生活、娱乐、设计、产品、运营、前端开发、Android 开发等等网址,欢迎前去查看
Hi World | 为互联网人而设计的国内国外名站导航​site.51git.cn

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

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

相关文章

【20171025早】alert(1) to win 练习

本人黑绝楼&#xff0c;自称老黑&#xff0c;男&#xff0c;25岁&#xff0c;曾经在BAT工作过两年&#xff0c;但是一直都是底层人员&#xff0c;整天做重复性工作&#xff0c;甚敢无趣&#xff0c;曾和工作十年之久的同事聊天&#xff0c;发现对方回首过往&#xff0c;生活是寡…

Arduino 与 SPI 结合使用 以及SPI 深层理解

本文主要讲解两部分内容&#xff0c;不做任何转发&#xff0c;仅个人学习记录: 一. Arduino 与 SPI 结合使用 &#xff1a; 二. SPI 深层理解 有价值的几个好的参考&#xff1a; 1. 中文版&#xff1a; https://blog.csdn.net/xxxxxx91116/article/details/42620413 这版本适…

VGG

VGG16网络&#xff0c;卷积核3*3&#xff0c;步长为1&#xff0c;填充&#xff08;padding&#xff09;为1&#xff1b; 池化2*2&#xff0c;步长为2 全连接层 卷积核1*1 Input Layer&#xff1a;224*224*3图像 Conv1-1 Layer&#xff1a;包含64个卷积核&#xff0c;kernal s…

guava 并发同步_Google Guava –与Monitor同步

guava 并发同步Google Guava项目是每个Java开发人员都应该熟悉的库的集合。 Guava库涵盖了I / O&#xff0c;集合&#xff0c;字符串操作和并发性。 在这篇文章中&#xff0c;我将介绍Monitor类。 Monitor是一种同步构造&#xff0c;可以在使用ReentrantLock的任何地方使用。 随…

qt 定时器_Qt开源作品23-颜色拾取器

## 一、前言在做很多项目的UI界面的时候&#xff0c;相信绝大部分人都有过抄袭别人的UI界面尤其是颜色的时候&#xff0c;毕竟十个程序员九个没有审美&#xff0c;或者说审美跟一坨屎一样&#xff0c;大家主要的精力以及擅长点都是在写功能实现具体功能上面&#xff0c;这个事情…

第5章学习小结

第五章主要学习了树的知识&#xff0c;以前一直很好奇&#xff0c;为什么电脑能存储像树一样的数据结构&#xff0c;学完才发现&#xff0c;ADT加数组或者ADT加链表真的可以衍生出多种多样的数据类型&#xff0c;以下做出本章小结&#xff1a; 1.利用ASCII码实现不同类型的数据…

tf.transpose

tf.transpose(input, [dimension_1,dimenaion_2,..,dimension_n]) 这个函数主要适用于交换输入张量的不同维度用的&#xff0c;如果输入张量是二维&#xff0c;就相当是转置。dimension_n是整数&#xff0c;如果张量是三维&#xff0c;就是用0,1,2来表示。这个列表里的每个数对…

【20171025中】alert(1) to win 脚本渲染自建

游戏误人生&#xff0c;一下午玩了将近四个小时的三国杀&#xff0c;后悔不已&#xff0c;然后重新拾起xss challenge&#xff0c;突发奇想&#xff0c;自己构建渲染后的html。 从最简单的开始。 自动检测html: <!DOCTYPE html> <html> <head><meta http-…

使用JPA和@NamedQueries的Spring数据

如果在JPA实体上使用Spring Data和NamedQuery批注&#xff0c;则可以使用spring数据存储库以更方便的方式轻松使用它们。 在先前的博客中&#xff0c;我们使用spring boot和docker 创建了spring数据项目。 我们将使用相同的项目并增强存储库的功能。 我们将实现一个命名查询&…

vs无法写入量的大数据_一个每天服务数万人的企业食堂:自助餐按重计价,大数据支持食材预备量...

钱江晚报小时新闻记者 祝瑶 昨天下午6点多&#xff0c;钱报记者来到杭州阿里巴巴总部。这里共有5个员工食堂&#xff0c;几乎覆盖了全国各地的口味&#xff0c;每天为数万阿里人服务。其中&#xff0c;最当红的是2号食堂&#xff0c;除了有丰俭由人的自助餐区&#xff0c;还有十…

6号板编译失败找不到arm-none-linux-gnueabi-gcc

明明已经添加到/etc/environment 安装sudo apt-get install lib32z1 lib32ncurses5转载于:https://www.cnblogs.com/xpylovely/p/10817240.html

tf.train.Saver

将训练好的模型参数保存起来&#xff0c;以便以后进行验证或测试&#xff0c;这是我们经常要做的事情。tf里面提供模型保存的是tf.train.Saver()模块。 模型保存&#xff0c;先要创建一个Saver对象&#xff1a;如 savertf.train.Saver() 在创建这个Saver对象的时候&#xff…

Neo4j:空值如何工作?

我时不时地发现自己想将CSV文件导入Neo4j&#xff0c;而我总是对如何处理可能潜伏在其中的各种空值感到困惑。 让我们从一个没有CSV文件的示例开始。 考虑以下列表&#xff0c;以及我尝试仅返回空值的尝试&#xff1a; WITH [null, "null", "", "Ma…

楼层钢筋验收会议纪要_钢筋施工质量通病防治

一、钢筋原材1、钢筋表面出现黄色浮锈&#xff0c;严重转为红色&#xff0c;日久后变成暗褐色&#xff0c;甚至发生鱼鳞片剥落现象。图片原因保管不良&#xff0c;受到雨雪侵蚀&#xff0c;存放期长&#xff0c;仓库环境潮湿&#xff0c;通风不良。防 治 措 施1、钢筋原料应存放…

simulink代码生成(一)——环境搭建

一、安装C2000的嵌入式环境&#xff1b; 点击matlab附加功能&#xff0c; 然后搜索C2000&#xff0c;安装嵌入式硬件支持包&#xff1b;点击安装即可&#xff1b;&#xff08;目前还不知道破解版的怎么操作&#xff0c;目前我用的是正版的这样&#xff0c;完全破解的可能操作…

五步法颈椎病自我按摩图解

​​1.揉捏颈、肩、臂 操作&#xff1a;自我按摩时取坐位。拇指张开&#xff0c;其余四指并拢&#xff0c;虎口相对用力&#xff0c;自枕部开始沿颈椎棘突两旁的肌肉向下揉捏&#xff0c;至上背部手能摸到之处为止。反复揉捏3分钟&#xff0c;然后以相同手法揉捏患侧上肢和颈部…

tf.one_hot

tf.one_hot(indices,#输入&#xff0c;这里是一维的depth,# one hotdimension.on_valueNone,#output 默认1off_valueNone,#output 默认0axisNone,#根据我的实验&#xff0c;默认为1dtypeNone,nameNone) 测试程序&#xff0c;一般说&#xff0c;有几类&#xff0c;depth等于分类…

使用get set方法添减属性_头皮银屑病“克星”使用方法,你GET了吗?

相信小伙伴们最近都了解了治疗头皮银屑病需要使用专业剂型。但...方法不对&#xff0c;心血白费。头皮银屑病专用剂型的正确使用方法&#xff0c;你真的知道吗&#xff1f;快来和利奥娜一起&#xff0c;Get√正确的使用方法吧&#xff01;适合头皮银屑病的专用药剂1.复方制剂卡…

spring hsqldb_在Spring中嵌入HSQLDB服务器实例

spring hsqldb我一直在愉快地使用XAMPP进行开发&#xff0c;直到不得不将其托管在可通过Internet访问的某个地方&#xff0c;供客户端进行测试和使用。 我有一个仅具有384 RAM的VPS&#xff0c;并且需要快速找到方法&#xff0c;因此决定将XAMPP安装到VPS中。 由于内存较低&…

线性回归,logistic回归和一般回归

http://www.cnblogs.com/riskyer/p/3217601.html转载于:https://www.cnblogs.com/focus-z/p/10822757.html