<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                [TOC] ## SharedPreferences存在的問題 ### SP的效率比較低 1.讀寫方式:直接I/O 2.數據格式:xml 3.寫入方式:全量更新 ![](https://img.kancloud.cn/03/3a/033a7c23e0e54769f6804b6bb4cb2228_445x229.png) 由于SP使用的xml格式保存數據,所以每次更新數據只能全量替換更新數據 這意味著如果我們有100個數據,如果只更新一項數據,也需要將所有數據轉化成xml格式,然后再通過io寫入文件中 這也導致SP的寫入效率比較低 ### commit導致的ANR ~~~ public boolean commit() { // 在當前線程將數據保存到mMap中 MemoryCommitResult mcr = commitToMemory(); SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null); try { // 如果是在singleThreadPool中執行寫入操作,通過await()暫停主線程,直到寫入操作完成。 // commit的同步性就是通過這里完成的。 mcr.writtenToDiskLatch.await(); } catch (InterruptedException e) { return false; } /* * 回調的時機: * 1. commit是在內存和硬盤操作均結束時回調 * 2. apply是內存操作結束時就進行回調 */ notifyListeners(mcr); return mcr.writeToDiskResult; } ~~~ 如上所示 1.commit有返回值,表示修改是否提交成功 2.commit提交是同步的,直到磁盤操作成功后才會完成 所以當數據量比較大時,使用commit很可能引起ANR ### Apply導致的ANR commit是同步的,同時SP也提供了異步的apply apply是將修改數據原子提交到內存, 而后異步真正提交到硬件磁盤, 而commit是同步的提交到硬件磁盤,因此,在多個并發的提交commit的時候,他們會等待正在處理的commit保存到磁盤后在操作,從而降低了效率。而apply只是原子的提交到內容,后面有調用apply的函數的將會直接覆蓋前面的內存數據,這樣從一定程度上提高了很多效率 但是apply同樣會引起ANR的問題 ~~~ public void apply() { final long startTime = System.currentTimeMillis(); final MemoryCommitResult mcr = commitToMemory(); final Runnable awaitCommit = new Runnable() { @Override public void run() { mcr.writtenToDiskLatch.await(); // 等待 ...... } }; // 將 awaitCommit 添加到隊列 QueuedWork 中 QueuedWork.addFinisher(awaitCommit); Runnable postWriteRunnable = new Runnable() { @Override public void run() { awaitCommit.run(); QueuedWork.removeFinisher(awaitCommit); } }; SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); } ~~~ * 將一個 `awaitCommit` 的 `Runnable` 任務,添加到隊列 `QueuedWork` 中,在 `awaitCommit` 中會調用 `await()` 方法等待,在 `handleStopService` 、 `handleStopActivity` 等等生命周期會以這個作為判斷條件,等待任務執行完畢 * 將一個 `postWriteRunnable` 的 `Runnable` 寫任務,通過 `enqueueDiskWrite` 方法,將寫入任務加入到隊列中,而寫入任務在一個線程中執行 為了保證異步任務及時完成,當生命周期處于 `handleStopService()` 、 `handlePauseActivity()` 、 `handleStopActivity()` 的時候會調用 `QueuedWork.waitToFinish()` 會等待寫入任務執行完畢 ~~~ 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 就不奇怪了 所以當數據量比較大時,`apply`也會造成ANR ### getXXX() 導致ANR 不僅是寫入操作,所有 getXXX() 方法都是同步的,在主線程調用 get 方法,必須等待 SP 加載完畢,也有可能導致ANR 調用 `getSharedPreferences()` 方法,最終會調用 `SharedPreferencesImpl#startLoadFromDisk()` 方法開啟一個線程異步讀取數據。 ~~~kotlin 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()` 方法。 ~~~kotlin 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 的數據還好,假設讀取一個大的文件,勢必會造成主線程阻塞。 ## MMKV的使用 MMKV 是基于 mmap 內存映射的 key-value 組件,底層序列化/反序列化使用 protobuf 實現,性能高,穩定性強。從 2015 年中至今在微信上使用,其性能和穩定性經過了時間的驗證。近期也已移植到 Android / macOS / Win32 / POSIX 平臺,一并開源。 ### MMKV優點 1.MMKV實現了SharedPreferences接口,可以無縫切換 2.通過 mmap 內存映射文件,提供一段可供隨時寫入的內存塊,App 只管往里面寫數據,由操作系統負責將內存回寫到文件,不必擔心 crash 導致數據丟失。 3.MMKV數據序列化方面選用 protobuf 協議,pb 在性能和空間占用上都有不錯的表現 4.SP是全量更新,MMKV是增量更新,有性能優勢 詳細的使用細節可以參考文檔:[github.com/Tencent/MMK…](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2FTencent%2FMMKV%2Fwiki "https://github.com/Tencent/MMKV/wiki") ## MMKV原理 ### 為什么MMKV寫入速度更快 #### IO操作 我們知道,SP是寫入是基于IO操作的,為了了解IO,我們需要先了解下用戶空間與內核空間 虛擬內存被操作系統劃分成兩塊:用戶空間和內核空間,用戶空間是用戶程序代碼運行的地方,內核空間是內核代碼運行的地方。為了安全,它們是隔離的,即使用戶的程序崩潰了,內核也不受影響。 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6254a74598c04cc0997648fb9d24ec39~tplv-k3u1fbpfcp-watermark.awebp) **寫文件流程:** 1、調用write,告訴內核需要寫入數據的開始地址與長度 2、內核將數據拷貝到內核緩存 3、由操作系統調用,將數據拷貝到磁盤,完成寫入 ![](https://img.kancloud.cn/17/67/1767b9d87ff040bc61b7ceb8343ddf94_1474x876.png) #### MMAP Linux通過將一個虛擬內存區域與一個磁盤上的對象關聯起來,以初始化這個虛擬內存區域的內容,這個過程稱為內存映射(memory mapping)。 ![](https://img.kancloud.cn/04/05/04054c08720c05af21e46d377874e91b_714x604.png) 對文件進行mmap,會在進程的虛擬內存分配地址空間,創建映射關系。 實現這樣的映射關系后,就可以采用指針的方式讀寫操作這一段內存,而系統會自動回寫到對應的文件磁盤上 #### MMAP優勢 * MMAP對文件的讀寫操作只需要從磁盤到用戶主存的一次數據拷貝過程,減少了數據的拷貝次數,提高了文件讀寫效率。 * MMAP使用邏輯內存對磁盤文件進行映射,操作內存就相當于操作文件,不需要開啟線程,操作MMAP的速度和操作內存的速度一樣快; * MMAP提供一段可供隨時寫入的內存塊,App 只管往里面寫數據,由操作系統如內存不足、進程退出等時候負責將內存回寫到文件,不必擔心 crash 導致數據丟失。 ![](https://img.kancloud.cn/09/c4/09c45224d084b534e1d0292429ce7fab_533x157.png) 可以看出,MMAP的寫入速度基本與內存寫入速度一致,遠高于SP,這就是MMKV寫入速度更快的原因 ### MMKV寫入方式 #### SP的數據結構 SP是使用XML格式存儲數據的,如下所示 ![](https://img.kancloud.cn/39/a2/39a215e22641dddf44f25f770820a813_802x521.png) 但是這也導致SP如果要更新數據的話,只能全量更新 #### MMKV數據結構 MMKV數據結構如下 ![](https://img.kancloud.cn/31/56/3156bf00537d3cd338469cc426995dd7_611x236.png) MMKV使用Protobuf存儲數據,冗余數據更少,更省空間,同時可以方便地在末尾追加數據 #### 寫入方式 **增量寫入** 不管key是否重復,直接將數據追加在前數據后。 這樣效率更高,更新數據只需要插入一條數據即可。 當然這樣也會帶來問題,如果不斷增量追加內容,文件越來越大,怎么辦? 當文件大小不夠,這時候需要全量寫入。將數據去掉重復key后,如果文件大小滿足寫入的數據大小,則可以直接更新全量寫入,否則需要擴容。(在擴容時根據平均每個K-V大小計算未來可能需要的文件大小進行擴容,防止經常性的全量寫入) ### MMKV三大優勢 * mmap防止數據丟失,提高讀寫效率; * 精簡數據,以最少的數據量表示最多的信息,減少數據大小; * 增量更新,避免每次進行相對增量來說大數據量的全量寫入。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看