SDWebImage緩存部分實(shí)現(xiàn)源碼解析

SDWebImage主要使用SDImageCache來緩存圖片,實(shí)現(xiàn)了內(nèi)存存取和磁盤存取還有一系列的處理然爆。下面分析它的源碼只损。本文分析的版本為4.4.3。首先來看一下它對(duì)開發(fā)者暴露的接口拖云。

屬性

首先我們來看一下它的屬性

#pragma mark - Properties
//配置
@property (nonatomic, nonnull, readonly) SDImageCacheConfig *config;
//最大內(nèi)存大小
@property (assign, nonatomic) NSUInteger maxMemoryCost;
//最大內(nèi)存數(shù)量
@property (assign, nonatomic) NSUInteger maxMemoryCountLimit;

這3個(gè)屬性都是可配置的屬性,其中maxMemoryCost和maxMemoryCountLimit用于配置其內(nèi)部的NSCache应又,config則負(fù)責(zé)大部分的配置,下面是它內(nèi)部的屬性宙项。

@interface SDImageCacheConfig : NSObject
//是否解壓縮圖片,默認(rèn)為YES
@property (assign, nonatomic) BOOL shouldDecompressImages;
//是否禁用iCloud備份株扛,默認(rèn)為YES
@property (assign, nonatomic) BOOL shouldDisableiCloud;
//是否緩存一份到內(nèi)存中尤筐,默認(rèn)為YES
@property (assign, nonatomic) BOOL shouldCacheImagesInMemory;
//是否額外存一份弱引用的緩存,默認(rèn)為YES
@property (assign, nonatomic) BOOL shouldUseWeakMemoryCache;
//從磁盤讀取圖片的配置項(xiàng)洞就,默認(rèn)是NSDataReadingMappedIfSafe盆繁,也就是使用文件映射內(nèi)存的方式,是不消耗內(nèi)存的
@property (assign, nonatomic) NSDataReadingOptions diskCacheReadingOptions;
//寫文件的配置項(xiàng)旬蟋,默認(rèn)是NSDataWritingAtomic油昂,也就是會(huì)覆蓋原有的文件
@property (assign, nonatomic) NSDataWritingOptions diskCacheWritingOptions;
//圖片在磁盤的最大時(shí)間,默認(rèn)是一周
@property (assign, nonatomic) NSInteger maxCacheAge;
//圖片在磁盤的最大大小,默認(rèn)是0冕碟,即沒有限制
@property (assign, nonatomic) NSUInteger maxCacheSize;
//清除磁盤緩存是基于什么清除稠腊,默認(rèn)是SDImageCacheConfigExpireTypeModificationDate,即基于圖片修改時(shí)間
@property (assign, nonatomic) SDImageCacheConfigExpireType diskCacheExpireType;
@end

可以看出SDImageCacheConfig中大多數(shù)配置都是跟磁盤相關(guān)的鸣哀。

初始化方法

//單例
+ (nonnull instancetype)sharedImageCache;
//新建一個(gè)存儲(chǔ)類架忌,如果是用init方法創(chuàng)建,默認(rèn)傳入的是default
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns;
//全能初始化方法我衬,比起上一個(gè)方法叹放,額外指定了存儲(chǔ)目錄,默認(rèn)目錄是在Cache/default的文件夾下
- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nonnull NSString *)directory NS_DESIGNATED_INITIALIZER;

其全能初始化方法的實(shí)現(xiàn)如下:

- (nonnull instancetype)initWithNamespace:(nonnull NSString *)ns
                       diskCacheDirectory:(nonnull NSString *)directory {
    if ((self = [super init])) {
        NSString *fullNamespace = [@"com.hackemist.SDWebImageCache." stringByAppendingString:ns];
        //創(chuàng)建專門讀寫磁盤的隊(duì)列挠羔,注意是并發(fā)
        _ioQueue = dispatch_queue_create("com.hackemist.SDWebImageCache", DISPATCH_QUEUE_SERIAL);
        //初始化配置config
        _config = [[SDImageCacheConfig alloc] init];
        //初始化內(nèi)存空間
        _memCache = [[SDMemoryCache alloc] initWithConfig:_config];
        _memCache.name = fullNamespace;
        //初始化存儲(chǔ)目錄
        if (directory != nil) {
            _diskCachePath = [directory stringByAppendingPathComponent:fullNamespace];
        } else {
            NSString *path = [self makeDiskCachePath:ns];
            _diskCachePath = path;
        }
        dispatch_sync(_ioQueue, ^{
            self.fileManager = [NSFileManager new];
        });
        //注冊(cè)通知井仰,大意就是在程序進(jìn)后臺(tái)和退出的時(shí)候,清理一下磁盤
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(deleteOldFiles)
                                                     name:UIApplicationWillTerminateNotification
                                                   object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(backgroundDeleteOldFiles)
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:nil];
    }
    return self;
}

其中破加,makeDiskCachePath也是個(gè)暴露的方法:

- (nullable NSString *)makeDiskCachePath:(nonnull NSString*)fullNamespace {
    NSArray<NSString *> *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    return [paths[0] stringByAppendingPathComponent:fullNamespace];
}

顯然文件的目錄基本都在Cache目錄下俱恶。

內(nèi)存存儲(chǔ)設(shè)計(jì)

這個(gè)類是使用SDMemoryCache來進(jìn)行存儲(chǔ),它繼承自NSCache范舀,而在這個(gè)類中合是,又持有了一個(gè)weakCache屬性弱引用著內(nèi)存,它實(shí)際上是在config中的shouldUseWeakMemoryCache置為YES才有效的锭环。

@interface SDMemoryCache <KeyType, ObjectType> : NSCache <KeyType, ObjectType>
@end
@interface SDMemoryCache <KeyType, ObjectType> ()
//配置
@property (nonatomic, strong, nonnull) SDImageCacheConfig *config;
//弱引用緩存
@property (nonatomic, strong, nonnull) NSMapTable<KeyType, ObjectType> *weakCache; 
//信號(hào)量的鎖
@property (nonatomic, strong, nonnull) dispatch_semaphore_t weakCacheLock; 
- (instancetype)initWithConfig:(nonnull SDImageCacheConfig *)config;
@end

首先里面的NSMapTable相當(dāng)于一個(gè)字典,他的相關(guān)知識(shí)可以參看這篇文章聪全,總的來說,它可以設(shè)置鍵和值是賦值方式辅辩,當(dāng)設(shè)置鍵的賦值方式為Copy,值的賦值方式為Strong的時(shí)候难礼,它就相當(dāng)于NSMutableDictionary。

它自身也是一個(gè)NSCache玫锋,但與父類不一樣的是蛾茉,它多了一個(gè)收到內(nèi)存警告,刪除父類所有對(duì)象的功能撩鹿。

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
}

- (instancetype)initWithConfig:(SDImageCacheConfig *)config {
    self = [super init];
    if (self) {
        //weakCache是一個(gè)鍵是強(qiáng)引用谦炬,值是弱引用的MapTable
        self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
        self.weakCacheLock = dispatch_semaphore_create(1);
        self.config = config;
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(didReceiveMemoryWarning:)
                                                     name:UIApplicationDidReceiveMemoryWarningNotification
                                                   object:nil];
    }
    return self;
}

- (void)didReceiveMemoryWarning:(NSNotification *)notification {
    //移除父類的對(duì)象
    [super removeAllObjects];
}

可以看出,在收到內(nèi)存警告的時(shí)候三痰,僅僅清除了父類的對(duì)象吧寺,并沒有清除weakCache的對(duì)象窜管,因?yàn)槭侨跻妙愋蜕⒔伲膊挥檬謩?dòng)清除。

接下來就是一些操作內(nèi)存的時(shí)候?qū)eakCache的一些同步操作方法:

//setObject的相關(guān)方法都會(huì)調(diào)到這里來幕帆,因此只需重寫這個(gè)方法
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
    [super setObject:obj forKey:key cost:g];
    if (!self.config.shouldUseWeakMemoryCache) {
        return;
    }
    if (key && obj) {
        LOCK(self.weakCacheLock);
        [self.weakCache setObject:obj forKey:key];
        UNLOCK(self.weakCacheLock);
    }
}

