Android SharedPreferences源碼都不會(huì),怎么通過面試棘钞?

SP整體認(rèn)知

sp結(jié)構(gòu).png

SharedPreferences的流程是非常簡(jiǎn)單的缠借,在ContextImpl中初始化,在SharedPreferenceImpl實(shí)現(xiàn)讀寫數(shù)據(jù)宜猜。先大概看一下sp的整體結(jié)構(gòu)泼返,先不用記住,后面還會(huì)說的姨拥,大概知道有這么幾個(gè)類就行绅喉。

  • SharedPreferences是個(gè)接口,它內(nèi)部還有2個(gè)接口Editor和OnSharedPreferenceChangeListener叫乌,其中Editor是寫入數(shù)據(jù)的柴罐,就是那個(gè)put()方法,OnSharedPreferenceChangeListener是在sp文件改變的時(shí)候回調(diào)的接口憨奸,此外SharedPreferences內(nèi)部還有獲取數(shù)據(jù)的方法革屠,也就是get()方法。
  • SharedPreferenceImpl是SharedPreferences的實(shí)現(xiàn)類排宰,EditorImpl是Editor的實(shí)現(xiàn)類似芝,它是SharedPreferenceImpl的內(nèi)部類。
  • 小結(jié)一下板甘,SharedPreferenceImpl是重中之重党瓮,初始化、讀寫都在這里完成盐类。

sp文件

SharedPreferences實(shí)際上是一個(gè)xml文件麻诀,存儲(chǔ)位置在

/data/data/應(yīng)用包名/shared_prefs/xx.xml

sp文件內(nèi)容.png

SharedPreferences基本用法

val sp: SharedPreferences = context.getSharedPreferences("zx", Context.MODE_PRIVATE)
        val editor = sp.edit()
        editor.putString("key0", "11")
        editor.commit() //同步提交
        editor.apply() //異步提交
        val result = sp.getString("key0", "")      

SharedPreferences有多種獲取方式,但是最終都是通過ContextImpl的getSharedPreferences()方法獲取的傲醉,這一步也叫初始化蝇闭,后面還有獲取、提交數(shù)據(jù)硬毕,很簡(jiǎn)單吧呻引,一共就3個(gè)部分,下面就來具體分析一下吐咳。

sp存儲(chǔ)初認(rèn)識(shí)

class ContextImpl extends Context {
    private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
  
    private ArrayMap<String, File> mSharedPrefsPaths;
}

class SharedPreferencesImpl implements SharedPreferences {
    private Map<String, Object> mMap;
}

class EditorImpl implements Editor {
    private final Map<String, Object> mModified = new HashMap<>();
}
  • 從文件上說逻悠,SharedPreferences是xml文件元践,每個(gè)xml文件都對(duì)應(yīng)一個(gè)存儲(chǔ)鍵值對(duì)的map。
  • 從內(nèi)存上說童谒,每個(gè)xml文件都會(huì)被加載進(jìn)內(nèi)存緩存起來单旁,這樣避免了頻繁的I/O,提升了性能饥伊。所以先來了解一下緩存相關(guān)的幾個(gè)map象浑。
    mSharedPrefsPaths:保存了sp文件名-xml文件的映射關(guān)系
    sSharedPrefsCache:保存了xml文件對(duì)應(yīng)的操作它的SharedPreferencesImpl的對(duì)象,據(jù)查源碼琅豆,它內(nèi)部只保存了一個(gè)鍵值對(duì)愉豺,key就是app的包名。
    mMap:保存了xml文件的內(nèi)容
    mModified:調(diào)用put()方法時(shí)茫因,臨時(shí)保存修改內(nèi)容蚪拦,在調(diào)用commit/apply之后保存到mMap中,并且寫進(jìn)xml文件里冻押。

好了驰贷,有個(gè)整體認(rèn)識(shí)之后,開始源碼解析洛巢。

獲取SharePreferences

