codeegginterviewgroup / CodeEggDailyInterview

码个蛋每日面试题
395 stars 54 forks source link

SharedPreferences 为什么存储大数据就比较占用内存? #184

Open kukyxs opened 4 years ago

kukyxs commented 4 years ago

SharedPreferencesImpl 会将 File 文件在子线程全部加载解析到一个内存的 Map 中,而 SharedPreferences 对象又会被 ContextImpl 的 static Map 进程 Cache 操作,所以 SharedPreferencesImpl 就相当于是一个单例存储的,故而其 Map 在内存中就会持续存在,即便使用 Editor 进行修改后 commit 操作实质也是对 SharedPreferencesImpl 中 Map 进行对应操作。

//一个SharedPreferences的get操作与Editor操作对应的内存Map操作原理
final class SharedPreferencesImpl implements SharedPreferences {
    //用来存储该SP的所有Key-Value对 
    private Map<String, Object> mMap;
    ......
    SharedPreferencesImpl(File file, int mode) {
        ......
        //构造方法新起一个线程从磁盘加载SP文件解析XML到mMap中
        startLoadFromDisk();
    }
    ......
    //通过SharedPreferences对象获取指定key的值
    public int getInt(String key, int defValue) {
        ......
        Integer v = (Integer)mMap.get(key);
        return v != null ? v : defValue;
    }
    ......
    //通过SharedPreferences.edit()获取的Editor对象
    public final class EditorImpl implements Editor {
        //Editor要增删改查的操作Map记录
        private final Map<String, Object> mModified = Maps.newHashMap();
        ......
        public Editor putString(String key, @Nullable String value) {
            ......
            mModified.put(key, value);
        }
        ......
        public Editor remove(String key) {
            ......
            mModified.put(key, this);
        }
        ......
        //提交到内存mMap
        // Returns true if any changes were made
        private MemoryCommitResult commitToMemory() {
            ......
            for (Map.Entry<String, Object> e : mModified.entrySet()) {
                String k = e.getKey();
                Object v = e.getValue();
                if (v == this || v == null) {
                    if (!mMap.containsKey(k)) {
                        continue;
                    }
                    mMap.remove(k);
                } else {
                    if (mMap.containsKey(k)) {
                        Object existingValue = mMap.get(k);
                        if (existingValue != null && existingValue.equals(v)) {
                            continue;
                        }
                    }
                    mMap.put(k, v);
                }
                changesMade = true;
                if (hasListeners) {
                    keysModified.add(k);
                }
            }
            //清空Editor的mModified的Map
            mModified.clear();
            ......
        }
        ......
        //commit提交
        public boolean commit() {
            ......
            //提交到内存
            MemoryCommitResult mcr = commitToMemory();
            //提交到磁盘文件
            SharedPreferencesImpl.this.enqueueDiskWrite(
                mcr, null /* sync write on this thread okay */);
            try {
                mcr.writtenToDiskLatch.await();
            } catch (InterruptedException e) {
                return false;
            } finally {
                ......
            }
            notifyListeners(mcr);
            return mcr.writeToDiskResult;
        }
        ......
    }
}

可以理解成一个 SpFile 对应一个单例的 SharedPreferencesImpl 对象,这个单例的 SharedPreferencesImpl 实例化时会将文件全部加载到内存中以 Map 存储,然后 Editor 操作会 new 一个新的 modifyMap,接着到了 Editor 的 commit 操作时会将 modifyMap 进行遍历增删改查到 SharedPreferencesImpl 的 Map 中,然后进行存盘操作,而 SharedPreferencesImpl 进行 getX(key) 操作时都是直接从 SharedPreferencesImpl 的 Map 中进行读取的,所以说 SharedPreferences 只适合轻量级的数据存储操作。