SDWebImage源碼詳解 - 緩存

SDWebImage源碼詳解 - 緩存

緩存的實(shí)現(xiàn)可以顯著的減少網(wǎng)絡(luò)流量的消耗,先將下載的圖片緩存到本地,下次獲取同一張圖片的時(shí)候,可以直接在本地緩存中獲取,而不用訪問服務(wù)器重新獲取圖片,這樣不僅可以減少網(wǎng)絡(luò)流量的消耗,并且提升了用戶體驗(yàn)(圖片加載速度快)。SDWebImage的緩存由SDImageCache類來實(shí)現(xiàn),這是一個(gè)單例類,該類負(fù)責(zé)處理內(nèi)存緩存及一個(gè)可選的磁盤緩存,其中磁盤緩存的寫操作是異步的,這樣就不會(huì)對UI操作造成影響。此外還提供了若干屬性和接口來配置和操作緩存對象。
先來看看SDImageCache的頭文件內(nèi)容

//定義三個(gè)枚舉常量,以控制緩存的存儲(chǔ)選項(xiàng)
typedef NS_ENUM(NSInteger, SDImageCacheType) {
    //不使用緩存策略,從網(wǎng)絡(luò)下載
    SDImageCacheTypeNone,
    //從磁盤中緩存中獲取圖片
    SDImageCacheTypeDisk,
    //從內(nèi)存中獲取圖片
    SDImageCacheTypeMemory
};

//回調(diào)函數(shù)類型變量
typedef void(^SDWebImageQueryCompletedBlock)(UIImage *image, SDImageCacheType cacheType);

typedef void(^SDWebImageCheckCacheCompletionBlock)(BOOL isInCache);

typedef void(^SDWebImageCalculateSizeBlock)(NSUInteger fileCount, NSUInteger totalSize);

@interface SDImageCache : NSObject
//是否在緩存之前解壓圖片,此項(xiàng)操作可以提升性能,但是會(huì)消耗較多的內(nèi)存,默認(rèn)是YES。注意:如果內(nèi)存不足,可以置為NO
@property (assign, nonatomic) BOOL shouldDecompressImages;

//是否禁止iCloud備份,默認(rèn)是YES
@property (assign, nonatomic) BOOL shouldDisableiCloud;

//是否啟用內(nèi)存緩存 默認(rèn)是YES
@property (assign, nonatomic) BOOL shouldCacheImagesInMemory;

//內(nèi)存最大容量
@property (assign, nonatomic) NSUInteger maxMemoryCost;

//內(nèi)存對象的最大數(shù)目
@property (assign, nonatomic) NSUInteger maxMemoryCountLimit;

//磁盤緩存保留的最長時(shí)間
@property (assign, nonatomic) NSInteger maxCacheAge;

//磁盤緩存最大容量,以字節(jié)為單位
@property (assign, nonatomic) NSUInteger maxCacheSize;

//返回緩存對象的單例
+ (SDImageCache *)sharedImageCache;

//以ns為緩存空間名字初始化緩存
- (id)initWithNamespace:(NSString *)ns;