##ContextImpl
public SharedPreferences getSharedPreferences(String name, int mode) {
     //...省略一些檢測(cè)代碼

        File file;
        synchronized (ContextImpl.class) {
            if (mSharedPrefsPaths == null) {
                mSharedPrefsPaths = new ArrayMap<>();
            }
            //先從緩存獲取xml文件
            file = mSharedPrefsPaths.get(name);
            if (file == null) {
                //創(chuàng)建name.xml文件
                file = getSharedPreferencesPath(name);
                //放入緩存
                mSharedPrefsPaths.put(name, file);
            }
        }
        return getSharedPreferences(file, mode);
    }

    //在指定目錄創(chuàng)建name.xml文件
    public File getSharedPreferencesPath(String name) {
        return makeFilename(getPreferencesDir(), name + ".xml");
    }
##ContextImpl
public SharedPreferences getSharedPreferences(File file, int mode) {
        SharedPreferencesImpl sp;
        synchronized (ContextImpl.class) {
            //從緩存sSharedPrefsCache獲取
            final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
            sp = cache.get(file);
            if (sp == null) {
                ...
                //沒有的話就new一個(gè),并且放入緩存饱苟。
                sp = new SharedPreferencesImpl(file, mode);
                cache.put(file, sp);
                return sp;
            }
        }
      ...
        return sp;
    }

//從緩存sSharedPrefsCache讀取SharedPreferencesImpl實(shí)例
 private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
        if (sSharedPrefsCache == null) {
            sSharedPrefsCache = new ArrayMap<>();
        }

        final String packageName = getPackageName();
        //從緩存sSharedPrefsCache中查看是否有對(duì)應(yīng)包名下的SharedPreferences
        ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
        if (packagePrefs == null) {
          //沒有的話創(chuàng)建一個(gè)空的map,然后放入緩存
            packagePrefs = new ArrayMap<>();
            sSharedPrefsCache.put(packageName, packagePrefs);
        }

        return packagePrefs;
    }

getSharedPreferences()就是獲取SharedPreferencesImpl對(duì)象狼渊,也就是真正實(shí)現(xiàn)sp的類,先從緩存sSharedPrefsCache獲取类垦,沒有的話就new一個(gè)并且放入緩存狈邑。再來看一下ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache對(duì)象,key為包名蚤认,值是一個(gè)ArrayMap米苹,值其實(shí)是在getSharedPreferences(File file, int mode)創(chuàng)建的,sSharedPrefsCache的size最多為1砰琢,值ArrayMap<File, SharedPreferencesImpl>存儲(chǔ)的是xml文件-sp實(shí)現(xiàn)類SharedPreferencesImpl映射蘸嘶,我們知道xml是可以有多個(gè)的,所以sSharedPrefsCache的值可以有多個(gè)陪汽。

小結(jié):在ContextImpl中通過緩存獲取或者創(chuàng)建了SharedPreferencesImpl對(duì)象训唱,它是sp真正的核心類。

核心類SharedPreferencesImpl

它實(shí)現(xiàn)了SharedPreferences接口挚冤,sp的初始化况增、獲取、寫入數(shù)據(jù)都是在這里完成的训挡。

SharedPreferencesImpl(File file, int mode) {
        mFile = file;
        mBackupFile = makeBackupFile(file);
        mMode = mode;
        mLoaded = false; //是否加載xml文件成功澳骤,默認(rèn)false
        mMap = null;
        mThrowable = null;
        //關(guān)鍵是這里
        startLoadFromDisk();
    }

    
    private void startLoadFromDisk() {
       
        synchronized (mLock) {
            mLoaded = false;
        }
        new Thread("SharedPreferencesImpl-load") {
            public void run() {
                //開啟子線程歧强,從磁盤加載、解析xml文件
                loadFromDisk();
            }
        }.start();
    }
 private void loadFromDisk() {
        synchronized (mLock) {
            //如果加載過了直接返回
            if (mLoaded) {
                return;
            }
          
               //解析xml后为肮,需要緩存到內(nèi)存中摊册,用map來保存
                Map<String, Object> map = null;
                BufferedInputStream str = null;
                try {
                    str = new BufferedInputStream(
                            new FileInputStream(mFile), 16 * 1024);
                    map = (Map<String, Object>) XmlUtils.readMapXml(str);
                } catch (Exception e) {
                    Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
                } finally {
                    IoUtils.closeQuietly(str);
                }
            }

      ...
      //加載成功之后,設(shè)置一下標(biāo)識(shí)位颊艳,并且喚醒線程
      synchronized (mLock) {
            mLoaded = true;
            try {
                if (thrown == null) {
                    if (map != null) {
                         //這個(gè)就是之前提到過的SharedPreferencesImpl中的成員變量mMap  ,這里給它復(fù)值
                        mMap = map;
                    } else {
                        mMap = new HashMap<>();
                    }
                }
            } catch (Throwable t) {
                mThrowable = t;
            } finally {
                //這里很關(guān)鍵茅特,加載完成之后喚醒等待阻塞的線程
                mLock.notifyAll();
            }
        }
    }