- (id)objectForKey:(id)key {
    //先看看自身有沒有這個(gè)值
    id obj = [super objectForKey:key];
    if (!self.config.shouldUseWeakMemoryCache) {
        return obj;
    }
    if (key && !obj) {
        //從緩存找获搏,找到的話重新設(shè)置回去
        LOCK(self.weakCacheLock);
        obj = [self.weakCache objectForKey:key];
        UNLOCK(self.weakCacheLock);
        if (obj) {
            NSUInteger cost = 0;
            if ([obj isKindOfClass:[UIImage class]]) {
                cost = SDCacheCostForImage(obj);
            }
            [super setObject:obj forKey:key cost:cost];
        }
    }
    return obj;
}
//移除的時(shí)候,weakCache也需要同步移除
- (void)removeObjectForKey:(id)key {    
    [super removeObjectForKey:key];
    if (!self.config.shouldUseWeakMemoryCache) {
        return;
    }
    if (key) {
        LOCK(self.weakCacheLock);
        [self.weakCache removeObjectForKey:key];
        UNLOCK(self.weakCacheLock);
    }
}

- (void)removeAllObjects {
    [super removeAllObjects];
    if (!self.config.shouldUseWeakMemoryCache) {
        return;
    }
    LOCK(self.weakCacheLock);
    [self.weakCache removeAllObjects];
    UNLOCK(self.weakCacheLock);
}

這一塊代碼容易讀懂,主要思想是在NSCache因?yàn)槟承┰蚯宄臅r(shí)候在內(nèi)存中仍然維持著一份弱引用常熙,只要這些弱引用的對(duì)象仍然被其他對(duì)象(比如UIImageView)所持有纬乍,那仍然會(huì)在該類中找到。

雖然這里引入了SDImageCacheConfig,但是實(shí)際上只使用了它的shouldUseWeakMemoryCache屬性裸卫,雖然代碼看上去并沒有直接設(shè)置shouldUseWeakMemoryCache這個(gè)成員屬性來得好仿贬,但是以后擴(kuò)展起來會(huì)容易一些。

存儲(chǔ)圖片方法

這個(gè)文件暴露了很多存圖片的API墓贿,大部分都會(huì)調(diào)到下面的方法中來:

- (void)storeImage:(nullable UIImage *)image
         imageData:(nullable NSData *)imageData
            forKey:(nullable NSString *)key
            toDisk:(BOOL)toDisk
        completion:(nullable SDWebImageNoParamsBlock)completionBlock {
        //沒有圖片和存儲(chǔ)鍵直接返回
    if (!image || !key) {
        if (completionBlock) {
            completionBlock();
        }
        return;
    }
    //內(nèi)存存一份
    if (self.config.shouldCacheImagesInMemory) {
        NSUInteger cost = SDCacheCostForImage(image);
        [self.memCache setObject:image forKey:key cost:cost];
    }
    
    if (toDisk) {
        dispatch_async(self.ioQueue, ^{
                //這里的data比較大茧泪,存到磁盤后需要及時(shí)釋放掉,不能讓其繼續(xù)占用內(nèi)存
            @autoreleasepool {
                NSData *data = imageData;
                if (!data && image) {
                    //如果data為nil聋袋,則轉(zhuǎn)換為data存儲(chǔ)
                    SDImageFormat format;
                    if (SDCGImageRefContainsAlpha(image.CGImage)) {
                        format = SDImageFormatPNG;
                    } else {
                        format = SDImageFormatJPEG;
                    }
                    data = [[SDWebImageCodersManager sharedInstance] encodedDataWithImage:image format:format];
                }
                //磁盤存一份队伟,這個(gè)方法會(huì)阻塞線程
                [self _storeImageDataToDisk:data forKey:key];
            }
            if (completionBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completionBlock();
                });
            }
        });
    } else {
        if (completionBlock) {
            completionBlock();
        }
    }
}

