欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android?SharedPreferences性能瓶頸解析

 更新時間:2023年02月01日 09:32:21   作者:冰河本尊  
這篇文章主要為大家介紹了Android?SharedPreferences性能瓶頸解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

正文

想必大家對SharedPreferences都已經很熟悉了,大型應用使用SharedPreferences開發(fā)很容易出現性能瓶頸,相信很多開發(fā)者已經遷移到MMKV進行配置存儲

說到MMKV我們總是會看到如下這張圖

在模擬1000次寫入的情況下,MMKV大幅度領先SharedPreferences,我們都知道MMKV使用了mmap方式進行存儲,而SharedPreferences還是使用傳統(tǒng)的文件系統(tǒng),以xml的方式進行配置存儲,mmap確實具備較好的性能和穩(wěn)定性,但是真的兩種不同的存儲方式可以帶來如此巨大的性能差異嗎?

測試

因此我編寫代碼進行了一次測試

        findViewById(R.id.test5).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                long time2 = System.currentTimeMillis();
                SharedPreferences mSharedPreferences = WebTurboConfiguration.getInstance().mContext.getSharedPreferences(WebTurboConfigSp.Key.SHARED_PREFS_FILE_NAME, Context.MODE_PRIVATE);
                SharedPreferences.Editor editor = mSharedPreferences.edit();
                for (int i = 0; i < 1000; i++) {
                    editor.putString(i + "", 1000 + "");
                    editor.apply();
                }
                long time3 = System.currentTimeMillis();
                Log.e("模擬寫入", "sp存儲耗時 = " + (time3 - time2));
                MMKV mmkv = MMKV.defaultMMKV();
                for (int i = 0; i < 1000; i++) {
                    mmkv.putString(i + "", 1000 + "");
                }
                long time4 = System.currentTimeMillis();
                Log.e("模擬寫入", "mmkv 存儲耗時 = " + (time4 - time3));
            }
        });

輸出如下

E/模擬寫入: sp存儲耗時 = 82ms

E/模擬寫入: mmkv 存儲耗時 = 6ms

MMKV確實性能顯著強于SharedPreferences

apply方法的注釋

SharedPreferences在使用的時候是推薦使用apply進行保存,我們來看一下apply方法的注釋

注釋中明確說明apply方法是先將存儲數據提交到內存,然后異步進行磁盤寫入,既然是異步寫入,理論上IO不會拖后腿,我們可以認為時間都被消耗在了將數據提交到內存上,在寫入內存上面SharedPreferences與MMKV會有這么大的性能差距嗎?

這激起了我的興趣

我使用AS自帶的性能分析工具對SharedPreferences存儲過程進行一次trace分析 分析圖如下

可以輕松的從圖中看到

數據存儲put方法的主要耗時在puMapEntries上

代碼調用如下

SharedPreferences的實際實現代碼在SharedPreferencesImpl中

        @Override
        public void apply() {
            final long startTime = System.currentTimeMillis();
            final MemoryCommitResult mcr = commitToMemory();
            final Runnable awaitCommit = new Runnable() {
                    @Override
                    public void run() {
                        try {
                            mcr.writtenToDiskLatch.await();
                        } catch (InterruptedException ignored) {
                        }
                        if (DEBUG && mcr.wasWritten) {
                            Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                                    + " applied after " + (System.currentTimeMillis() - startTime)
                                    + " ms");
                        }
                    }
                };
            QueuedWork.addFinisher(awaitCommit);
            Runnable postWriteRunnable = new Runnable() {
                    @Override
                    public void run() {
                        awaitCommit.run();
                        QueuedWork.removeFinisher(awaitCommit);
                    }
                };
            SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
            notifyListeners(mcr);
        }

主要看

final MemoryCommitResult mcr = commitToMemory();