這里省略了一些無關(guān)的判斷,這里的邏輯比較簡(jiǎn)單籽暇,通過XmlUtils工具類去讀取xml文件內(nèi)容温治,其實(shí)就是用了XmlParser解析,從xml文件取到數(shù)據(jù)之后就緩存到mMap中戒悠,后面就可以直接從內(nèi)存讀取了熬荆,這樣就提高了讀取效率。

讀取數(shù)據(jù)

##SharedPreferencesImpl
public String getString(String key, @Nullable String defValue) {
        //這里之所以要加鎖绸狐,是因?yàn)閍waitLoadedLocked()里面用了wait()等待阻塞卤恳,wait必須要加鎖才能用
        synchronized (mLock) {
            //如果xml文件沒有加載完成,就一直等待阻塞
            awaitLoadedLocked();
            //從內(nèi)存獲取
            String v = (String)mMap.get(key);
            return v != null ? v : defValue;
        }
    }

private void awaitLoadedLocked() {
        if (!mLoaded) {
            // Raise an explicit StrictMode onReadFromDisk for this
            // thread, since the real read will be in a different
            // thread and otherwise ignored by StrictMode.
            BlockGuard.getThreadPolicy().onReadFromDisk();
        }

        //循環(huán)等待xml文件加載完成寒矿,加載完成之后會(huì)喚醒阻塞
        while (!mLoaded) {
            try {
                mLock.wait();
            } catch (InterruptedException unused) {
            }
        }
        if (mThrowable != null) {
            throw new IllegalStateException(mThrowable);
        }
    }

這里以getString()為例突琳,讀取的時(shí)候會(huì)先加鎖,如果xml文件沒有加載完成符相,就一直等待阻塞拆融;如果加載完成會(huì)自動(dòng)喚醒阻塞線程,并且從內(nèi)存獲取數(shù)據(jù)啊终。

修改數(shù)據(jù)

修改數(shù)據(jù)其實(shí)分為2部分镜豹,臨時(shí)存放修改數(shù)據(jù)(put)和提交修改數(shù)據(jù)(commit、apply)蓝牲。

臨時(shí)存放數(shù)據(jù)Editor

通常向SharedPreferences存放數(shù)據(jù)的時(shí)候趟脂,是通過如下方式完成的

 val editor: SharedPreferences.Editor = sp.edit()
 editor.putString("key0", "11")

首先調(diào)用SharedPreferences的edit()方法獲取Editor對(duì)象,然后調(diào)用put()方法例衍,注意:Editor是一個(gè)接口昔期,它的實(shí)現(xiàn)類是EditorImpl。

##SharedPreferencesImpl
@Override
    public Editor edit() {
       //阻塞等待佛玄,直到xml加載完成
        synchronized (mLock) {
            awaitLoadedLocked();
        }

        return new EditorImpl();
    }

class EditorImpl implements Editor {
        private final Object mEditorLock = new Object();
        //臨時(shí)存儲(chǔ)要提交數(shù)據(jù)的map
        private final Map<String, Object> mModified = new HashMap<>();

        @Override
        public Editor putString(String key, @Nullable String value) {
            synchronized (mEditorLock) {
                mModified.put(key, value);
                return this;
            }
        }
}

這里代碼很簡(jiǎn)單硼一,創(chuàng)建Editor對(duì)象之前會(huì)先判斷xml文件是否加載完成,每次edit()調(diào)用都是創(chuàng)建一個(gè)新的對(duì)象梦抢。put()方法是線程安全的欠动,它只是把數(shù)據(jù)存放到一個(gè)臨時(shí)的Map集合,并沒有提交到內(nèi)存緩存和磁盤。

