Dart的 IO 库包含了文件读写的相关类,它属于 Dart 语法标准的一部分,所以通过 Dart IO 库,无论是 Dart VM 下的脚本还是 Flutter,都是通过 Dart IO 库来操作文件的,不过和 Dart VM 相比,Flutter 有一个重要差异是文件系统路径不同,这是因为Dart VM 是运行在 PC 或服务器操作系统下,而 Flutter 是运行在移动操作系统中,他们的文件系统会有一些差异。
1. APP目录
Android 和 iOS 的应用存储目录不同,PathProvider
插件提供了一种平台透明的方式来访问设备文件系统上的常用位置。该类当前支持访问两个文件系统位置:
临时目录: 可以使用
getTemporaryDirectory()
来获取临时目录; 系统可随时清除的临时目录(缓存)。在 iOS 上,这对应于NSTemporaryDirectory()
返回的值。在 Android上,这是getCacheDir()
返回的值。文档目录: 可以使用
getApplicationDocumentsDirectory()
来获取应用程序的文档目录,该目录用于存储只有自己可以访问的文件。只有当应用程序被卸载时,系统才会清除该目录。在 iOS 上,这对应于NSDocumentDirectory
。在 Android 上,这是AppData
目录。外部存储目录:可以使用
getExternalStorageDirectory()
来获取外部存储目录,如 SD 卡;由于 iOS不支持外部目录,所以在 iOS 下调用该方法会抛出UnsupportedError
异常,而在 Android 下结果是Android SDK 中getExternalStorageDirectory
的返回值。
2. 文件操作基本使用
2.1 获取文件操作对象File
File代表一个整体的文件,他有三个构造函数,分别是:
factory File(String path)
factory File.fromUri(Uri uri)
factory File.fromRawPath(Uint8List rawPath)
var file = File('file.txt');
2.2 读取文件所有内容
文件读取本身有两种形式,一种是文本,一种是二进制。
2.2.1 读取文本内容
如果是文本文件,File提供了readAsString、readAsLines、readAsStringSync、readAsLinesSync方法,读取文本内容
readAsString 一次性读取所有文本
Future<String> readAsString({Encoding encoding: utf8});
var stringContents = await file.readAsString();
readAsLines 一行行的读取文本
Future<List<String>> readAsLines({Encoding encoding: utf8});
结果返回的是一个List,list中表示文件每行的内容
var lines = await file.readAsLines();
readAsStringSync、readAsLinesSync同步读取文本
String readAsStringSync({Encoding encoding: utf8});
List<String> readAsLinesSync({Encoding encoding: utf8});
2.2.2 读取二进制内容
如果文件是二进制,那么可以使用readAsBytes或者同步的方法readAsBytesSync:
Future<Uint8List> readAsBytes();
Uint8List readAsBytesSync();
dart中表示二进制有一个专门的类型叫做Uint8List,他实际上表示的是一个int的List。
2.3 以流的形式读取文件
上面提到的读取方式,都是一次性读取整个文件,缺点就是如果文件太大的话,可能造成内存空间的压力。
所以File为我们提供了另外一种读取文件的方法,流的形式来读取文件.
Stream<List<int>> openRead([int? start, int? end]);
示例
import 'dart:io';
import 'dart:convert';
Future<void> main() async {
var file = File('file.txt');
Stream<List<int>> inputStream = file.openRead();
var lines = utf8.decoder
.bind(inputStream)
.transform(const LineSplitter());
try {
await for (final line in lines) {
print('Got ${line.length} characters from stream');
}
print('file is now closed');
} catch (e) {
print(e);
}
}
2.4 随机访问
dart提供了open和openSync两个方法来进行随机文件读写:
Future<RandomAccessFile> open({FileMode mode: FileMode.read});
RandomAccessFile openSync({FileMode mode: FileMode.read});
- FileMode.read 只读
- FileMode.write 可读可写,如果文件存在覆盖,如果文件不存在创建
- FileMode.append 可读可写,如果文件存在在末尾追加,如果文件不存在创建
- FileMode.writeOnly 只写,如果文件存在覆盖,如果文件不存在创建
- FileModel.writeOnlyAppend 只写,如果文件存在在末尾追加,如果文件不存在创建
2.5 文件的写入
写入和文件读取一样,可以一次性写入或者获得一个写入句柄,然后再写入。
一次性写入的方法有四种,分别对应字符串和二进制
Future<File> writeAsBytes(List<int> bytes,
{FileMode mode: FileMode.write, bool flush: false});
void writeAsBytesSync(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 writeAsStringSync(String contents,
{FileMode mode: FileMode.write,
Encoding encoding: utf8,
bool flush: false});
句柄形式可以调用openWrite方法,返回一个IOSink对象,然后通过这个对象进行写入:
IOSink openWrite({FileMode mode: FileMode.write, Encoding encoding: utf8});
var logFile = File('log.txt');
var sink = logFile.openWrite();
sink.write('FILE ACCESSED ${DateTime.now()}\n');
await sink.flush();
await sink.close();
默认情况下写入是会覆盖整个文件的,但是可以通过下面的方式来更改写入模式:
var sink = logFile.openWrite(mode: FileMode.append);
2.6 处理异常
虽然dart中所有的异常都是运行时异常,但是和java一样,要想手动处理文件读写中的异常,则可以使用try,catch:
Future<void> main() async {
var config = File('config.txt');
try {
var contents = await config.readAsString();
print(contents);
} catch (e) {
print(e);
}
}
3. 示例
我们还是以计数器为例,实现在应用退出重启后可以恢复点击次数。 这里,我们使用文件来保存数据:
1.引入PathProvider插件;在pubspec.yaml文件中添加如下声明:
执行 flutter pub get
2.实现如下
class MSFileDemo extends StatefulWidget {
const MSFileDemo({
Key? key,
}) : super(key: key);
@override
State<MSFileDemo> createState() => _MSFileDemoState();
}
class _MSFileDemoState extends State<MSFileDemo> {
var _counter = 0;
@override
void initState() {
super.initState();
//从文件读取点击次数
_readCounter().then((value) {
setState(() {
_counter = value;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(
"$_counter",
style: TextStyle(
fontSize: 20, color: Colors.red, fontWeight: FontWeight.bold),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
setState(() {
_counter++;
});
_writeCounter();
},
),
);
}
// 获取本地文件路径
Future<String> _getLocalFileDir() async {
Directory tempDir = await getTemporaryDirectory();
return tempDir.path;
}
// 获取文件
Future<File> _getLocalFile() async {
String dir = await _getLocalFileDir();
return File("$dir/counter.txt");
}
// 读取内容
Future<int> _readCounter() async {
try {
File file = await _getLocalFile();
// 读取文本内容
String counterString = file.readAsStringSync();
return int.parse(counterString);
} on FileSystemException {
return 0;
}
}
// 写入内容
void _writeCounter() async {
File file = await _getLocalFile();
file.writeAsString("$_counter"); // 覆盖写入
}
}
参考://www.greatytc.com/p/92b09aaecf17
https://book.flutterchina.club/chapter11/file_operation.html