[Android]高性能MMKV數(shù)據(jù)交互分析-MMKV初始化

大家好茫舶,我系蒼王铭污。
以下是我這個(gè)系列的相關(guān)文章冀偶,有興趣可以參考一下舆声,可以給個(gè)喜歡或者關(guān)注我的文章花沉。

[Android]如何做一個(gè)崩潰率少于千分之三噶應(yīng)用app--章節(jié)列表

Android組件化架構(gòu)熱賣中

組件化群1已經(jīng)滿員柳爽,進(jìn)來(lái)的可以加群2 763094035

MMKV框架初始化

    MMKV.initialize(this);

    public static String initialize(Context context) {
        //創(chuàng)建存儲(chǔ)目錄
        rootDir = context.getFilesDir().getAbsolutePath() + "/mmkv";
        initialize(rootDir);
        return rootDir;
    }

    private static native void initialize(String var0);

之后是調(diào)用jni中的操作了

extern "C" JNIEXPORT JNICALL void
Java_com_tencent_mmkv_MMKV_initialize(JNIEnv *env, jobject obj, jstring rootDir) {
    //c++中大于0和非空都是為真 
    if (!rootDir) {
        return;
    }

    //string轉(zhuǎn)為char*
    const char *kstr = env->GetStringUTFChars(rootDir, nullptr);
    if (kstr) {
        MMKV::initializeMMKV(kstr);
        //初始化完后釋放kstr
        env->ReleaseStringUTFChars(rootDir, kstr);
    }
}

MMKV初始化媳握,
pthread_once()函數(shù)詳解

在多線程環(huán)境中,有些事僅需要執(zhí)行一次磷脯。通常當(dāng)初始化應(yīng)用程序時(shí)蛾找,可以比較容易地將其放在main函數(shù)中。但當(dāng)你寫一個(gè)庫(kù)時(shí)赵誓,就不能在main里面初始化了打毛,你可以用靜態(tài)初始化,但使用一次初始化(pthread_once)會(huì)比較容易些俩功。

int pthread_once(pthread_once_t once_control, void (init_routine) (void))幻枉;

功能:本函數(shù)使用初值為PTHREAD_ONCE_INIT的once_control變量保證init_routine()函數(shù)在本進(jìn)程執(zhí)行序列中僅執(zhí)行一次。

在多線程編程環(huán)境下诡蜓,盡管pthread_once()調(diào)用會(huì)出現(xiàn)在多個(gè)線程中熬甫,init_routine()函數(shù)僅執(zhí)行一次,究竟在哪個(gè)線程中執(zhí)行是不定的蔓罚,是由內(nèi)核調(diào)度來(lái)決定椿肩。
那么initialize方法只會(huì)執(zhí)行一次瞻颂。

void MMKV::initializeMMKV(const std::string &rootDir) {
    //進(jìn)程控制
    static pthread_once_t once_control = PTHREAD_ONCE_INIT;
    //進(jìn)程中只執(zhí)行一次initialize
    pthread_once(&once_control, initialize);
    //保存文件地址
    g_rootDir = rootDir;
    //strdup字符串拷貝
    //c_str()函數(shù)返回一個(gè)指向正規(guī)C字符串的指針, 內(nèi)容與本string串相同.這是為了與c語(yǔ)言兼容,在c語(yǔ)言中沒有string類型郑象,故必須通過(guò)string類對(duì)象的成員函數(shù)c_str()把string 對(duì)象轉(zhuǎn)換成c中的字符串樣式贡这。注意:一定要使用strcpy()函數(shù) 等來(lái)操作方法c_str()返回的指針
    //需要分配空間的
    char *path = strdup(g_rootDir.c_str());
    //創(chuàng)建MmapedFile
    mkPath(path);
    //清除地址空間
    free(path);

    MMKVInfo("root dir: %s", g_rootDir.c_str());
}