//在directory目錄下,以ns為緩存空間名字初始化緩存
- (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory;

//返回磁盤緩存空間的路徑
-(NSString *)makeDiskCachePath:(NSString*)fullNamespace;

//添加只讀內(nèi)存空間路徑,一般用在圖片已經(jīng)下載置相應(yīng)的緩存目錄
- (void)addReadOnlyCachePath:(NSString *)path;

//以key為鍵值將圖片image存儲(chǔ)置緩存中
- (void)storeImage:(UIImage *)image forKey:(NSString *)key;

//以key為鍵值將圖片image存儲(chǔ)置緩存中,toDisk控制是否寫入磁盤緩存
- (void)storeImage:(UIImage *)image forKey:(NSString *)key toDisk:(BOOL)toDisk;

//以key為鍵值將圖片image存儲(chǔ)置緩存中,toDisk控制是否寫入磁盤緩存,此外如果recalculate為YES或imageData有數(shù)據(jù),則將imageData存儲(chǔ)置磁盤緩存中
- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk;

//在內(nèi)存或磁盤緩存中以key為鍵值查找圖片緩存,如果找到則執(zhí)行doneBlock回調(diào)
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;

//在內(nèi)存緩存中查找圖片緩存,并返回圖片對象
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key;

//在硬盤緩存中查找圖片緩存,并返回圖片對象
- (UIImage *)imageFromDiskCacheForKey:(NSString *)key;

//在內(nèi)存或硬盤緩存中刪除指定key緩存
- (void)removeImageForKey:(NSString *)key;

//在內(nèi)存或硬盤緩存中刪除指定key緩存,完成后執(zhí)行響應(yīng)回調(diào)
- (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion;

//在內(nèi)存或硬盤緩存中刪除指定key緩存,fromDisk控制是否刪除磁盤緩存對象
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk;
//在內(nèi)存或硬盤緩存中刪除指定key緩存,完成后執(zhí)行響應(yīng)回調(diào),fromDisk控制是否刪除磁盤緩存對象
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion;

//清除內(nèi)存緩存
- (void)clearMemory;

//清除磁盤緩存,完成后執(zhí)行回調(diào)
- (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion;
- (void)clearDisk;

//清除過期緩存,如果緩存容量超過限制,則清除部分緩存直至達(dá)到預(yù)期目標(biāo)為止
- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock;
- (void)cleanDisk;

//返回磁盤緩存的大小
- (NSUInteger)getSize;
//返回磁盤緩存對象的數(shù)目
- (NSUInteger)getDiskCount;

//異步計(jì)算磁盤緩存所需大小
- (void)calculateSizeWithCompletionBlock:(SDWebImageCalculateSizeBlock)completionBlock;

//異步查看磁盤緩存中是否存在指定key的圖片,完成后執(zhí)行回調(diào)
- (void)diskImageExistsWithKey:(NSString *)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock;
- (BOOL)diskImageExistsWithKey:(NSString *)key;

//返貨指定路徑path下的key對象的緩存路徑
- (NSString *)cachePathForKey:(NSString *)key inPath:(NSString *)path;

//返回默認(rèn)路徑下key對象的緩存路徑
- (NSString *)defaultCachePathForKey:(NSString *)key; 

從頭文件可以看出,SDWebImage的緩存對象提供了幾個(gè)屬性(緩存時(shí)間,緩存大小限制等)和若干函數(shù)來對緩存對象進(jìn)行操作(獲取、移除及清空圖片)。對于這么多的函數(shù),有些其實(shí)僅僅是調(diào)用而已,只需關(guān)注幾個(gè)主要函數(shù)即可,稍后我們將會(huì)針對幾個(gè)主要函數(shù)進(jìn)行講解。
</br>
SDWebImage緩存的主要實(shí)現(xiàn)分別采用了內(nèi)存緩存和磁盤緩存,內(nèi)存緩存使用NSCash對象來實(shí)現(xiàn),NSCache是一個(gè)類似于集合的容器。它存儲(chǔ)key-value對,這一點(diǎn)類似于NSDictionary類,NSCache類的詳細(xì)用法,這里不過多介紹,以后有機(jī)會(huì)專門介紹。磁盤緩存則使用NSFileManager對象來實(shí)現(xiàn)。圖片存儲(chǔ)的位置是位于app的Cache文件夾下。另外,SDImageCache還定義了一個(gè)串行隊(duì)列,來異步存儲(chǔ)圖片。接下我們就代碼的執(zhí)行流程來詳細(xì)的看一下代碼的實(shí)現(xiàn):

初始化緩存空間
//獲取內(nèi)存對象的單例
+ (SDImageCache *)sharedImageCache {
    static dispatch_once_t once;
    static id instance;
    dispatch_once(&once, ^{
        instance = [self new];
    });
    return instance;
}   

ImageCache單例對象由函數(shù)new來初始換,而new函數(shù)默認(rèn)調(diào)用init函數(shù)。

- (id)init {
    return [self initWithNamespace:@"default"];
}

- (id)initWithNamespace:(NSString *)ns {
    //獲取磁盤緩存的路徑
    NSString *path = [self makeDiskCachePath:ns];
    return [self initWithNamespace:ns diskCacheDirectory:path];
}

- (id)initWithNamespace:(NSString *)ns diskCacheDirectory:(NSString *)directory {
    if ((self = [super init])) {
        NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];

        //初始化PNG圖片的簽名數(shù)據(jù)
        kPNGSignatureData = [NSData dataWithBytes:kPNGSignatureBytes length:8];

        // 創(chuàng)建IO 串行對壘
        _ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);

        // 初始化最大緩存時(shí)間
        _maxCacheAge = kDefaultCacheMaxCacheAge;

        // 初始化內(nèi)存緩存
        _memCache = [[AutoPurgeCache alloc] init];
        _memCache.name = fullNamespace;

        // 保存磁盤緩存的目錄路徑
        if (directory != nil) {
            _diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
        } else {
            NSString *path = [self makeDiskCachePath:ns];
            _diskCachePath = path;
        }

        // 設(shè)置默認(rèn)屬性
        _shouldDecompressImages = YES;
        _shouldCacheImagesInMemory = YES;
        _shouldDisableiCloud = YES;

        dispatch_sync(_ioQueue, ^{
            _fileManager = [NSFileManager new];
        });
    
#if TARGET_OS_IPHONE
        // 注冊系統(tǒng)通知事件
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(clearMemory)
                                                     name:UIApplicationDidReceiveMemoryWarningNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(cleanDisk)
                                                     name:UIApplicationWillTerminateNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(backgroundCleanDisk)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];
#endif
    }

    return self;
}

