前面在Flutter之旅:Dart語法掃尾-包訪問-泛型--異常-異步-mixin中向你們說過:
會有一篇專門介紹Dart中異步的文章,如今如約而至,我將用精緻的圖文加上生動的例子向你闡述
各位,下面一塊兒來看看吧。程序員
程序同步是按順序執行:一個任務執行完才能進入下一個任務,
就像下面的代碼,掃地用了15分鐘,而後才能燒水,必須等水開了才能洗衣服。數據庫
main() {
print("A任務: 掃地 15min");
print("B任務: 燒水 25min");
print("C任務: 洗衣服 25min");
}
複製代碼
若是把一我的看做勞動力,那麼這樣執行會減小勞動力的利用率。
對於殘酷的剝削者而言,這樣的工做方式顯然是不能讓他滿意的:
徹底能夠先燒水,開火以後去掃地,掃完地倒垃圾,而後再洗衣服,
等到水開了,停下洗衣服的動做,衝完水再去洗衣服,這纔是剝削者的思路數組
CPU就是那個勞動力,而程序員就是殘酷的剝削者。爲了讓它能賣命的工做,就產生了異步
當咱們須要鏈接網絡,讀取文件,數據庫操做等耗時操做,就像在等水燒開
你確定不想一個勞動力傻傻站那等水開吧,因此你要告訴它,如今去洗衣服,水開了再來沖水
因而就涉及到了一個問題,我怎麼知道誰燒開了呢?這是發生在將來的不肯定時間點的事件
因而須要搞點東西來標識一下,就像水開了會嗚嗚響,否則的話,一直洗衣服,還不燒乾了?bash
在讀取文件的時候,經過File對象的readXXX方法,你會驚奇的發現:
沒有Sync後綴的方法名都是一個Future對象,它代表該操做返回的是一個將來的對象
在將來的對象,如今固然還拿不到,那怎麼用呢?能夠看到Future有一個then方法微信
---->[sky_engine/lib/async/future.dart:601]----
Future<R> then<R>(FutureOr<R> onValue(T value), {Function onError});
複製代碼
該方法上註釋以下: then方法用來註冊未來完成時要調用的回調。
當這個future使用一個值完成時,將該值在[onValue]中回調。
若是這個future已經完成,那麼回調將不會當即調用,而是將在稍後的微任務中調度。
另外能夠看到一個可選參數onError,當執行錯誤時會進行錯誤回調網絡
既然知道then中能夠傳遞一個回調來獲取文件內容,那就簡單了
看下圖的結果,能夠感覺到讀取文件是異步的,文件讀取的代碼在上,運行時在下面
說明該程序在讀取文件這個耗時操做時,先執行後面代碼,讀取完成後才執行then的回調app
import 'dart:io';
main() {
var path = '/Volumes/coder/Project/Flutter/flutter_journey/lib/day6/漫感.txt';
Future<String> futureStr = File(path).readAsString();
futureStr.then((value){
print(value);
});
print("=======看看控制檯,我是第在哪裏?======");
}
複製代碼
async
和await
異步讀取文件給一個方法名加上
async
標註,就說明該方法是異步方法,其中能夠執行異步操做
好比異步讀取文件,只須要在Future對象前加上await,便可獲取將來的值。dom
import 'dart:io';
main() {
readByAsync();
print("=======看看控制檯,我是第在哪裏?======");
}
readByAsync() async{
var path = '/Volumes/coder/Project/Flutter/flutter_journey/lib/day6/漫感.txt';
var result = await File(path).readAsString();
print(result);
}
複製代碼
同步讀取就像等着燒開水,完成再去作別的事,讀取文件接收才能執行下一行代碼異步
main() {
readBySync();
print("=======看看控制檯,我是第在哪裏?======");
}
readBySync() {
var path = '/Volumes/coder/Project/Flutter/flutter_journey/lib/day6/漫感.txt';
var result = File(path).readAsStringSync();
print(result);
}
複製代碼
Stream流也不是什麼新鮮的玩意了,各大語言基本上都有流的操做,
這裏就Dart中的Stream流進行詳細的闡述。首先看Stream的幾個建立方法async
factory Stream.empty() = _EmptyStream<T>//建立一個空的流
Stream.fromFuture(Future<T> future)//由一個Future對象建立
Stream.fromFutures(Iterable<Future<T>> futures)//由多個Future對象建立
Stream.fromIterable(Iterable<T> elements)//由可迭代對象建立
Stream.periodic(Duration period,[T computation(int computationCount)])//有周期的流
複製代碼
我以爲Stream的認知中最重要的是區別它和列表有什麼不一樣,下面先親身體驗一下
普通列表遍歷
var fishes = ["A", "B", "C"];
fishes.forEach((e){
print(e);
});
print("====");
---->[打印結果]----
A
B
C
====
複製代碼
流遍歷
void byStream() {
var fishes = ["A", "B", "C"];
var stream =Stream.fromIterable(fishes);
stream.forEach((e){
print(e);
});
print("====");
}
---->[打印結果]----
====
A
B
C
複製代碼
不知有心人是否看出二者的區別:Stream在遍歷的時候竟然是異步的,這就是它和列表最大的不一樣
一個List在遍歷的那一刻,我就知道里面是什麼,有多少元素,能夠怎麼這麼操做它。
List就像後宮佳麗三千都在宮裏等你隨時操做,Stream則是後宮佳麗三千正在趕來的路上,你再急也沒辦法。
算了,換個例子,List就像魚缸,裏面盛着魚,你知道魚就在那,並且隨時能夠拿出來吃了
Stream像一條小溪,你只是知道里面的魚在向你游來,在這一刻你不能撈出它們,
何時游到你這裏也未知,對你而言它們都是你將來的財富。
話說這樣有什麼用
如今,邪惡的我在魚遊動的過程當中偷偷給A下毒,而後將來你拿到A後吃掉就傻傻的死掉
這就是Stream中的元素到達目的地以前,均可以進行控制和操做,我黑你幾條魚你也不知道。
listen
也就是站在前面的你,在等待着魚過來。說明你訂閱了這個流中的元素。
在風平浪靜,沒人下毒的狀況下,將來你必定能拿到河裏向你游來的這三條魚。
var fishes = ["A", "B", "C"];
var stream =Stream.fromIterable(fishes);
stream.listen((fish)=>print("拿到了$fish"));
---->[打印結果]----
拿到了A
拿到了B
拿到了C
複製代碼
訂閱的回調
var fishes = ["A", "B", "C"];
var stream = Stream.fromIterable(fishes);
stream.listen((fish) => print("拿到了$fish"),
onDone: () => print("已所有拿到"),//完成回調
onError: () => print("產生錯誤"),//錯誤回調
cancelOnError: false);//錯誤時是否取消訂閱
複製代碼
一旦訂閱取消成功,onDone不會回調,即便你已經拿到了最後一條魚
下面就說明你在拿到B後,你就取消訂閱,走人
var fishes = ["A", "B", "C"];
var stream = Stream.fromIterable(fishes);
var you = stream.listen(null);//你訂閱了這條小溪
you.onData((fish){//聲明魚到達你那裏你的行爲
print("拿到了$fish");
if(fish=="B"){//拿到B後,你就取消訂閱,走人
you.cancel();
}
});
you.onError((e)=>print("產生錯誤$e"));
you.onDone(()=>print('已所有拿到'));
複製代碼
裏面就只有三條魚,你感受很不爽,這時善良的管理員說,我如今就給你加
StreamController中有一個stream對象,能夠經過它進行流的操做
因爲是異步的,能夠在訂閱後繼續添加,也是不影響你對數據的獲取
就像你訂閱以後,管理員將魚放在水裏,魚也會游到你的面前。
StreamController controller = StreamController();
controller.add("A");
controller.add("B");
controller.add("C");
controller.stream.listen((fish) => print("拿到了$fish"));
controller.add("D");
controller.add("E");
controller.add("F");
controller.close();
複製代碼
邪惡的我來了,在中游截獲一條條魚。記住這幅圖,Stream流的思想就差很少了。
StreamController controller = StreamController();
controller.add("A");
controller.add("B");
controller.add("C");
controller.stream
.map((fish) {//每條魚都從我面前遊過
if (fish == "C") {
print("我已經已經對C下毒");
return "中毒的C";
}
if(fish=="D"){
print("D已經被我吃完了");
return "D的骨頭";
}
return fish;
})
.skip(2)//扔掉前兩個
.take(2)//最終只能拿兩個
.listen((fish) => print("傻傻的你拿到了$fish"));
controller.add("D");
controller.add("E");
controller.add("F");
controller.close();
---->[打印結果]----
我已經已經對C下毒
傻傻的你拿到了中毒的C
D已經被我吃完了
傻傻的你拿到了D的骨頭
複製代碼
當魚塘里加到B魚以後,你朋友和你站在一塊兒,也訂閱了,這時候他只能監聽到以後添加的。
使用broadcast方法可讓一個流被多人監聽,不然異常:Stream has already been listened to.
StreamController<String> controller = StreamController<String>.broadcast();
StreamSubscription you =
controller.stream.listen((value) => print('監聽到 $value魚游到你身邊'));
controller.sink.add("A");
controller.sink.add("B");
StreamSubscription youFriend =
controller.stream.listen((value) => print('監聽到 $value魚游到你朋友身邊'));
controller.sink.add("C");
controller.sink.add("D");
controller.close();
複製代碼
在Dart中文件的頂層爲FileSystemEntity抽象類,其下有三個孩子:
File接口,Directory接口,Link接口,其中三個各有一個私有類分別繼承之
---->[構造方法]----
Directory(String path)//從路徑
Directory.fromUri(Uri uri)//從uri
Directory.fromRawPath(Uint8List path)//從原生路徑
Uri get uri;
Directory get current;
Directory get absolute;
---->[異步操做]----
Future<Directory> create({bool recursive: false});//建立文件夾
Future<Directory> createTemp([String prefix]);//建立臨時文件夾
Future<Directory> rename(String newPath);//重命名
Stream<FileSystemEntity> list(//遍歷
{bool recursive: false, bool followLinks: true});
---->[同步操做]----
void createSync({bool recursive: false});
Directory createTempSync([String prefix]);
Directory renameSync(String newPath);
Stream<FileSystemEntity> list(
{bool recursive: false, bool followLinks: true});
複製代碼
var dir=Directory(path);
print(dir.path);//Volumes/coder/Project/Flutter/flutter_journey/lib/day6/data
print(Directory.current.path);//當前項目磁盤路徑:/Volumes/coder/Project/Flutter/flutter_journey
print(dir.absolute.path);//Volumes/coder/Project/Flutter/flutter_journey/lib/day6/data
dir.createTemp("-");//隨機建立自定義前綴的一個文件夾,
dir.list(recursive: true).forEach((e){
print(e.path);
}).then((v){
print("遍歷完畢");
});
print("----");//驗證list方法爲異步
複製代碼
文件操做相關
---->[異步操做]----
Future<File> create({bool recursive: false}); //異步建立一個文件(是否遞歸)
Future<File> rename(String newPath);//異步重命名文件
Future<File> copy(String newPath);//異步拷貝文件到新路徑
Future<RandomAccessFile> open({FileMode mode: FileMode.read});//異步打開文件
---->[同步操做]----
void createSync({bool recursive: false});//同步建立一個文件(是否遞歸)
File renameSync(String newPath);//同步重命名文件
File copySync(String newPath);//同步拷貝文件到新路徑
RandomAccessFile openSync({FileMode mode: FileMode.read});//同步打開文件
複製代碼
不知簡寫成下面的樣子你們可不能夠接受,這是Future對象的鏈式調用
咱們能夠看到create返回的還是一個Future對象,也就是說then方法的回調值還是File對象
你就能夠繼續調用相應的異步方法再進行then,再回調,再then,是否是頗有趣。
var path =
'/Volumes/coder/Project/Flutter/flutter_journey/lib/day6/data/應龍.txt';
var pathCopy =
'/Volumes/coder/Project/Flutter/flutter_journey/lib/day6/data/應龍-copy.txt';
var pathRename =
'/Volumes/coder/Project/Flutter/flutter_journey/lib/day6/data/應龍-rename.txt';
var file = File(path);
file
.create(recursive: true)
.then((file) => file.copy(pathCopy)
.then((file) => file.rename(pathRename)
.then((file)=>print("建立,拷貝,重命名完畢"))));
複製代碼
文件信息相關
這一組沒什麼好說的,顧名思義,須要的時候知道有這些API就好了
---->[異步操做]----
Future<int> length();//異步獲取文件大小
Future<DateTime> lastAccessed();//異步獲取最後訪問時間
Future setLastAccessed(DateTime time);//異步設置最後訪問時間
Future<DateTime> lastModified();//異步獲取最後修改時間
Future setLastModified(DateTime time);//異步設置最後修改時間
---->[同步操做]----
int lengthSync();//同步獲取文件大小
DateTime lastAccessedSync();//同步獲取最後訪問時間
void setLastAccessedSync(DateTime time);//同步設置最後訪問時間
DateTime lastModifiedSync();//同步獲取最後修改時間
void setLastModifiedSync(DateTime time);//異步設置最後修改時間
File get absolute;//獲取絕對文件
String get path;//獲取路徑
Directory get parent => new Directory(parentOf(path));//獲取父文件
複製代碼
文件讀寫相關
文件的讀寫可謂是重中之重
IOSink openWrite({FileMode mode: FileMode.write, Encoding encoding: utf8});
---->[異步寫操做]----
Future<File> writeAsBytes(List<int> bytes,
{FileMode mode: FileMode.write, bool flush: false});
Future<File> writeAsString(String contents,
{FileMode mode: FileMode.write,Encoding encoding: utf8,bool flush: false});
---->[同步寫操做]----
void writeAsBytesSync(List<int> bytes,
{FileMode mode: FileMode.write, bool flush: false});
void writeAsStringSync(String contents,
{FileMode mode: FileMode.write,Encoding encoding: utf8,bool flush: false});
Stream<List<int>> openRead([int start, int end]);
---->[異步讀操做]----
Future<List<int>> readAsBytes();
Future<String> readAsString({Encoding encoding: utf8});
Future<List<String>> readAsLines({Encoding encoding: utf8});
---->[同步讀操做]----
List<int> readAsBytesSync();
String readAsStringSync({Encoding encoding: utf8});
List<String> readAsLinesSync({Encoding encoding: utf8});
複製代碼
openWrite方法
其一,它返回了一個IOSink對象;其二,它就收模式和編碼兩個入參
這裏測試了一下,它能夠自動建立文件並寫入字符,注意它並不能自動建立文件夾
var path =
'/Volumes/coder/Project/Flutter/flutter_journey/lib/day6/data/應龍-openWrite.txt';
var file=File(path);
file.openWrite().write("應龍");
複製代碼
其中返回的IOSink對象有幾個方法能夠對不一樣的的類型進行寫入,好比數組
在寫入時能夠自定義分隔符
var li=["Java","Dart","Kotlin","Swift"];
file.openWrite().writeAll(li,"¥¥");
---->[結果]----
Java¥¥Dart¥¥Kotlin¥¥Swift
複製代碼
默認狀況下是
FileMode.write
,名稱寫入都會先將原來的內容清空,除此以外,還有:
FileMode.write//打開可讀寫文件,會覆蓋已有文件
FileMode.append//打開可讀寫文件,日後追加
FileMode.writeOnly//打開只寫文件,會覆蓋已有文件
FileMode.writeOnlyAppend//打開只寫文件,日後追加
複製代碼
openRead返回一個Stream<List>對象,它和Future比較像,有一個listen回調方法
它能夠回調多個將來的對象的序列 ,你能夠測試一下,它也是異步的
這裏回調出的是一個List,也就是對應的字節在碼錶中的數值集合。
var path =
'/Volumes/coder/Project/Flutter/flutter_journey/lib/day6/data/應龍-openRead.txt';
file.openRead().listen((li) => li.forEach((e) => print(String.fromCharCode(e))));
複製代碼
能夠看到openRead方法中有兩個不定參數,能夠控制讀取的起止點
至於爲何這樣作:若是一個很是大的文件經過readAsString,那麼會一次加載到內存中
若是內存不足就會崩掉,Stream就像是細水長流,一點一點進行讀取。
var path =
'/Volumes/coder/Project/Flutter/flutter_journey/lib/day6/data/應龍-openRead.txt';
file.openRead().listen((li) => li.forEach((e) => print(String.fromCharCode(e))));
複製代碼
另外的一些方法,使用上都大同小異,就不贅述了。
本文到此接近尾聲了,若是想快速嚐鮮Flutter,《Flutter七日》會是你的必備佳品;若是想細細探究它,那就跟隨個人腳步,完成一次Flutter之旅。
另外本人有一個Flutter微信交流羣,歡迎小夥伴加入,共同探討Flutter的問題,本人微信號:zdl1994328
,期待與你的交流與切磋。