void initialize() { 
    //建立哈希表<id,mmkv>
   // 使用unordered_map優(yōu)點(diǎn)因?yàn)閮?nèi)部實(shí)現(xiàn)了哈希表,因此其查找速度非常的快 厂榛,缺點(diǎn)哈希表的建立比較耗費(fèi)時(shí)間
    g_instanceDic = new unordered_map<std::string, MMKV *>;
    //初始化線程鎖
    g_instanceLock = ThreadLock();

    //testAESCrypt();

    MMKVInfo("page size:%d", DEFAULT_MMAP_SIZE);
}
//使用getpagesize函數(shù)獲得一頁(yè)內(nèi)存大小
//系統(tǒng)給我們提供真正的內(nèi)存時(shí)盖矫,用頁(yè)為單位提供,一次最少提供一頁(yè)的真實(shí)內(nèi)存空間
 //分配內(nèi)存空間:你真實(shí)的分配了多少內(nèi)存击奶,就使用多少內(nèi)存炼彪,不要越界使用
 //但是系統(tǒng)提供的真實(shí)內(nèi)存空間是以頁(yè)來(lái)提供的。
const int DEFAULT_MMAP_SIZE = getpagesize();

創(chuàng)建保存的文件夾正歼,遍歷地址名辐马,然后循環(huán)創(chuàng)建

bool mkPath(char *path) {
    //文件(夾)信息結(jié)構(gòu)體
    struct stat sb = {};
    bool done = false;
    char *slash = path;

    while (!done) {
        //拿出文件夾名
        slash += strspn(slash, "/");
        slash += strcspn(slash, "/");
        //遍歷到最尾部
        done = (*slash == '\0');
        *slash = '\0';
        //如果文件夾不為空
        if (stat(path, &sb) != 0) {
            //創(chuàng)建文件夾
            if (errno != ENOENT || mkdir(path, 0777) != 0) {
                MMKVWarning("%s", path);
                return false;
            }
        } else if (!S_ISDIR(sb.st_mode)) {  //如果不是文件夾,拋錯(cuò)誤
            MMKVWarning("%s: %s", path, strerror(ENOTDIR));
            return false;
        }

        *slash = '/';
    }

    return true;
}

保存文件初始化

    //需要填寫mmapId局义,線程(單線程喜爷,多線程),加密密鑰
    MMKV.mmkvWithID(mmapID, MMKV.SINGLE_PROCESS_MODE, cryptKey);

    public static MMKV mmkvWithID(String mmapID, int mode, String cryptKey) {
        if(rootDir == null) {  //如果主目錄未初始化拋出異常
            throw new IllegalStateException("You should Call MMKV.initialize() first.");
        } else {
            //id 不能超過(guò)46個(gè)字節(jié)
            verifyMMID(mmapID); 
            創(chuàng)立出jni中分配的ID
            long handle = getMMKVWithID(mmapID, mode, cryptKey);
            return new MMKV(handle);
        }
    }
    //mmkv的id是long類型
    private static native long getMMKVWithID(String var0, int var1, String var2);

extern "C" JNIEXPORT JNICALL jlong Java_com_tencent_mmkv_MMKV_getMMKVWithID(
    JNIEnv *env, jobject obj, jstring mmapID, jint mode, jstring cryptKey) {
    MMKV *kv = nullptr;  //初始化為空指針
    if (!mmapID) {  //如果為空萄唇,直接返回空指針
        return (jlong) kv;  
    }
    string str = jstring2string(env, mmapID);  //格式化mmapId

    if (cryptKey != nullptr) {
        string crypt = jstring2string(env, cryptKey);  //格式化密鑰
        if (crypt.length() > 0) { 
            //創(chuàng)建文件檩帐,大小為一頁(yè),和加密密鑰
            kv = MMKV::mmkvWithID(str, DEFAULT_MMAP_SIZE, (MMKVMode) mode, &crypt);
        }
    }
    if (!kv) {  //如果創(chuàng)建失敗另萤,重新創(chuàng)建一個(gè)不加密的文件
        kv = MMKV::mmkvWithID(str, DEFAULT_MMAP_SIZE, (MMKVMode) mode, nullptr);
    }

    return (jlong) kv;
}

