NSINputStream輸入流的兩種解決方案(適用大檔案上傳讀取)
阿新 • • 發佈:2019-02-15
眾所周知 , 移動端有時候挺受記憶體限制 , 特別是前幾年還是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上的說法 , 但是據我觀察 , 並不能解決記憶體暴漲問題 . 所以建議第一種方法 .