- (void)_storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
    if (!imageData || !key) {
        return;
    }
    if (![self.fileManager fileExistsAtPath:_diskCachePath]) {
        [self.fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
    }
    //返回MD5后的字符串,這一步也是耗時(shí)的
    NSString *cachePathForKey = [self defaultCachePathForKey:key];
    NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
    //寫文件
    [imageData writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil];
    if (self.config.shouldDisableiCloud) {
        //不讓該文件被iCloud備份
        [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
    }
}

所有關(guān)于磁盤的耗時(shí)操作都放在ioQueue里操作幽勒,這樣保證了主線程的正常運(yùn)行嗜侮。

獲取圖片方法

讀取圖片的方法有很多,其中先從最常用的方法講起:

- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key {
    //先從緩存讀
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        return image;
    }
    //緩存找不到啥容,就從磁盤找
    image = [self imageFromDiskCacheForKey:key];
    return image;
}

其中锈颗,imageFromMemoryCacheForKey這個(gè)方法比較簡單,無非就是從memCache中讀取而已咪惠,這里就不貼代碼了宜猜。

imageFromDiskCacheForKey這個(gè)方法的實(shí)現(xiàn)如下:

- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key {
    UIImage *diskImage = [self diskImageForKey:key];
    if (diskImage && self.config.shouldCacheImagesInMemory) {
        //重新將圖片放進(jìn)內(nèi)存
        NSUInteger cost = SDCacheCostForImage(diskImage);
        [self.memCache setObject:diskImage forKey:key cost:cost];
    }
    return diskImage;
}

- (nullable UIImage *)diskImageForKey:(nullable NSString *)key {
    //先讀取出數(shù)據(jù)
    NSData *data = [self diskImageDataForKey:key];
    //再將數(shù)據(jù)轉(zhuǎn)成圖片
    return [self diskImageForKey:key data:data];
}

從磁盤中讀取圖片主要分成2個(gè)步驟,一是從磁盤中讀取出數(shù)據(jù)硝逢,二是將數(shù)據(jù)轉(zhuǎn)化為圖片姨拥。

- (nullable NSData *)diskImageDataForKey:(nullable NSString *)key {
    if (!key) {
        return nil;
    }
    __block NSData *imageData = nil;
    //在ioQueue,阻塞當(dāng)前線程
    dispatch_sync(self.ioQueue, ^{
        imageData = [self diskImageDataBySearchingAllPathsForKey:key];
    });
    return imageData;
}

- (nullable NSData *)diskImageDataBySearchingAllPathsForKey:(nullable NSString *)key {
    //MD5字符串
    NSString *defaultPath = [self defaultCachePathForKey:key];
    NSData *data = [NSData dataWithContentsOfFile:defaultPath options:self.config.diskCacheReadingOptions error:nil];
    if (data) {
        return data;
    }
    //現(xiàn)在默認(rèn)路徑上找
    data = [NSData dataWithContentsOfFile:defaultPath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil];
    if (data) {
        return data;
    }
    //如果默認(rèn)路徑?jīng)]找到,則在其他路徑找渠鸽,這些路徑可由開發(fā)者配置
    NSArray<NSString *> *customPaths = [self.customPaths copy];
    for (NSString *path in customPaths) {
        NSString *filePath = [self cachePathForKey:key inPath:path];
        NSData *imageData = [NSData dataWithContentsOfFile:filePath options:self.config.diskCacheReadingOptions error:nil];
        if (imageData) {
            return imageData;
        }
        imageData = [NSData dataWithContentsOfFile:filePath.stringByDeletingPathExtension options:self.config.diskCacheReadingOptions error:nil];
        if (imageData) {
            return imageData;
        }
    }
    return nil;
}

- (nullable UIImage *)diskImageForKey:(nullable NSString *)key data:(nullable NSData *)data {
    return [self diskImageForKey:key data:data options:0];
}