通過代碼可以看出,ImageCache對象的初始化工作,分別創(chuàng)建了內(nèi)存緩存空間和磁盤緩存空間,這里面有一個(gè)函數(shù)-(NSString *)makeDiskCachePath:(NSString*)fullNamespace木有出現(xiàn),這個(gè)函數(shù)的主要作用就是返回app的緩存目錄。

-(NSString *)makeDiskCachePath:(NSString*)fullNamespace{
    //獲取app的緩存文件夾
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    //返回緩存文件夾下以fullNamespace命名的路徑
    return [paths[0] stringByAppendingPathComponent:fullNamespace];
}

保存圖片

雖然ImageCache對外提供了許多保存圖片置緩存的函數(shù),但是這么多函數(shù)都調(diào)用一個(gè)基礎(chǔ)函數(shù)- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk;,具體實(shí)現(xiàn)如下:

- (void)storeImage:(UIImage *)image recalculateFromImage:(BOOL)recalculate imageData:(NSData *)imageData forKey:(NSString *)key toDisk:(BOOL)toDisk {
    if (!image || !key) {
        return;
    }
    // 如果保存置內(nèi)存緩存屬性為YES,則將圖片保留在內(nèi)存緩存中
    if (self.shouldCacheImagesInMemory) {
        NSUInteger cost = SDCacheCostForImage(image);
        [self.memCache setObject:image forKey:key cost:cost];
    }
    
    //如果需要保存在磁盤緩存中,則將寫人磁盤緩存的隊(duì)列放入創(chuàng)建的串行隊(duì)列ioQueue中
    if (toDisk) {
        dispatch_async(self.ioQueue, ^{
            NSData *data = imageData;
            
            //如果recalculate為YES或者data數(shù)據(jù)為空,但是image有數(shù)據(jù),則對iamge圖片做處理
            //如果recalculate為YES并且data數(shù)據(jù)非空,則直接對data數(shù)據(jù)進(jìn)行保存
            if (image && (recalculate || !data)) {
#if TARGET_OS_IPHONE
                 // 需要確定圖片是PNG還是JPEG。PNG圖片容易檢測,因?yàn)橛幸粋€(gè)唯一簽名。
                         // PNG圖像的前8個(gè)字節(jié)總是包含以下值:137 80 78 71 13 10 26 10
                // 在imageData為nil的情況下假定圖像為PNG。我們將其當(dāng)作PNG以避免丟失透明度。
                        //而當(dāng)有圖片數(shù)據(jù)時(shí),我們檢測其前綴,確定圖片的類型
                int alphaInfo = CGImageGetAlphaInfo(image.CGImage);
                BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
                                  alphaInfo == kCGImageAlphaNoneSkipFirst ||
                                  alphaInfo == kCGImageAlphaNoneSkipLast);
                BOOL imageIsPng = hasAlpha;

                // But if we have an image data, we will look at the preffix
                if ([imageData length] >= [kPNGSignatureData length]) {
                    imageIsPng = ImageDataHasPNGPreffix(imageData);
                }

                if (imageIsPng) {
                    data = UIImagePNGRepresentation(image);
                }
                else {
                    data = UIImageJPEGRepresentation(image, (CGFloat)1.0);
                }
#else
                data = [NSBitmapImageRep representationOfImageRepsInArray:image.representations usingType: NSJPEGFileType properties:nil];
#endif
            }
            //創(chuàng)建緩存文件并存儲(chǔ)圖片
            if (data) {
                //創(chuàng)建保留緩存文件的上層目錄
                if (![_fileManager fileExistsAtPath:_diskCachePath]) {
                    [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
                }

                //以圖片的URL做MD5轉(zhuǎn)換后的文件名創(chuàng)建緩存文件
                NSString *cachePathForKey = [self defaultCachePathForKey:key];
                NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
                [_fileManager createFileAtPath:cachePathForKey contents:data attributes:nil];

                // 是否啟用iCloud云備份
                if (self.shouldDisableiCloud) {
                    [fileURL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil];
                }
            }
        });
    }
}

