Sp效率分析和理解 r囧r小猫 2023-10-09 10:41 6阅读 0赞 #### 目录介绍 #### * 01.Sp简单介绍 * 1.1 Sp作用分析 * 1.2 案例分析思考 * 02.Sp初始化操作 * 2.1 如何获取sp * 2.2 SharedPreferencesImpl构造 * 03.edit方法源码 * 04.put和get方法源码 * 4.1 put方法源码 * 4.2 get方法源码 * 05.commit和apply * 5.1 commit源码 * 5.2 apply源码 * 06.总结分析 ### 好消息 ### * 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇\[近100万字,陆续搬到网上\],转载请注明出处,谢谢! * 链接地址:[https://github.com/yangchong211/YCBlogs][https_github.com_yangchong211_YCBlogs] * 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议,万事起于忽微,量变引起质变! ### 01.Sp简单介绍说明 ### #### 1.1 Sp作用分析 #### * sp作用说明 * SharedPreferences是Android中比较常用的存储方法,它可以用来存储一些比较小的键值对集合,并最终会在手机的/data/data/package\_name/shared\_prefs/目录下生成一个 xml 文件存储数据。 * 分析sp包含那些内容 * 获取SharedPreferences对象过程中,系统做了什么? * getXxx方法做了什么? * putXxx方法做了什么? * commit/apply方法如何实现同步/异步写磁盘? * 分析sp包含那些源码 * SharedPreferences 接口 * SharedPreferencesImpl 实现类 * QueuedWork 类 #### 1.2 案例分析思考 #### ##### 1.2.1 edit用法分析 ##### * 代码如下所示 * 然后开始执行操作 * A操作和B操作,在代码逻辑上应该是一样的,都是想SP中写入200次不同字段的数据,区别只是在于,A操作每次都去获取新的Editor,而B操作是只使用一个Eidtor去存储。两个操作都分别执行两次。 * A操作和C操作,在代码逻辑上应该是一样的,都是想SP中写入200次不同字段的数据,区别只是在于,A操作每次都去获取新的Editor,而C操作是只使用一个Editor去存储,并且只commit一次。两个操作都分别执行两次。 * B和C的操作几乎都是一样的,唯一不同的是B操作只是获取一次preferencesB对象,而C操作则是获取200次preferencesC操作。 * 然后看一下执行结果 * 结果分析 * 通过A和B操作进行比较可知:使用commit()的方式,如果每次都使用sp.edit()方法获取一个新的Editor的话,新建和修改的执行效率差了非常的大。也就是说,存储一个从来没有用过的Key,和修改一个已经存在的Key,在效率上是有差别的。 * 通过B和C操作进行比较可知:getSharedPreferences操作一次和多次其实是没有多大的区别,因为在有缓存,如果存在则从缓存中取。 * 然后看看里面存储值 * 其存储的值并不是按照顺序的。 ##### 1.2.2 commit和apply ##### * 代码如下所示 * 然后看一下执行结果 * 得出结论 * 从执行结果可以发现,使用apply因为是异步操作,基本上是不耗费时间的,效率上都是OK的。从这个结论上来看,apply影响效率的地方,在sp.edit()方法。 * 可以看出多次执行edit方法还是很影响效率的。 * 在edit()中是有synchronized这个同步锁来保证线程安全的,纵观EditorImpl.java的实现,可以看到大部分操作都是有同步锁的,但是只锁了(this),也就是只对当前对象有效,而edit()方法是每次都会去重新new一个EditorImpl()这个Eidtor接口的实现类。所以效率就应该是被这里影响到了。 ##### 1.2.3 给出的建议 ##### * edit()是有效率影响的,所以不要在循环中去调用吃方法,最好将edit()方法获取的Editor对象方在循环之外,在循环中共用同一个Editor()对象进行操作。 * commit()的时候,「new-key」和「update-key」的效率是有差别的,但是有返回结果。 * apply()是异步操作,对效率的影响,基本上是ms级的,可以忽略不记。 ### 02.Sp初始化操作 ### #### 2.1 如何获取sp #### * 首先看ContextWrapper源码 * 然后看一下ContextImpl类 * 然后接着看一下getSharedPreferences(file, mode)方法源码 * 这段源码的流程还是清晰易懂的,注释已经说得很明白,这里我们总结一下这个方法的要点: * 缓存未命中, 才构造SharedPreferences对象,也就是说,多次调用getSharedPreferences方法并不会对性能造成多大影响,因为又缓存机制。 * SharedPreferences对象的创建过程是线程安全的,因为使用了synchronize关键字。 * 如果命中了缓存,并且参数mode使用了Context.MODE\_MULTI\_PROCESS,那么将会调用sp.startReloadIfChangedUnexpectedly()方法,在startReloadIfChangedUnexpectedly方法中,会判断是否由其他进程修改过这个文件,如果有,会重新从磁盘中读取文件加载数据。 #### 2.2 SharedPreferencesImpl构造 #### * 看SharedPreferencesImpl的构造方法,源码如下所示 * 将传进来的参数file以及mode分别保存在mFile以及mMode中 * 创建一个.bak备份文件,当用户写入失败的时候会根据这个备份文件进行恢复工作 * 将存放键值对的mMap初始化为null * 调用startLoadFromDisk()方法加载数据 * 然后看一下调用startLoadFromDisk()方法加载数据 * 对startLoadFromDisk()方法进行了分析,有分析我们可以得到以下几点总结: * 如果有备份文件,直接使用备份文件进行回滚 * 第一次调用getSharedPreferences方法的时候,会从磁盘中加载数据,而数据的加载时通过开启一个子线程调用loadFromDisk方法进行异步读取的 * 将解析得到的键值对数据保存在mMap中 * 将文件的修改时间戳以及大小分别保存在mStatTimestamp以及mStatSize中(保存这两个值有什么用呢?我们在分析getSharedPreferences方法时说过,如果有其他进程修改了文件,并且mode为MODE\_MULTI\_PROCESS,将会判断重新加载文件。如何判断文件是否被其他进程修改过,没错,根据文件修改时间以及文件大小即可知道) * 调用notifyAll()方法通知唤醒其他等待线程,数据已经加载完毕 ### 03.edit方法源码 ### * 源码方法如下所示 ### 04.put和get方法源码 ### #### 4.1 put方法源码 #### * 就以putString为例分析源码。通过sharedPreferences.edit()方法返回的SharedPreferences.Editor,所有我们对SharedPreferences的写操作都是基于这个Editor类的。在 Android 系统中,Editor是一个接口类,它的具体实现类是EditorImpl: * 从EditorImpl类的源码我们可以得出以下总结: * SharedPreferences的写操作是线程安全的,因为使用了synchronize关键字 * 对键值对数据的增删记录保存在mModified中,而并不是直接对SharedPreferences.mMap进行操作(mModified会在commit/apply方法中起到同步内存SharedPreferences.mMap以及磁盘数据的作用) #### 4.2 get方法源码 #### * 就以getString为例分析源码 * getString方法代码很简单,其他的例如getInt,getFloat方法也是一样的原理,直接对这个疑问进行总结: * getXxx方法是线程安全的,因为使用了synchronize关键字 * getXxx方法是直接操作内存的,直接从内存中的mMap中根据传入的key读取value * getXxx方法有可能会卡在awaitLoadedLocked方法,从而导致线程阻塞等待(什么时候会出现这种阻塞现象呢?前面我们分析过,第一次调用getSharedPreferences方法时,会创建一个线程去异步加载数据,那么假如在调用完getSharedPreferences方法之后立即调用getXxx方法,此时的mLoaded很有可能为false,这就会导致awaiteLoadedLocked方法阻塞等待,直到loadFromDisk方法加载完数据并且调用notifyAll来唤醒所有等待线程) ### 05.commit和apply ### #### 5.1 commit源码 #### * commit()方法分析 * commit()方法的主体结构很清晰简单: * 首先将写操作记录同步到内存的SharedPreferences.mMap中(将mModified同步到mMap) * 然后调用enqueueDiskWrite方法将数据写入到磁盘上 * 同步等待写磁盘操作完成(这就是为什么commit()方法会同步阻塞等待的原因) * 通知监听者(可以通过registerOnSharedPreferenceChangeListener方法注册监听) * 最后返回执行结果:true or false * 接着来看一下它调用的commitToMemory()方法: * commitToMemory()方法主要做了这几件事: * mDiskWritesInFlight自增1(mDiskWritesInFlight代表“此时需要将数据写入磁盘,但还未处理或未处理完成的次数”,提示,整个SharedPreferences的源码中,唯独在commitToMemory()方法中“有且仅有”一处代码会对mDiskWritesInFlight进行增加,其他地方都是减) * 将mcr.mapToWriteToDisk指向mMap,mcr.mapToWriteToDisk就是最终需要写入磁盘的数据 * 判断mClear的值,如果是true,清空mMap(调用clear()方法,会设置mClear为true) * 同步mModified数据到mMap中,然后清空mModified最后返回一个MemoryCommitResult对象,这个对象的mapToWriteToDisk参数指向了最终需要写入磁盘的mMap * 对调用的enqueueDiskWrite方法进行分析: * writeToFile这个方法大致分为三个过程: * 先把已存在的老的 SP 文件重命名(加“.bak”后缀),然后删除老的 SP 文件,这相当于做了备份(灾备) * 向mFile中一次性写入所有键值对数据,即mcr.mapToWriteToDisk(这就是commitToMemory所说的保存了所有键值对数据的字段) 一次性写入到磁盘。 * 如果写入成功则删除备份(灾备)文件,同时记录了这次同步的时间如果往磁盘写入数据失败,则删除这个半成品的 SP 文件 #### 5.2 apply源码 #### * apply()方法分析 * 总结一下apply()方法: * commitToMemory()方法将mModified中记录的写操作同步回写到内存 SharedPreferences.mMap 中。此时, 任何的getXxx方法都可以获取到最新数据了 * 通过enqueueDiskWrite方法调用writeToFile将方法将所有数据异步写入到磁盘中 ### 06.总结分析 ### * SharedPreferences是线程安全的,它的内部实现使用了大量synchronized关键字 * SharedPreferences不是进程安全的 * 第一次调用getSharedPreferences会加载磁盘 xml 文件(这个加载过程是异步的,通过new Thread来执行,所以并不会在构造SharedPreferences的时候阻塞线程,但是会阻塞getXxx/putXxx/remove/clear等调用),但后续调用getSharedPreferences会从内存缓存中获取。如果第一次调用getSharedPreferences时还没从磁盘加载完毕就马上调用getXxx/putXxx,那么getXxx/putXxx操作会阻塞,直到从磁盘加载数据完成后才返回 * 所有的getXxx都是从内存中取的数据,数据来源于SharedPreferences.mMap * apply同步回写(commitToMemory())内存SharedPreferences.mMap,然后把异步回写磁盘的任务放到一个单线程的线程池队列中等待调度。apply不需要等待写入磁盘完成,而是马上返回 * commit同步回写(commitToMemory())内存SharedPreferences.mMap,然后如果mDiskWritesInFlight(此时需要将数据写入磁盘,但还未处理或未处理完成的次数)的值等于1,那么直接在调用commit的线程执行回写磁盘的操作,否则把异步回写磁盘的任务放到一个单线程的线程池队列中等待调度。commit会阻塞调用线程,知道写入磁盘完成才返回 * MODE\_MULTI\_PROCESS是在每次getSharedPreferences时检查磁盘上配置文件上次修改时间和文件大小,一旦所有修改则会重新从磁盘加载文件,所以并不能保证多进程数据的实时同步 * 从 Android N 开始,,不支持MODE\_WORLD\_READABLE & MODE\_WORLD\_WRITEABLE。一旦指定, 直接抛异常 ### 其他介绍 ### #### 01.关于博客汇总链接 #### * 1.[技术博客汇总][Link 1] * 2.[开源项目汇总][Link 2] * 3.[生活博客汇总][Link 3] * 4.[喜马拉雅音频汇总][Link 4] * 5.[其他汇总][Link 5] #### 02.关于我的博客 #### * github:[https://github.com/yangchong211][https_github.com_yangchong211] * 知乎:[https://www.zhihu.com/people/yczbj/activities][https_www.zhihu.com_people_yczbj_activities] * 简书:[http://www.jianshu.com/u/b7b2c6ed9284][http_www.jianshu.com_u_b7b2c6ed9284] * csdn:[http://my.csdn.net/m0\_37700275][http_my.csdn.net_m0_37700275] * 喜马拉雅听书:[http://www.ximalaya.com/zhubo/71989305/][http_www.ximalaya.com_zhubo_71989305] * 开源中国:[https://my.oschina.net/zbj1618/blog][https_my.oschina.net_zbj1618_blog] * 泡在网上的日子:[http://www.jcodecraeer.com/member/content\_list.php?channelid=1][http_www.jcodecraeer.com_member_content_list.php_channelid_1] * 邮箱:yangchong211@163.com * 阿里云博客:[https://yq.aliyun.com/users/article?spm=5176.100-][https_yq.aliyun.com_users_article_spm_5176.100-] 239.headeruserinfo.3.dT4bcV * segmentfault头条:[https://segmentfault.com/u/xiangjianyu/articles][https_segmentfault.com_u_xiangjianyu_articles] * 掘金:[https://juejin.im/user/5939433efe88c2006afa0c6e][https_juejin.im_user_5939433efe88c2006afa0c6e] 转载于:https://www.cnblogs.com/yc211/p/11435317.html [https_github.com_yangchong211_YCBlogs]: https://github.com/yangchong211/YCBlogs [Link 1]: https://www.jianshu.com/p/614cb839182c [Link 2]: https://blog.csdn.net/m0_37700275/article/details/80863574 [Link 3]: https://blog.csdn.net/m0_37700275/article/details/79832978 [Link 4]: https://www.jianshu.com/p/f665de16d1eb [Link 5]: https://www.jianshu.com/p/53017c3fc75d [https_github.com_yangchong211]: https://github.com/yangchong211 [https_www.zhihu.com_people_yczbj_activities]: https://www.zhihu.com/people/yczbj/activities [http_www.jianshu.com_u_b7b2c6ed9284]: http://www.jianshu.com/u/b7b2c6ed9284 [http_my.csdn.net_m0_37700275]: http://my.csdn.net/m0_37700275 [http_www.ximalaya.com_zhubo_71989305]: http://www.ximalaya.com/zhubo/71989305/ [https_my.oschina.net_zbj1618_blog]: https://my.oschina.net/zbj1618/blog [http_www.jcodecraeer.com_member_content_list.php_channelid_1]: http://www.jcodecraeer.com/member/content_list.php?channelid=1 [https_yq.aliyun.com_users_article_spm_5176.100-]: https://yq.aliyun.com/users/article?spm=5176.100- [https_segmentfault.com_u_xiangjianyu_articles]: https://segmentfault.com/u/xiangjianyu/articles [https_juejin.im_user_5939433efe88c2006afa0c6e]: https://juejin.im/user/5939433efe88c2006afa0c6e
相关 Java垃圾回收机制理解:内存泄漏与GC效率分析 在Java编程中,垃圾回收机制(Garbage Collection, GC)是确保内存资源被有效利用的一个重要机制。下面我们将从内存泄漏和GC效率两方面进行理解。 1. 内 Bertha 。/ 2024年09月20日 05:33/ 0 赞/ 73 阅读
相关 Java字符串操作:效率和理解误区 在Java中,字符串是基本的数据类型。对字符串的操作涉及到内存管理、字符编码等多种因素,直接影响到程序的性能。 理解和误区: 1. **误区:频繁创建和销毁字符串对象** ╰+哭是因爲堅強的太久メ/ 2024年09月18日 16:36/ 0 赞/ 50 阅读
相关 Java 8 Stream 数据流效率分析和解读 Stream 是Java SE 8类库中新增的关键抽象,它被定义于 java.util.stream (这个包里有若干流类型:Stream代表对象引用流,此外还有一系列特化流, 短命女/ 2024年04月01日 12:14/ 0 赞/ 43 阅读
相关 理解 REST 和 RPC 的差异,提高开发效率 随着Web应用程序和分布式系统的不断发展,面向服务的架构和分布式系统的设计变得越来越重要。在这个领域中,REST和RPC是两种广泛使用的架构风格。本文将分别介绍REST和RPC r囧r小猫/ 2024年03月16日 17:03/ 0 赞/ 62 阅读
相关 lr pc sp寄存器相关理解 转载链接:http://hi.baidu.com/a843538946/item/4e2a34fe48b6e5be31c199ec http 旧城等待,/ 2022年08月06日 02:11/ 0 赞/ 182 阅读
相关 深入理解SP、LR和PC 深入理解ARM的这三个寄存器,对编程以及操作系统的移植都有很大的裨益。 1、堆栈指针r13(SP):每一种异常模式都有其自己独立的r13,它通常指向异常模式所专用的堆栈,也就 「爱情、让人受尽委屈。」/ 2022年08月06日 01:08/ 0 赞/ 253 阅读
相关 [SQL Server] sp_who, sp_who2和sp_who3 \[SQL Server\] sp\_who, sp\_who2和sp\_who3 sp\_who可以返回如下信息: (可选参数LoginName, 或active代 墨蓝/ 2022年07月12日 14:10/ 0 赞/ 223 阅读
相关 Java Switch 和 If else 使用效率对比和分析 前言 在我们实际开发中,会经常用到逻辑走向的判断语法,最熟悉的逻辑判断莫过于switch和if else它们俩了,但是它们之间究竟哪个更优呢?我们来一起做个试验吧!其 迈不过友情╰/ 2021年09月14日 02:08/ 0 赞/ 549 阅读
还没有评论,来说两句吧...