MMKV *MMKV::mmkvWithID(const std::string &mmapID, int size, MMKVMode mode, string *cryptKey) {
    //如果mmapId為空范圍空指針
    if (mmapID.empty()) {
        return nullptr;
    }
    //設(shè)定單例鎖
    SCOPEDLOCK(g_instanceLock);
    //在記錄文件的map中找mampID湃密,對(duì)應(yīng)的mapFile文件
    auto itr = g_instanceDic->find(mmapID);
    //返回結(jié)束位置胡迭代器
    //如果已經(jīng)存在
    if (itr != g_instanceDic->end()) {
        //返回現(xiàn)在對(duì)應(yīng)mmkv
        MMKV *kv = itr->second;
        return kv;
    }
    //新建一個(gè)mmkv
    auto kv = new MMKV(mmapID, size, mode, cryptKey);
     //保存到map中
    (*g_instanceDic)[mmapID] = kv;
    return kv;
}

創(chuàng)建MMKV 初始化

MMKV::MMKV(const std::string &mmapID, int size, MMKVMode mode, string *cryptKey)
    : m_mmapID(mmapID)
    , m_path(mappedKVPathWithID(m_mmapID, mode)) //構(gòu)建mmkv文件地址
    , m_crcPath(crcPathWithID(m_mmapID, mode))  //crc文件
    , m_metaFile(m_crcPath, DEFAULT_MMAP_SIZE, (mode & MMKV_ASHMEM) ? MMAP_ASHMEM : MMAP_FILE) //初始化MmapedFile
    , m_crypter(nullptr)  //加密器
    , m_fileLock(m_metaFile.getFd())  //文件鎖
    , m_sharedProcessLock(&m_fileLock, SharedLockType)   //進(jìn)程鎖
    , m_exclusiveProcessLock(&m_fileLock, ExclusiveLockType)  //專用進(jìn)程鎖
    , m_isInterProcess((mode & MMKV_MULTI_PROCESS) != 0)  //是否多進(jìn)程
    , m_isAshmem((mode & MMKV_ASHMEM) != 0) {       //是否開啟共享內(nèi)存
    m_fd = -1;
    m_ptr = nullptr;
    m_size = 0;
    m_actualSize = 0;
    m_output = nullptr;

    if (m_isAshmem) { //如果需要共享內(nèi)存,需要使用創(chuàng)建共享共享內(nèi)存的MmapedFile
        m_ashmemFile = new MmapedFile(m_mmapID, static_cast<size_t>(size), MMAP_ASHMEM);
        //讀取匿名內(nèi)存的文件fd地址
        m_fd = m_ashmemFile->getFd();
    } else {
        m_ashmemFile = nullptr;
    }

    if (cryptKey && cryptKey->length() > 0) { //是否存在加密密鑰四敞,存在則創(chuàng)建AES加密器
        m_crypter = new AESCrypt((const unsigned char *) cryptKey->data(), cryptKey->length());
    }
    //是否直接沖文件加載
    m_needLoadFromFile = true;

    m_crcDigest = 0;
    //是否開啟進(jìn)程鎖
    m_sharedProcessLock.m_enable = m_isInterProcess;
    //是否開啟專用進(jìn)程鎖
    m_exclusiveProcessLock.m_enable = m_isInterProcess;

    // sensitive zone
    {
         //單例鎖
        SCOPEDLOCK(m_sharedProcessLock);
        loadFromFile();
    }
}

讀取文件