代碼比較長

        private MemoryCommitResult commitToMemory() {
            long memoryStateGeneration;
            boolean keysCleared = false;
            List<String> keysModified = null;
            Set<OnSharedPreferenceChangeListener> listeners = null;
            Map<String, Object> mapToWriteToDisk;
            synchronized (SharedPreferencesImpl.this.mLock) {
                if (mDiskWritesInFlight > 0) {
                    mMap = new HashMap<String, Object>(mMap);
                }
                mapToWriteToDisk = mMap;
                mDiskWritesInFlight++;
                boolean hasListeners = mListeners.size() > 0;
                if (hasListeners) {
                    keysModified = new ArrayList<String>();
                    listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
                }
                synchronized (mEditorLock) {
                    boolean changesMade = false;
                    if (mClear) {
                        if (!mapToWriteToDisk.isEmpty()) {
                            changesMade = true;
                            mapToWriteToDisk.clear();
                        }
                        keysCleared = true;
                        mClear = false;
                    }
                    for (Map.Entry<String, Object> e : mModified.entrySet()) {
                        String k = e.getKey();
                        Object v = e.getValue();
                        if (v == this || v == null) {
                            if (!mapToWriteToDisk.containsKey(k)) {
                                continue;
                            }
                            mapToWriteToDisk.remove(k);
                        } else {
                            if (mapToWriteToDisk.containsKey(k)) {
                                Object existingValue = mapToWriteToDisk.get(k);
                                if (existingValue != null && existingValue.equals(v)) {
                                    continue;
                                }
                            }
                            mapToWriteToDisk.put(k, v);
                        }
                        changesMade = true;
                        if (hasListeners) {
                            keysModified.add(k);
                        }
                    }
                    mModified.clear();
                    if (changesMade) {
                        mCurrentMemoryStateGeneration++;
                    }
                    memoryStateGeneration = mCurrentMemoryStateGeneration;
                }
            }
            return new MemoryCommitResult(memoryStateGeneration, keysCleared, keysModified,
                    listeners, mapToWriteToDisk);
        }

執(zhí)行邏輯

step1:可能需要對現有的數據mMap進行一次深度拷貝,生成新的mMap對象

step2:對存儲了已修改數據的map(mModified)進行遍歷,寫入mMap

step3:返回包含了全部數據的map用于存入文件系統(tǒng)

上文提到的大量耗時的puMapEntries方法就發(fā)生在step1中map的深度拷貝代碼中

if (mDiskWritesInFlight > 0) {
    mMap = new HashMap<String, Object>(mMap);
}
public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    putMapEntries(m, false);
}

為什么step1中說可能需要進行一次深度拷貝呢,因為mDiskWritesInFlight的值,在有配置需要寫入時,他就會+1,只有完全寫入磁盤,也就是此次配置已經被持久化,mDiskWritesInFlight才會-1,也就是說深度拷貝在上文提到的1000次寫入的場景下是一定會發(fā)生的,除了第一次可能不需要深度拷貝,后面999次大概率會發(fā)生深度拷貝,因為在整個1000次的寫入過程中,線程一直在不斷的將配置寫入磁盤,一直到1000次apply完成,數據可能還需要一段時間才能往磁盤里面寫完

我們代碼來模擬深度拷貝的場景,看深度拷貝map到底有多耗時,在代碼中我們模擬了1000次深度拷貝

E/模擬寫入: map深度拷貝耗時 = 52ms

E/模擬寫入: sp存儲耗時 = 59ms

E/模擬寫入: mmkv 存儲耗時 = 4ms

可以看到1000次深度拷貝的耗時已經接近SP1000次寫入的耗時

因此我們得到如下結論 在開發(fā)者使用SharedPreferences的apply方法進行存儲時,高頻次的apply調用會導致每次apply時進行map的深度拷貝,導致耗時,如果只是一次調用,或者低頻次的調用,那么SharedPreferences依然可以具備較好的性能

下面是一次調用的模擬,可以看到單次場景下與MMKV的性能差距不明顯

E/模擬寫入: sp存儲耗時 = 231192ns

E/模擬寫入: mmkv 存儲耗時 = 229154ns

那么如果需要高頻次寫入SharedPreferences,如何保證較好的性能呢,比如在一個循環(huán)中寫入SharedPreferences,那就要想辦法避免map被頻繁的深度拷貝,解決辦法就是多次put完成后再apply

示例代碼如下

        findViewById(R.id.test5).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                long time1 = System.currentTimeMillis();
                HashMap<String, String> map = new HashMap<>();
                for (int i = 0; i < 1000; i++) {
                    map.put(i + "", 1000 + "");
                    new HashMap<>(map);
                }
                long time2 = System.currentTimeMillis();
                Log.e("模擬寫入", "map深度拷貝耗時 = " + (time2 - time1));
                SharedPreferences mSharedPreferences = WebTurboConfiguration.getInstance().mContext.getSharedPreferences(WebTurboConfigSp.Key.SHARED_PREFS_FILE_NAME, Context.MODE_PRIVATE);
                SharedPreferences.Editor editor = mSharedPreferences.edit();
                for (int i = 0; i < 1000; i++) {
                    editor.putString(i + "", 1000 + "");
                }
                editor.apply();
                long time3 = System.currentTimeMillis();
                Log.e("模擬寫入", "sp存儲耗時 = " + (time3 - time2));
                MMKV mmkv = MMKV.defaultMMKV();
                for (int i = 0; i < 1000; i++) {
                    mmkv.putString(i + "", 1000 + "");
                }
                long time4 = System.currentTimeMillis();
                Log.e("模擬寫入", "mmkv 存儲耗時 = " + (time4 - time3));
            }
        });