- (nullable UIImage *)diskImageForKey:(nullable NSString *)key data:(nullable NSData *)data options:(SDImageCacheOptions)options {
    if (data) {
        //圖片解碼
        UIImage *image = [[SDWebImageCodersManager sharedInstance] decodedImageWithData:data];
        //這里主要是進(jìn)行圖片放大叫乌、動(dòng)圖的操作
        image = [self scaledImageForKey:key image:image];
        if (self.config.shouldDecompressImages) {
            BOOL shouldScaleDown = options & SDImageCacheScaleDownLargeImages;
            //解壓圖片
            image = [[SDWebImageCodersManager sharedInstance] decompressedImageWithImage:image data:&data options:@{SDWebImageCoderScaleDownLargeImagesKey: @(shouldScaleDown)}];
        }
        return image;
    } else {
        return nil;
    }
}

除了同步獲取圖片的方法,該類還提供了異步獲取圖片的方法,其原理基本是一樣的,這里僅貼出接口:

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDCacheQueryCompletedBlock)doneBlock徽缚;

他返回了一個(gè)NSOperation憨奸,開發(fā)者可以通過這個(gè)Operation中斷查找。

刪除圖片方法

刪除圖片的實(shí)現(xiàn)如下:

- (void)removeImageForKey:(nullable NSString *)key withCompletion:(nullable SDWebImageNoParamsBlock)completion {
    [self removeImageForKey:key fromDisk:YES withCompletion:completion];
}

- (void)removeImageForKey:(nullable NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(nullable SDWebImageNoParamsBlock)completion {
    if (key == nil) {
        return;
    }
    //從緩存刪除
    if (self.config.shouldCacheImagesInMemory) {
        [self.memCache removeObjectForKey:key];
    }

    if (fromDisk) {
        //在ioQueue中從磁盤中刪除
        dispatch_async(self.ioQueue, ^{
            [self.fileManager removeItemAtPath:[self defaultCachePathForKey:key] error:nil];
            if (completion) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    completion();
                });
            }
        });
    } else if (completion){
        completion();
    }
    
}

可以看出凿试,刪除圖片的操作還是比較簡單的排宰。

此外,還有清除內(nèi)存那婉、清除磁盤的方法板甘,代碼也比較簡單,這里就不貼出來了详炬。

刪除磁盤舊圖片功能實(shí)現(xiàn)

SDImageCache還可以定期刪除磁盤中的圖片盐类,其實(shí)現(xiàn)方式是在程序進(jìn)入后臺(tái)或者程序結(jié)束時(shí),調(diào)用下面這個(gè)方法:

- (void)deleteOldFilesWithCompletionBlock:(nullable SDWebImageNoParamsBlock)completionBlock {
    dispatch_async(self.ioQueue, ^{
        NSURL *diskCacheURL = [NSURL fileURLWithPath:self.diskCachePath isDirectory:YES];
            //選擇是根據(jù)修改時(shí)間還是根據(jù)創(chuàng)建時(shí)間清除老文件
        NSURLResourceKey cacheContentDateKey = NSURLContentModificationDateKey;
        switch (self.config.diskCacheExpireType) {
            case SDImageCacheConfigExpireTypeAccessDate:
                cacheContentDateKey = NSURLContentAccessDateKey;
                break;

            case SDImageCacheConfigExpireTypeModificationDate:
                cacheContentDateKey = NSURLContentModificationDateKey;
                break;

            default:
                break;
        }
        //清除文件,只需要知道文件是否是文件夾在跳、時(shí)間以及占用大小3個(gè)信息
        NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, cacheContentDateKey, NSURLTotalFileAllocatedSizeKey];
        //獲取文件的迭代
        NSDirectoryEnumerator *fileEnumerator = [self.fileManager enumeratorAtURL:diskCacheURL
                                                   includingPropertiesForKeys:resourceKeys
                                                                      options:NSDirectoryEnumerationSkipsHiddenFiles
                                                                 errorHandler:NULL];
        //得到過期的時(shí)間
        NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:-self.config.maxCacheAge];
        NSMutableDictionary<NSURL *, NSDictionary<NSString *, id> *> *cacheFiles = [NSMutableDictionary dictionary];
        NSUInteger currentCacheSize = 0;
        NSMutableArray<NSURL *> *urlsToDelete = [[NSMutableArray alloc] init];
        for (NSURL *fileURL in fileEnumerator) {
            NSError *error;
            NSDictionary<NSString *, id> *resourceValues = [fileURL resourceValuesForKeys:resourceKeys error:&error];
            //排除錯(cuò)誤的文件以及文件夾
            if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
                continue;
            }
             //如果文件過期枪萄,則添加到待刪除的數(shù)組中
            NSDate *modifiedDate = resourceValues[cacheContentDateKey];
            if ([[modifiedDate laterDate:expirationDate] isEqualToDate:expirationDate]) {
                [urlsToDelete addObject:fileURL];
                continue;
            }
            //如果沒有過期,則計(jì)算其占用大小
            NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
            currentCacheSize += totalAllocatedSize.unsignedIntegerValue;
            cacheFiles[fileURL] = resourceValues;
        }
        
        for (NSURL *fileURL in urlsToDelete) {
            [self.fileManager removeItemAtURL:fileURL error:nil];
        }

        //如果這個(gè)時(shí)候總大小仍比配置的大小要大猫妙,則按照時(shí)間刪除文件瓷翻,知道文件總大小小于配置大小的一半
        if (self.config.maxCacheSize > 0 && currentCacheSize > self.config.maxCacheSize) {
            const NSUInteger desiredCacheSize = self.config.maxCacheSize / 2;
            NSArray<NSURL *> *sortedFiles = [cacheFiles keysSortedByValueWithOptions:NSSortConcurrent                                                                   usingComparator:^NSComparisonResult(id obj1, id obj2) {                                                                       return [obj1[cacheContentDateKey] compare:obj2[cacheContentDateKey]];
                                                                     }];
            for (NSURL *fileURL in sortedFiles) {
                if ([self.fileManager removeItemAtURL:fileURL error:nil]) {
                    NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileURL];
                    NSNumber *totalAllocatedSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
                    currentCacheSize -= totalAllocatedSize.unsignedIntegerValue;

                    if (currentCacheSize < desiredCacheSize) {
                        break;
                    }
                }
            }
        }
        if (completionBlock) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completionBlock();
            });
        }
    });
}