void MMKV::loadFromFile() {
    if (m_isAshmem) {  //是否使用匿名內(nèi)存泛源,是則初始化匿名內(nèi)存
        loadFromAshmem();
        return;
    }

    m_metaInfo.read(m_metaFile.getMemory());
   //打開文件,m_fd文件描述符
    m_fd = open(m_path.c_str(), O_RDWR | O_CREAT, S_IRWXU);  
    if (m_fd < 0) {  //文件不存在
        MMKVError("fail to open:%s, %s", m_path.c_str(), strerror(errno));
    } else {
        m_size = 0;
        struct stat st = {0};
        if (fstat(m_fd, &st) != -1) { //文件狀態(tài)是否可讀
            m_size = static_cast<size_t>(st.st_size);
        }
        // round up to (n * pagesize)
        if (m_size < DEFAULT_MMAP_SIZE || (m_size % DEFAULT_MMAP_SIZE != 0)) { //文件大小有內(nèi)容忿危,且小于1頁(yè)
            size_t oldSize = m_size;
            m_size = ((m_size / DEFAULT_MMAP_SIZE) + 1) * DEFAULT_MMAP_SIZE;
            if (ftruncate(m_fd, m_size) != 0) {  //指定文件大小輸出是否可行
                MMKVError("fail to truncate [%s] to size %zu, %s", m_mmapID.c_str(), m_size,
                          strerror(errno));
                m_size = static_cast<size_t>(st.st_size);
            }
            zeroFillFile(m_fd, oldSize, m_size - oldSize); //清空文件
        }
         //將文件映射到內(nèi)存
        m_ptr = (char *) mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0);
        //之后操作和匿名內(nèi)存中操作相同
        if (m_ptr == MAP_FAILED) {
            MMKVError("fail to mmap [%s], %s", m_mmapID.c_str(), strerror(errno));
        } else {
            memcpy(&m_actualSize, m_ptr, Fixed32Size);
            MMKVInfo("loading [%s] with %zu size in total, file size is %zu", m_mmapID.c_str(),
                     m_actualSize, m_size);
            bool loaded = false;
            if (m_actualSize > 0) {
                if (m_actualSize < m_size && m_actualSize + Fixed32Size <= m_size) {
                    if (checkFileCRCValid()) {
                        MMKVInfo("loading [%s] with crc %u sequence %u", m_mmapID.c_str(),
                                 m_metaInfo.m_crcDigest, m_metaInfo.m_sequence);
                        MMBuffer inputBuffer(m_ptr + Fixed32Size, m_actualSize, MMBufferNoCopy);
                        if (m_crypter) {
                            decryptBuffer(*m_crypter, inputBuffer);
                        }
                        //初始化Photobuf存儲(chǔ)結(jié)構(gòu)orderedMap
                        m_dic = MiniPBCoder::decodeMap(inputBuffer);
                        m_output = new CodedOutputData(m_ptr + Fixed32Size + m_actualSize,
                                                       m_size - Fixed32Size - m_actualSize);
                        loaded = true;
                    }
                }
            }
            if (!loaded) {
                SCOPEDLOCK(m_exclusiveProcessLock);

                if (m_actualSize > 0) {
                    writeAcutalSize(0);
                }
                m_output = new CodedOutputData(m_ptr + Fixed32Size, m_size - Fixed32Size);
                recaculateCRCDigest();
            }
            MMKVInfo("loaded [%s] with %zu values", m_mmapID.c_str(), m_dic.size());
        }
    }

    if (!isFileValid()) {
        MMKVWarning("[%s] file not valid", m_mmapID.c_str());
    }

    m_needLoadFromFile = false;
}

讀取匿名內(nèi)存

