Flutter 之 文件操作(二十九)

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容