查詢圖片

ImageCache對外提供了三個(gè)查詢緩存圖片的接口函數(shù)


//在內(nèi)存和磁盤緩存中查找key指定的圖片
- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;

//在內(nèi)存緩存中查找key指定的圖片
- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key;

//先在內(nèi)存緩存中查找,然后在磁盤緩存中查找key指定的圖片
- (UIImage *)imageFromDiskCacheForKey:(NSString *)key;

這里看一下第一個(gè)函數(shù)的實(shí)現(xiàn),其他類似

- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock {
    if (!doneBlock) {
        return nil;
    }

    if (!key) {
        doneBlock(nil, SDImageCacheTypeNone);
        return nil;
    }

    // 先在內(nèi)存緩存中查找
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        doneBlock(image, SDImageCacheTypeMemory);
        return nil;
    }
    //如果內(nèi)存緩存中沒有找到,則去磁盤緩存中去查找- (UIImage *)diskImageForKey:(NSString *)key
    //在磁盤緩存中找到后,同時(shí)更新置內(nèi)存緩存中
    //有回調(diào)則調(diào)用doneBlock回調(diào)
    NSOperation *operation = [NSOperation new];
    dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) {
            return;
        }

        @autoreleasepool {
            UIImage *diskImage = [self diskImageForKey:key];
            if (diskImage && self.shouldCacheImagesInMemory) {
                NSUInteger cost = SDCacheCostForImage(diskImage);
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }

            dispatch_async(dispatch_get_main_queue(), ^{
                doneBlock(diskImage, SDImageCacheTypeDisk);
            });
        }
    });

    return operation;
}

移除圖片

ImageCache對外提供了四個(gè)刪除緩存圖片的函數(shù),