void MMKV::loadFromAshmem() {
    //復(fù)制MmapedFile的段地址
    m_metaInfo.read(m_metaFile.getMemory());
    //如果匿名內(nèi)存存在且匿名內(nèi)存文件為真
    if (m_fd < 0 || !m_ashmemFile) {
        MMKVError("ashmem file invalid %s, fd:%d", m_path.c_str(), m_fd);
    } else {
        //讀取匿名內(nèi)存文件大小
        m_size = m_ashmemFile->getFileSize();
        //讀取匿名內(nèi)存起始地址
        m_ptr = (char *) m_ashmemFile->getMemory();
        if (m_ptr != MAP_FAILED) {  //如果地址存在
            //復(fù)制匿名內(nèi)存出來(lái)
            memcpy(&m_actualSize, m_ptr, Fixed32Size);
            MMKVInfo("loading [%s] with %zu size in total, file size is %zu", m_mmapID.c_str(),
                     m_actualSize, m_size);
            bool loaded = false;
            if (m_actualSize > 0) {
                if (m_actualSize < m_size && m_actualSize + Fixed32Size <= m_size) {
                    if (checkFileCRCValid()) {//檢查m_ptr和m_size都設(shè)置了
                        MMKVInfo("loading [%s] with crc %u sequence %u", m_mmapID.c_str(),
                                 m_metaInfo.m_crcDigest, m_metaInfo.m_sequence);
                        //創(chuàng)造MMBuffer數(shù)據(jù)                
                        MMBuffer inputBuffer(m_ptr + Fixed32Size, m_actualSize, MMBufferNoCopy);
                        if (m_crypter) { //如果加密达箍,解密MMBuffer數(shù)據(jù)
                            decryptBuffer(*m_crypter, inputBuffer);
                        }
                        //               
                        m_dic = MiniPBCoder::decodeMap(inputBuffer);
                        m_output = new CodedOutputData(m_ptr + Fixed32Size + m_actualSize,
                                                       m_size - Fixed32Size - m_actualSize);
                       //標(biāo)志已經(jīng)讀取成功
                        loaded = true;
                    }
                }
            }
            if (!loaded) {
                 //使用范圍鎖
                SCOPEDLOCK(m_exclusiveProcessLock);

                if (m_actualSize > 0) {
                    writeAcutalSize(0);
                }
                //輸出流
                m_output = new CodedOutputData(m_ptr + Fixed32Size, m_size - Fixed32Size);
                //重新計(jì)算crc校驗(yàn)
                recaculateCRCDigest();
            }
            MMKVInfo("loaded [%s] with %zu values", m_mmapID.c_str(), m_dic.size());
        }
    }

    if (!isFileValid()) {
        MMKVWarning("[%s] ashmem not valid", m_mmapID.c_str());
    }

    m_needLoadFromFile = false;
}

說(shuō)明一下存儲(chǔ)文件的創(chuàng)建(分為文件和共享內(nèi)存)