總結(jié)

總的來說,緩存的邏輯主要復(fù)雜在磁盤的讀寫上割坠,所有的磁盤操作都放在io線程上讀取逻悠。此外,在內(nèi)存上使用NSCache+NSMapTable而不是NSDictionary存儲(chǔ)圖片韭脊,也值得我們借鑒童谒。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市沪羔,隨后出現(xiàn)的幾起案子饥伊,更是在濱河造成了極大的恐慌,老刑警劉巖蔫饰,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件琅豆,死亡現(xiàn)場離奇詭異,居然都是意外死亡篓吁,警方通過查閱死者的電腦和手機(jī)茫因,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來杖剪,“玉大人冻押,你說我怎么就攤上這事∈⒑伲” “怎么了洛巢?”我有些...
    開封第一講書人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長次兆。 經(jīng)常有香客問我稿茉,道長,這世上最難降的妖魔是什么芥炭? 我笑而不...
    開封第一講書人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任漓库,我火速辦了婚禮,結(jié)果婚禮上园蝠,老公的妹妹穿的比我還像新娘渺蒿。我一直安慰自己,他們只是感情好砰琢,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開白布蘸嘶。 她就那樣靜靜地躺著良瞧,像睡著了一般陪汽。 火紅的嫁衣襯著肌膚如雪训唱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評(píng)論 1 312
  • 那天挚冤,我揣著相機(jī)與錄音况增,去河邊找鬼。 笑死训挡,一個(gè)胖子當(dāng)著我的面吹牛澳骤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播澜薄,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼为肮,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了肤京?” 一聲冷哼從身側(cè)響起颊艳,我...
    開封第一講書人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎忘分,沒想到半個(gè)月后棋枕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡妒峦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年重斑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肯骇。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡窥浪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出笛丙,到底是詐尸還是另有隱情寒矿,我是刑警寧澤,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布若债,位于F島的核電站符相,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蠢琳。R本人自食惡果不足惜啊终,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望傲须。 院中可真熱鬧蓝牲,春花似錦、人聲如沸泰讽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至佛玄,卻和暖如春硼一,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背梦抢。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來泰國打工般贼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人奥吩。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓哼蛆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親霞赫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子腮介,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

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