輸出結果如下,SharedPreferences的存儲耗時甚至低于MMKV

E/模擬寫入: map深度拷貝耗時 = 55

E/模擬寫入: sp存儲耗時 = 1

E/模擬寫入: mmkv 存儲耗時 = 4

本文只針對循環(huán)保存配置這一種場景進行分析,無論如何使用,MMKV性能強于SharedPreferences是不爭的事實,如果開發(fā)者開發(fā)的只是一個小工具,小應用,推薦使用SharedPreferences,他足夠的輕量,如果開發(fā)商用中大型應用,MMKV依然是最好的選擇,至于jetpack中的DataStore,并未使用過,不做評價

以上就是Android SharedPreferences性能瓶頸解析的詳細內容,更多關于Android SharedPreferences性能瓶頸的資料請關注腳本之家其它相關文章!

相關文章

  • Android 日常開發(fā)總結的60條技術經驗

    Android 日常開發(fā)總結的60條技術經驗

    這篇文章主要介紹了Android日常開發(fā)總結的技術經驗60條,需要的朋友可以參考下
    2016-03-03
  • Android Service綁定過程完整分析

    Android Service綁定過程完整分析

    這篇文章主要為大家詳細介紹了Android Service綁定完整過程,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • Android編程四大組件之Activity用法實例分析

    Android編程四大組件之Activity用法實例分析

    這篇文章主要介紹了Android編程四大組件之Activity用法,實例分析了Activity的創(chuàng)建,生命周期,內存管理及啟動模式等,具有一定參考借鑒價值,需要的朋友可以參考下
    2016-01-01
  • Android實現淘寶購物車

    Android實現淘寶購物車

    這篇文章主要為大家詳細介紹了Android實現淘寶購物車,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-05-05
  • Android響應事件onClick方法的五種實現方式小結

    Android響應事件onClick方法的五種實現方式小結

    本篇文章主要介紹了Android響應onClick方法的五種實現方式小結,具有一定的參考價值,感興趣的小伙伴們可以參考一下。
    2017-03-03
  • 學習理解Android菜單Menu操作

    學習理解Android菜單Menu操作

    這篇文章主要幫助大家理解Android菜單Menu操作,引入Android菜單Menu操作的知識,感興趣的小伙伴們可以參考一下
    2016-04-04
  • Android開發(fā)使用HttpURLConnection進行網絡編程詳解【附源碼下載】

    Android開發(fā)使用HttpURLConnection進行網絡編程詳解【附源碼下載】

    這篇文章主要介紹了Android開發(fā)使用HttpURLConnection進行網絡編程的方法,結合實例形式分析了Android基于HttpURLConnection實現顯示圖片與文本功能,涉及Android布局、文本解析、數據傳輸、權限控制等相關操作技巧,需要的朋友可以參考下
    2018-01-01
  • 詳細分析Android中onTouch事件傳遞機制

    詳細分析Android中onTouch事件傳遞機制

    相信不少朋友在剛開始學習Android的時候,對于onTouch相關的事件一頭霧水。分不清onTouch(),onTouchEvent()和OnClick()之間的關系和先后順序,所以覺得有必要搞清onTouch事件傳遞的原理。經過一段時間的琢磨以及相關博客的介紹,這篇文章就給大家詳細的分析介紹下。
    2016-10-10
  • Android 動態(tài)改變布局實例詳解

    Android 動態(tài)改變布局實例詳解

    這篇文章主要介紹了Android 動態(tài)改變布局實例詳解的相關資料,這里舉例說明如何實現動態(tài)改變布局的例子,幫助大家學習理解,需要的朋友可以參考下
    2016-11-11
  • Android自定義控件(實現狀態(tài)提示圖表)

    Android自定義控件(實現狀態(tài)提示圖表)

    本篇文章主要介紹了android實現狀態(tài)提示圖表的功能,實現了動態(tài)圖表的顯示,有需要的朋友可以了解一下。
    2016-11-11

最新評論