MmapedFile::MmapedFile(const std::string &path, size_t size, bool fileType)
    : m_name(path), m_fd(-1), m_segmentPtr(nullptr), m_segmentSize(0), m_fileType(fileType) {
    if (m_fileType == MMAP_FILE) {  //文件創(chuàng)建
        //創(chuàng)建一個(gè)文件
        m_fd = open(m_name.c_str(), O_RDWR | O_CREAT, S_IRWXU);
        if (m_fd < 0) {
            MMKVError("fail to open:%s, %s", m_name.c_str(), strerror(errno));
        } else {
            //文件屬性的訪問
            struct stat st = {};
            if (fstat(m_fd, &st) != -1) {
                m_segmentSize = static_cast<size_t>(st.st_size);
            }
            if (m_segmentSize < DEFAULT_MMAP_SIZE) {
                m_segmentSize = static_cast<size_t>(DEFAULT_MMAP_SIZE);
                if (ftruncate(m_fd, m_segmentSize) != 0 || !zeroFillFile(m_fd, 0, m_segmentSize)) {
                    MMKVError("fail to truncate [%s] to size %zu, %s", m_name.c_str(),
                              m_segmentSize, strerror(errno));
                    close(m_fd);
                    m_fd = -1;
                    removeFile(m_name);
                    return;
                }
            }
            m_segmentPtr =
                (char *) mmap(nullptr, m_segmentSize, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0);
            if (m_segmentPtr == MAP_FAILED) {
                MMKVError("fail to mmap [%s], %s", m_name.c_str(), strerror(errno));
                close(m_fd);
                m_fd = -1;
                m_segmentPtr = nullptr;
            }
        }
    } else {
        //共享內(nèi)存創(chuàng)建 #define ASHMEM_NAME_DEF "/dev/ashmem"
        m_fd = open(ASHMEM_NAME_DEF, O_RDWR); 
        if (m_fd < 0) { //創(chuàng)建失敗
            MMKVError("fail to open ashmem:%s, %s", m_name.c_str(), strerror(errno));
        } else {
            if (ioctl(m_fd, ASHMEM_SET_NAME, m_name.c_str()) != 0) {  //內(nèi)存訪問io控制
                MMKVError("fail to set ashmem name:%s, %s", m_name.c_str(), strerror(errno));
            } else if (ioctl(m_fd, ASHMEM_SET_SIZE, size) != 0) {
                MMKVError("fail to set ashmem:%s, size %d, %s", m_name.c_str(), size,
                          strerror(errno));
            } else { //訪問成功
                 //獲取頁(yè)長(zhǎng)度
                m_segmentSize = static_cast<size_t>(size);
                //獲取頁(yè)指針
                m_segmentPtr = (char *) mmap(nullptr, m_segmentSize, PROT_READ | PROT_WRITE,
                                             MAP_SHARED, m_fd, 0);
                if (m_segmentPtr == MAP_FAILED) {
                    MMKVError("fail to mmap [%s], %s", m_name.c_str(), strerror(errno));
                    m_segmentPtr = nullptr;
                } else {
                    return;
                }
            }
            close(m_fd);
            m_fd = -1;
        }
    }
}

MMKV初始化內(nèi)容

第二和第三篇的分析,已經(jīng)在小專欄里刊登铺厨,請(qǐng)瀏覽https://xiaozhuanlan.com/cangwang_process查看更詳細(xì)的分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末缎玫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子解滓,更是在濱河造成了極大的恐慌赃磨,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洼裤,死亡現(xiàn)場(chǎng)離奇詭異邻辉,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門恩沛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)在扰,“玉大人,你說(shuō)我怎么就攤上這事雷客∶⒅椋” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵搅裙,是天一觀的道長(zhǎng)皱卓。 經(jīng)常有香客問我,道長(zhǎng)部逮,這世上最難降的妖魔是什么娜汁? 我笑而不...
    開封第一講書人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮兄朋,結(jié)果婚禮上掐禁,老公的妹妹穿的比我還像新娘。我一直安慰自己颅和,他們只是感情好傅事,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著峡扩,像睡著了一般蹭越。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上教届,一...
    開封第一講書人閱讀 51,708評(píng)論 1 305
  • 那天响鹃,我揣著相機(jī)與錄音,去河邊找鬼案训。 笑死买置,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的萤衰。 我是一名探鬼主播堕义,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼脆栋!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起洒擦,我...
    開封第一講書人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤椿争,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后熟嫩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體秦踪,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了椅邓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柠逞。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖景馁,靈堂內(nèi)的尸體忽然破棺而出板壮,到底是詐尸還是另有隱情,我是刑警寧澤合住,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布绰精,位于F島的核電站,受9級(jí)特大地震影響透葛,放射性物質(zhì)發(fā)生泄漏笨使。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一僚害、第九天 我趴在偏房一處隱蔽的房頂上張望硫椰。 院中可真熱鬧,春花似錦萨蚕、人聲如沸最爬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)爱致。三九已至,卻和暖如春寒随,著一層夾襖步出監(jiān)牢的瞬間糠悯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工妻往, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留互艾,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓讯泣,卻偏偏與公主長(zhǎng)得像纫普,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子好渠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容