提交修改數(shù)據(jù):commit()和apply()

 public boolean commit() {
            //將Editor修改的內(nèi)容提交到內(nèi)存緩存mMap
            MemoryCommitResult mcr = commitToMemory();
            //把數(shù)據(jù)寫入磁盤文件
            SharedPreferencesImpl.this.enqueueDiskWrite(
                mcr, null /* sync write on this thread okay */);
            try {
                //CountDownLatch阻塞等待
                mcr.writtenToDiskLatch.await();
            } catch (InterruptedException e) {
                return false;
            } 
            //回調(diào)通知
            notifyListeners(mcr);
            //寫入磁盤文件是否成功
            return mcr.writeToDiskResult;
        }


public void apply() {
           //修改內(nèi)存緩存mMap
            final MemoryCommitResult mcr = commitToMemory();
            //等待寫入文件完成的任務(wù)
            final Runnable awaitCommit = new Runnable() {
                    @Override
                    public void run() {
                        try {
                            //阻塞等待寫入文件完成,否則阻塞在這
                            //利用CountDownLatch來等待任務(wù)的完成
//后面執(zhí)行enqueueDiskWrite寫入文件成功后會(huì)把writtenToDiskLatch多線程計(jì)數(shù)器減1具伍, 這樣的話下面的阻塞代碼就可以通過了.
                            mcr.writtenToDiskLatch.await();
                        } catch (InterruptedException ignored) {
                        }
                    }
                };
            //QueuedWork是用來確保SharedPrefenced的寫操作在Activity 銷毀前執(zhí)行完的一個(gè)全局隊(duì)列. 
            //QueuedWork里面的隊(duì)列是通過LinkedList實(shí)現(xiàn)的翅雏,LinkedList不僅可以做鏈表,也可以做隊(duì)列
            //添加到全局的工作隊(duì)列中
            QueuedWork.addFinisher(awaitCommit);

          //這個(gè)任務(wù)是等待磁盤寫入完成人芽,然后從隊(duì)列中移除任務(wù)
            Runnable postWriteRunnable = new Runnable() {
                    @Override
                    public void run() {
                        //執(zhí)行阻塞任務(wù)
                        awaitCommit.run();
                        //阻塞完成之后望几,從隊(duì)列中移除任務(wù)
                        QueuedWork.removeFinisher(awaitCommit);
                    }
                };
            //異步執(zhí)行磁盤文件寫入,注意這里和commit不同的是postWriteRunnable不為空
            SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);

       
            notifyListeners(mcr);
        }

commit()方法很簡(jiǎn)單萤厅,就3步橄抹,寫入內(nèi)存緩存、寫入磁盤文件惕味、返回寫入文件結(jié)果楼誓。apply的流程和commit差不多,只是apply沒有返回值名挥。二者都會(huì)調(diào)用enqueueDiskWrite寫入文件疟羹,commit的postWriteRunnable參數(shù)為null,apply是有值的禀倔。