- (void)removeImageForKey:(NSString *)key;
- (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion;
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk;
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion;

移除函數(shù)比較簡單,也有一個(gè)基礎(chǔ)函數(shù)- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion;,這個(gè)函數(shù)比較簡單,刪除內(nèi)存緩存,刪除磁盤下的緩存文件,看一看代碼就明白什么意思,這里就不過多說明

清理緩存

清理緩存圖片的清理操作有內(nèi)存清理和磁盤緩存清理,而磁盤緩存又可以分為完全清空和部分清理。完全清空操作是直接把緩存的文件夾移除,清空操作有以下三個(gè)方法:

- (void)clearMemory;
- (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion;
- (void)clearDisk

這三個(gè)函數(shù)比較簡單,也不過多介紹
接下來我們詳細(xì)介紹一下部分清理,部分清空針對磁盤緩存,根據(jù)我們設(shè)定的一些參數(shù)值來移除一些文件,這里主要有兩個(gè)指標(biāo):文件的緩存有效期及最大緩存空間大小。文件的緩存有效期可以通過maxCacheAge屬性來設(shè)置,默認(rèn)是1周的時(shí)間。如果文件的緩存時(shí)間超過這個(gè)時(shí)間值,則將其移除。而最大緩存空間大小是通過maxCacheSize屬性來設(shè)置的,如果所有緩存文件的總大小超過這一大小,則會(huì)按照文件最后修改時(shí)間的逆序排序,循環(huán)移除那些較早的文件,直到磁盤緩存的實(shí)際大小小于或等于我們設(shè)置的空間預(yù)設(shè)目標(biāo),這里設(shè)為最大緩存大小的一半。清理的操作在-cleanDiskWithCompletionBlock:方法中,其實(shí)現(xiàn)如下:

- (void)cleanDiskWithCompletionBlock:(SDWebImageNoParamsBlock)completionBlock {
    dispatch_async(self.ioQueue, ^{
        NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
        NSArray *resourceKeys = @[NSURLIsDirectoryKey, NSURLContentModificationDateKey, NSURLTotalFileAllocatedSizeKey];

        // 該枚舉器預(yù)先獲取緩存文件的有用的屬性,文件夾,修改時(shí)間,文件大小
        NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtURL:diskCacheURL
                                                   includingPropertiesForKeys:resourceKeys
                                                                      options:NSDirectoryEnumerationSkipsHiddenFiles
                                                                 errorHandler:NULL];

        NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.maxCacheAge];
        NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary];
        NSUInteger currentCacheSize = 0;

        // 枚舉緩存文件夾中所有文件,
            //該迭代有兩個(gè)目的:移除比過期日期更老的文件;存儲(chǔ)文件屬性以備后面執(zhí)行基于緩存大小的清理操作
        NSMutableArray *urlsToDelete = [[NSMutableArray alloc] init];
        for (NSURL *fileURL in fileEnumerator) {
            NSDictionary *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:NULL];

            // 跳過文件夾
            if ([resourceValues[NSURLIsDirectoryKey] boolValue]) {
                continue;
            }

            // 將需要?jiǎng)h除的文件,加入需要?jiǎng)h除的數(shù)組urlsToDelete中
            NSDate *modificationDate = resourceValues[NSURLContentModificationDateKey];
            if ([[modificationDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
                [urlsToDelete addObject:fileURL];
                continue;
            }

            //存儲(chǔ)有效期內(nèi)的文件大小,留作備用
            NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
            currentCacheSize += [totalAllocatedSize unsignedIntegerValue];
            [cacheFiles setObject:resourceValues forKey:fileURL];
        }
        
        //刪除過期緩存
        for (NSURL *fileURL in urlsToDelete) {
            [_fileManager removeItemAtURL:fileURL error:nil];
        }

        // 如果磁盤緩存的大小大于我們配置的最大大小,則執(zhí)行基于文件大小的清理,首先刪除最老的文件
        if (self.maxCacheSize > 0 && currentCacheSize > self.maxCacheSize) {
            // 以設(shè)置的最大緩存大小的一半作為清理目標(biāo)
            const NSUInteger desiredCacheSize = self.maxCacheSize / 2;

            // 按照最后修改時(shí)間來排序剩下的緩存文件
            NSArray *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent
                                                            usingComparator:^NSComparisonResult(id obj1, id obj2) {
                                                                return [obj1[NSURLContentModificationDateKey] compare:obj2[NSURLContentModificationDateKey]];
                                                            }];

            // 循環(huán)刪除文件,直到緩存總大小降到我們期望的大小
            for (NSURL *fileURL in sortedFiles) {
                if ([_fileManager removeItemAtURL:fileURL error:nil]) {
                    NSDictionary *resourceValues = cacheFiles[fileURL];
                    NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
                    currentCacheSize -= [totalAllocatedSize unsignedIntegerValue];

                    if (currentCacheSize < desiredCacheSize) {
                        break;
                    }
                }
            }
        }
        //有回調(diào)則執(zhí)行回調(diào)
        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock();
            });
        }
    });
}

到這里緩存的實(shí)現(xiàn)就講解的差不多了,這里我們主要分析了SDWebImage的SDImageCache緩存類的相關(guān)操作,著重介紹了幾個(gè)主要的操作,另外SDImageCache還提供了一些其他的輔助方法如獲取緩存大小、緩存中圖片的數(shù)量、判斷緩存中是否存在某個(gè)key指定的圖片,具體的實(shí)現(xiàn)可以參照源碼,實(shí)現(xiàn)都不怎么復(fù)雜。
</br>
下一節(jié)我們主要介紹一下異步下載器的實(shí)現(xiàn)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,963評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,348評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,083評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,706評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,442評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,802評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,795評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,983評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,542評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,287評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,486評論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,030評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,710評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,116評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,412評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,224評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,462評論 2 378

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