1. 程式人生 > >NSINputStream輸入流的兩種解決方案(適用大檔案上傳讀取)

NSINputStream輸入流的兩種解決方案(適用大檔案上傳讀取)

眾所周知 , 移動端有時候挺受記憶體限制 , 特別是前幾年還是512M時 , 如果讀取一個幾百M的視訊 , 那麼手機就直接崩潰了.. 近兩年隨著記憶體不斷升級 , 情況已經好很多 , 大部分時候開發者已經不用考慮記憶體的問題 , 但是對於比較小眾的需求 , 比如大檔案上傳下載 , 還是需要考慮記憶體的問題 , 所以需要考慮讀取檔案時分步讀入或者以流的方式讀出 . 而後臺伺服器也會經常要求你進行分段式上傳 , 所以瞭解輸入流和逐步讀入檔案必不可少 (下載輸出流下篇文章寫)

解決方案一:

NSInputStream輸入流

舉個反面栗子: 直接一次性將一個300M的大檔案載入進記憶體

- (void
)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSString *path = [[NSBundle mainBundle]pathForResource:@"03-FMDB的簡單使用.mp4" ofType:nil]; //一次性全部轉換 NSData *data = [NSData dataWithContentsOfFile:path]; }

結果就是這樣..記憶體一下暴漲
這裡寫圖片描述

使用NSInputStream輸入流逐步讀入 , 程式碼如下

1.
//遵循代理協議 @interface ViewController ()<NSStreamDelegate> @end
2.//建立NSInputStream物件 , 配置路徑 , 加入執行迴圈等..
    NSString *path = [[NSBundle mainBundle]pathForResource:@"03-FMDB的簡單使用.mp4" ofType:nil];

    //[NSInputStream inputStreamWithURL:<#(nonnull NSURL *)#>]
    //[NSInputStream inputStreamWithData:<#(nonnull NSData *)#>]
//根據路徑建立輸入流 , 建立輸入流的方法有很多,如上 NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:path]; //設定代理 inputStream.delegate = self; //加入執行迴圈 [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; //開啟輸入流 [inputStream open];
//3.實現代理方法
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{

//    NSStreamEventOpenCompleted = 1UL << 0,     // 輸入流開啟完成
//    NSStreamEventHasBytesAvailable = 1UL << 1,  //獲取到位元組數
//    NSStreamEventHasSpaceAvailable = 1UL << 2, //有可用的空間,不知道怎麼翻譯..
//    NSStreamEventErrorOccurred = 1UL << 3,     // 發生錯誤
//    NSStreamEventEndEncountered = 1UL << 4     //輸入完成

    NSInputStream *inputStream = (NSInputStream *)aStream;

    switch (eventCode) {

            //開始輸入
        case NSStreamEventHasBytesAvailable:

        {

            //定義一個數組
            uint8_t streamData[1000000];

           //返回輸入長度
            NSUInteger length = [inputStream read:streamData maxLength:1000000];

            if (length) {

                //轉換為data
                NSData *data = [NSData dataWithBytes:streamData length:length];

                NSLog(@"%lu",(unsigned long)data.length);

            }else{


                NSLog(@"沒有資料");
            }
        }
            break;

            //異常處理
            case NSStreamEventErrorOccurred:

            NSLog(@"進行異常處理");

            break;

            //輸入完成
            case NSStreamEventEndEncountered:
        {
            //輸入流關閉處理
            [inputStream close];
            //從執行迴圈中移除
            [inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
            //置為空
            inputStream = nil;

        }
            break;
        default:
            break;
    }
}

解決方案二

操作NSFileHandle檔案控制代碼

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{

    //檔案路徑
    NSString *path = [[NSBundle mainBundle]pathForResource:@"03-FMDB的簡單使用.mp4" ofType:nil];

    //計算檔案大小
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSDictionary *attriDict = [fileManager attributesOfItemAtPath:path error:nil];

    //檔案屬性列表
    //    (lldb) po attriDict
//    {
//        NSFileCreationDate = "2016-11-08 13:54:58 +0000";
//        NSFileExtensionHidden = 0;
//        NSFileGroupOwnerAccountID = 20;
//        NSFileGroupOwnerAccountName = staff;
//        NSFileModificationDate = "2016-11-08 13:54:58 +0000";
//        NSFileOwnerAccountID = 501;
//        NSFilePosixPermissions = 420;
//        NSFileReferenceCount = 1;
//        NSFileSize = 335670302;
//        NSFileSystemFileNumber = 12755195;
//        NSFileSystemNumber = 16777218;
//        NSFileType = NSFileTypeRegular;
//    }

//獲取檔案大小
    NSString * fileSize = attriDict[NSFileSize];

     NSLog(@"-------總大小%lld",fileSize.longLongValue);

    //迴圈次數
    NSInteger times = fileSize.longLongValue/1000000;

    //開始迴圈讀取
    for (NSInteger index = 0; index < times ; index ++) {
       //建立控制代碼
    self.fileHandle = [NSFileHandle fileHandleForReadingAtPath:path];

//設定每次控制代碼偏移量
    [self.fileHandle seekToFileOffset:1000000 *(index + 1)];
        //獲取每次讀入data
    NSData *data = [self.fileHandle readDataOfLength:1000000];

    NSLog(@"-----%lu",(unsigned long)data.length);
                if (index == times - 1) {

    //關閉控制代碼
    [self.fileHandle closeFile];

        }
    }
}

總結 : 對於大檔案的操作 , 兩種方法都可以 , 並且各有利弊 . 第一種方法相對複雜 , 但是不用考慮迴圈次數 , 個人感覺效率更高一些 . 第二種寫法相較簡單 , 但是需要自己計算迴圈次數和控制代碼偏移量 , 看各自喜好 . 但是有一點 , 第二種方法 , 是在Stack Overflow上的說法 , 但是據我觀察 , 並不能解決記憶體暴漲問題 . 所以建議第一種方法 .