private MemoryCommitResult commitToMemory() {
      ...只保留關(guān)鍵代碼
          //mModified就是之前edit().put()來存放的數(shù)據(jù)
          for (Map.Entry<String, Object> e : mModified.entrySet()) {
                        String k = e.getKey();
                        Object v = e.getValue();
                    // 如果執(zhí)行了remove榄融,則v對(duì)應(yīng)的this,將這些key/value從mMap移除
                        if (v == this || v == null) {
                            if (!mapToWriteToDisk.containsKey(k)) {
                                continue;
                            }
                            mapToWriteToDisk.remove(k);
                        } else {
                            //將mModified中要修改的數(shù)據(jù)寫到內(nèi)存緩存mMap中
                            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();

      return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
                    mapToWriteToDisk);
}

commitToMemory就是將mModified中的修改寫入到內(nèi)存緩存中救湖,接著看一下寫入文件方法enqueueDiskWrite()愧杯。

##SharedPreferencesImpl
private void enqueueDiskWrite(final MemoryCommitResult mcr,
                                  final Runnable postWriteRunnable) {
        //commit和apply的區(qū)別,commit 的postWriteRunnable參數(shù)為null鞋既,而apply是有值的
        final boolean isFromSyncCommit = (postWriteRunnable == null);

        //寫入磁盤文件的任務(wù)
        final Runnable writeToDiskRunnable = new Runnable() {
                @Override
                public void run() {
                    synchronized (mWritingToDiskLock) {
                        //就是將MemoryCommitResult的mapToWriteToDisk寫入到文件力九,其實(shí)就是mMap的內(nèi)容
                        // 寫入文件完成之后,會(huì)調(diào)用writtenToDiskLatch.countDown()將計(jì)數(shù)器-1邑闺,這樣就不會(huì)阻塞了
                        writeToFile(mcr, isFromSyncCommit);
                    }
                    synchronized (mLock) {
                        mDiskWritesInFlight--;
                    }
                    //apply才執(zhí)行
                    if (postWriteRunnable != null) {
                        // 寫文件成功后則執(zhí)行移除全局隊(duì)列中的任務(wù)的任務(wù).
                        // 此時(shí)waitCommit 任務(wù)就不會(huì)阻塞了, 因?yàn)閣rittenToDiskLatch==0 了.
                        // 不阻塞 QueuedWork.remove(awaitCommit); 就會(huì)被調(diào)用, 也就是說該任務(wù)執(zhí)行完了
                      // 會(huì)將該任務(wù)從隊(duì)列中移除
                        postWriteRunnable.run();
                    }
                }
            };

       //commit才執(zhí)行跌前,即在UI線程寫入文件
        if (isFromSyncCommit) {
            boolean wasEmpty = false;
            synchronized (mLock) {
                // mDiskWritesInFlight 會(huì)在commitToMemory() 方法中進(jìn)行+1 操作
                wasEmpty = mDiskWritesInFlight == 1;
            }
            if (wasEmpty) {
                // 在當(dāng)前線程執(zhí)行寫任務(wù)
                //這里是直接調(diào)用Runnable的run方法,就是普通的方法調(diào)用检吆,而不是開啟子線程,懂了吧
                writeToDiskRunnable.run();
                return;
            }
        }
        
        //apply會(huì)調(diào)用這個(gè)程储,里面會(huì)通過HandlerThread去執(zhí)行writeToDiskRunnable
        QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
    }

小結(jié):apply的邏輯稍微復(fù)雜了一點(diǎn)蹭沛,大家仔細(xì)看。
apply是異步操作章鲤,每次調(diào)用apply會(huì)把寫入任務(wù)放在一個(gè)LinkedList實(shí)現(xiàn)的隊(duì)列中摊灭,然后在QueuedWork中通過一個(gè)HandlerThread串行的執(zhí)行寫入文件的任務(wù)。排隊(duì)是通過CountDownLatch來實(shí)現(xiàn)的败徊,它其實(shí)是一個(gè)多線程計(jì)數(shù)器帚呼,調(diào)用它的await可以阻塞等待寫入文件的子線程(HandlerThread)完成,在寫入文件完成之后會(huì)調(diào)用它的countDown()將計(jì)數(shù)器-1,這樣writtenToDiskLatch==0就不會(huì)阻塞等待了,然后再移除隊(duì)列中的該任務(wù)煤杀。

##QueuedWork
private static final LinkedList<Runnable> sWork = new LinkedList<>();
/** If new work can be delayed or not */
private static boolean sCanDelay = true;

public static void queue(Runnable work, boolean shouldDelay) {
        //獲取子線程的Handler眷蜈,通過HandlerThread創(chuàng)建的
        Handler handler = getHandler();

        synchronized (sLock) {
            //Runnable加入list排隊(duì)
            sWork.add(work);
            //apply傳入的shouldDelay為true
            if (shouldDelay && sCanDelay) {
   //apply執(zhí)行這句,默認(rèn)延遲100ms執(zhí)行任務(wù)             handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
            } else {
                handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
            }
        }
    }

//懶加載創(chuàng)建子線程的Handler沈自,后面的寫入文件的任務(wù)都在子線程完成
private static Handler getHandler() {
        synchronized (sLock) {
            if (sHandler == null) {
                HandlerThread handlerThread = new HandlerThread("queued-work-looper",
                        Process.THREAD_PRIORITY_FOREGROUND);
                handlerThread.start();

                sHandler = new QueuedWorkHandler(handlerThread.getLooper());
            }
            return sHandler;
        }
    }

private static class QueuedWorkHandler extends Handler {
        static final int MSG_RUN = 1;

        QueuedWorkHandler(Looper looper) {
            super(looper);
        }

        public void handleMessage(Message msg) {
            if (msg.what == MSG_RUN) {
                //處理排隊(duì)的寫文件任務(wù)
                processPendingWork();
            }
        }
    }

這里代碼量不少酌儒,但是邏輯卻很簡(jiǎn)單,把a(bǔ)pply提交的任務(wù)加到一個(gè)LinkedList中枯途,然后開啟子線程去串行的執(zhí)行任務(wù)忌怎。

private static void processPendingWork() {
       synchronized (sProcessingWork) {
            LinkedList<Runnable> work;

            synchronized (sLock) {
                work = (LinkedList<Runnable>) sWork.clone();
                sWork.clear();

                // Remove all msg-s as all work will be processed now
                getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
            }
            //太簡(jiǎn)單了,串行處理排隊(duì)的寫文件任務(wù)
            //注意:這里已經(jīng)在HandlerThread這個(gè)子線程中了
            if (work.size() > 0) {
                for (Runnable w : work) {
                    w.run();
                }
            }
        }
    }

好了酪夷,到這里SharedPreferences源碼就分析完了榴啸。

總結(jié)

  • 1、初始化:通過XmlUtils這個(gè)工具類讀取晚岭、解析xml文件鸥印,并且把數(shù)據(jù)加載到內(nèi)存緩存中,這樣避免了頻繁的 I/O腥例,提升了讀取數(shù)據(jù)的效率辅甥;
  • 2、寫操作:通過Editor把數(shù)據(jù)存放到臨時(shí)map集合燎竖,當(dāng)調(diào)用commit()/apply()的時(shí)候璃弄,再把數(shù)據(jù)分別提交到內(nèi)存和文件;
  • 3构回、讀操作:需要先阻塞等待加載文件完成夏块,然后再?gòu)膬?nèi)存中讀取數(shù)據(jù);第一次會(huì)比較慢纤掸,以后就很快脐供;
  • 4、commit()有返回值借跪,是在主線程寫入文件政己;apply()沒有返回值,在子線程寫入文件掏愁。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末歇由,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子果港,更是在濱河造成了極大的恐慌沦泌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辛掠,死亡現(xiàn)場(chǎng)離奇詭異谢谦,居然都是意外死亡释牺,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門回挽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來没咙,“玉大人,你說我怎么就攤上這事厅各【盗茫” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵队塘,是天一觀的道長(zhǎng)袁梗。 經(jīng)常有香客問我,道長(zhǎng)憔古,這世上最難降的妖魔是什么遮怜? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮鸿市,結(jié)果婚禮上锯梁,老公的妹妹穿的比我還像新娘。我一直安慰自己焰情,他們只是感情好陌凳,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著内舟,像睡著了一般合敦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上验游,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天充岛,我揣著相機(jī)與錄音,去河邊找鬼耕蝉。 笑死崔梗,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的垒在。 我是一名探鬼主播蒜魄,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼场躯!你這毒婦竟也來了谈为?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤推盛,失蹤者是張志新(化名)和其女友劉穎峦阁,沒想到半個(gè)月后谦铃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耘成,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瘪菌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片撒会。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖师妙,靈堂內(nèi)的尸體忽然破棺而出诵肛,到底是詐尸還是另有隱情,我是刑警寧澤默穴,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布怔檩,位于F島的核電站,受9級(jí)特大地震影響蓄诽,放射性物質(zhì)發(fā)生泄漏薛训。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一仑氛、第九天 我趴在偏房一處隱蔽的房頂上張望乙埃。 院中可真熱鬧,春花似錦锯岖、人聲如沸介袜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽遇伞。三九已至,卻和暖如春趋箩,著一層夾襖步出監(jiān)牢的瞬間赃额,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工叫确, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留跳芳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓竹勉,卻偏偏與公主長(zhǎng)得像飞盆,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子次乓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348