Flutter学习指南
交互、手势和动画
UI布局和控件
熟悉Dart语言
编写第一个应用
开发环境搭建

本篇文章我们先学习 Flutter IO 相关的基础知识,然后在 Flutter学习指南:交互、手势和动画 的基础上,继续开发一个 echo 客户端。由于日常开发中 HTTP 比 socket 更常见,我们的 echo 客户端将会使用 HTTP 协议跟服务端通信。Echo 服务器也会使用 Dart 来实现。

文件

为了执行文件操作,我们可以使用 Dart 的 io 包:

import 'dart:io';复制代码

创建文件

在 Dart 里,我们通过类 File 来执行文件操作:

void foo() async {  const filepath = "path to your file";  var file = File(filepath);  try {    bool exists = await file.exists();    if (!exists) {      await file.create();    }  } catch (e) {    print(e);  }}复制代码

相对于 CPU,IO 总是很慢的,所以大部分文件操作都返回一个 Future,并在出错的时候抛出一个异常。如果你需要,也可以使用同步版本,这些方法都带一个后缀 Sync:

void foo() {  const filepath = "path to your file";  var file = File(filepath);  try {    bool exists = file.existsSync();    if (!exists) {      file.createSync();    }  } catch (e) {    print(e);  }}复制代码

async 方法使得我们可以像写同步方法一样写异步代码,同步版本的 io 方法已经没有太多使用的必要了(Dart 1 不支持 async 函数,所以同步版本的方法的存在是有必要的)。

写文件

写 String 时我们可以使用 writeAsString 和 writeAsBytes 方法:

const filepath = "path to your file";var file = File(filepath);await file.writeAsString('Hello, Dart IO');List<int> toBeWritten = [1, 2, 3];await file.writeAsBytes(toBeWritten);复制代码

如果只是为了写文件,还可以使用 openWrite 打开一个 IOSink:

void foo() async {  const filepath = "path to your file";  var file = File(filepath);  IOSink sink;  try {    sink = file.openWrite();    // 默认的写文件操作会覆盖原有内容;如果要追究内容,用 append 模式    // sink = file.openWrite(mode: FileMode.append);

    // write() 的参数是一个 Object,他会执行 obj.toString() 把转换后    // 的 String 写入文件    sink.write('Hello, Dart');    //调用 flush 后才会真的把数据写出去    await sink.flush();  } catch (e) {    print(e);  } finally {    sink?.close();  }}复制代码

读文件

读写原始的 bytes 也是相当简单的:

var msg = await file.readAsString();List<int> content = await file.readAsBytes();复制代码

和写文件类似,它还有一个 openRead 方法:

// Stream 是 async 包里的类import 'dart:async';// utf8、LineSplitter 属于 convert 包import 'dart:convert';import 'dart:io';

void foo() async {  const filepath = "path to your file";  var file = File(filepath);  try {    Stream<List<int>> stream = file.openRead();    var lines = stream        // 把内容用 utf-8 解码        .transform(utf8.decoder)        // 每次返回一行        .transform(LineSplitter());    await for (var line in lines) {      print(line);    }  } catch (e) {    print(e);  }}复制代码

最后需要注意的是,我们读写 bytes 的时候,使用的对象是 List<int>,而一个 int 在 Dart 里面有 64 位。Dart 一开始设计就是用于 Web,这部分的效率也就不那么高了。

JSON

JSON 相关的 API 放在了 convert 包里面:

import 'dart:convert';复制代码

把对象转换为 JSON

假设我们有这样一个对象:

class Point {  int x;  int y;  String description;

  Point(this.x, this.y, this.description);}复制代码

为了把他转换为 JSON,我们给他定义一个 toJson 方法(注意,不能改变他的方法签名):

class Point {  // ...

  // 注意,我们的方法只有一个语句,这个语句定义了一个 map。  // 使用这种语法的时候,Dart 会自动把这个 map 当做方法的返回值  Map<String, dynamic> toJson() => {    'x': x,    'y': y,    'desc': description  };}复制代码

接下来我们调用 json.encode 方法把对象转换为 JSON:

void main() {  var point = Point(2, 12, 'Some point');  var pointJson = json.encode(point);  print('pointJson = $pointJson');

  // List, Map 都是支持的  var points = [point, point];  var pointsJson = json.encode(points);  print('pointsJson = $pointsJson');}

// 执行后打印出:// pointJson = {"x":2,"y":12,"desc":"Some point"}// pointsJson = [{"x":2,"y":12,"desc":"Some point"},{"x":2,"y":12,"desc":"Some point"}]复制代码

把 JSON 转换为对象

首先,我们给 Point 类再加多一个构造函数:

class Point {  // ...

  Point.fromJson(Map<String, dynamic> map)      : x = map['x'], y = map['y'], description = map['desc'];

  // 为了方便后面演示,也加入一个 toString  @override  String toString() {    return "Point{x=$x, y=$y, desc=$description}";  }}复制代码

为了解析 JSON 字符串,我们可以用 json.decode 方法:

dynamic obj = json.decode(jsonString);复制代码

返回一个 dynamic 的原因在于,Dart 不知道传进去的 JSON 是什么。如果是一个 JSON 对象,返回值将是一个 Map;如果是 JSON 数组,则会返回 List<dynamic>:

void main() {  var point = Point(2, 12, 'Some point');  var pointJson = json.encode(point);  print('pointJson = $pointJson');  var points = [point, point];  var pointsJson = json.encode(points);  print('pointsJson = $pointsJson');  print('');

  var decoded = json.decode(pointJson);  print('decoded.runtimeType = ${decoded.runtimeType}');  var point2 = Point.fromJson(decoded);  print('point2 = $point2');

  decoded = json.decode(pointsJson);  print('decoded.runtimeType = ${decoded.runtimeType}');  var points2 = <Point>[];  for (var map in decoded) {    points2.add(Point.fromJson(map));  }  print('points2 = $points2');}复制代码

运行结果如下:

pointJson = {"x":2,"y":12,"desc":"Some point"}pointsJson = [{"x":2,"y":12,"desc":"Some point"},{"x":2,"y":12,"desc":"Some point"}]

decoded.runtimeType = _InternalLinkedHashMap<String, dynamic>point2 = Point{x=2, y=12, desc=Some point}decoded.runtimeType = List<dynamic>points2 = [Point{x=2, y=12, desc=Some point}, Point{x=2, y=12, desc=Some point}]复制代码

需要说明的是,我们把 Map 转化为对象时使用时定义了一个构造函数,但这个是任意的,使用静态方法、Dart 工厂方法等都是可行的。之所以限定 toJson 方法的原型,是因为 json.encode 只支持 Map、List、String、int 等内置类型。当它遇到不认识的类型时,如果没有给它设置参数 toEncodable,就会调用对象的 toJson 方法(所以方法的原型不能改变)。

HTTP

为了向服务器发送 HTTP 请求,我们可以使用 io 包里面的 HttpClient。但它实在不是那么好用,于是就有人弄出了一个 http 包。为了使用 http 包,需要修改 pubspec.yaml:

# pubspec.yamldependencies:  http: ^0.11.3+17复制代码

http 包的使用非常直接,为了发出一个 GET,可以使用 http.get 方法;对应的,还有 post、put 等。

import 'package:http/http.dart' as http;

Future<String> getMessage() async {  try {    final response = await http.get('http://www.xxx.com/yyy/zzz');    if (response.statusCode == 200) {      return response.body;    }  } catch (e) {    print('getMessage: $e');  }  return null;}复制代码

HTTP POST 的例子我们在下面实现 echo 客户端的时候再看。

使用 SQLite 数据库

包 sqflite 可以让我们使用 SQLite:

dependencies:  sqflite: any复制代码

sqflite 的 API 跟 Android 的那些非常像,下面我们直接用一个例子来演示:

import 'package:sqflite/sqflite.dart';

class Todo {  static const columnId = 'id';  static const columnTitle = 'title';  static const columnContent = 'content';

  int id;  String title;  String content;

  Todo(this.title, this.content, [this.id]);

  Todo.fromMap(Map<String, dynamic> map)      : id = map[columnId], title = map[columnTitle], content = map[columnContent];

  Map<String, dynamic> toMap() => {    columnTitle: title,    columnContent: content,  };

  @override  String toString() {    return 'Todo{id=$id, title=$title, content=$content}';  }}

void foo() async {  const table = 'Todo';  // getDatabasesPath() 的 sqflite 提供的函数  var path = await getDatabasesPath() + '/demo.db';  // 使用 openDatabase 打开数据库  var database = await openDatabase(      path,      version: 1,      onCreate: (db, version) async {        var sql ='''            CREATE TABLE $table ('            ${Todo.columnId} INTEGER PRIMARY KEY,'            ${Todo.columnTitle} TEXT,'            ${Todo.columnContent} TEXT'            )            ''';        // execute 方法可以执行任意的 SQL        await db.execute(sql);      }  );  // 为了让每次运行的结果都一样,先把数据清掉  await database.delete(table);

  var todo1 = Todo('Flutter', 'Learn Flutter widgets.');  var todo2 = Todo('Flutter', 'Learn how to to IO in Flutter.');

  // 插入数据  await database.insert(table, todo1.toMap());  await database.insert(table, todo2.toMap());

  List<Map> list = await database.query(table);  // 重新赋值,这样 todo.id 才不会为 0  todo1 = Todo.fromMap(list[0]);  todo2 = Todo.fromMap(list[1]);  print('query: todo1 = $todo1');  print('query: todo2 = $todo2');

  todo1.content += ' Come on!';  todo2.content += ' I\'m tired';  // 使用事务  await database.transaction((txn) async {    // 注意,这里面只能用 txn。直接使用 database 将导致死锁    await txn.update(table, todo1.toMap(),        // where 的参数里,我们可以使用 ? 作为占位符,对应的值按顺序放在 whereArgs

        // 注意,whereArgs 的参数类型是 List,这里不能写成 todo1.id.toString()。        // 不然就变成了用 String 和 int 比较,这样一来就匹配不到待更新的那一行了        where: '${Todo.columnId} = ?', whereArgs: [todo1.id]);    await txn.update(table, todo2.toMap(),        where: '${Todo.columnId} = ?', whereArgs: [todo2.id]);  });

  list = await database.query(table);  for (var map in list) {    var todo = Todo.fromMap(map);    print('updated: todo = $todo');  }

  // 最后,别忘了关闭数据库  await database.close();}复制代码

运行结果如下:

query: todo1 = Todo{id=1, title=Flutter, content=Learn Flutter widgets}query: todo2 = Todo{id=2, title=Flutter, content=Learn how to to IO in Flutter}updated: todo = Todo{id=1, title=Flutter, content=Learn Flutter widgets. Come on!}updated: todo = Todo{id=2, title=Flutter, content=Learn how to to IO in Flutter. I'm tired}复制代码

有 Android 经验的读者会发现,使用 Dart 编写数据库相关代码的时候舒服很多。如果读者对数据库不太熟悉,可以参考《SQL必知必会》。本篇的主要知识点到这里的就讲完了,作为练习,下面我们就一起来实现 echo 客户端的后端。

echo 客户端

HTTP 服务端

在开始之前,你可以在 GitHub 上找到上篇文章的代码,我们将在它的基础上进行开发。

git clone https://github.com/Jekton/flutter_demo.gitcd flutter_demogit checkout ux-basic复制代码

服务端架构

首先我们来看看服务端的架构(说是架构,但其实非常的简单,或者说很简陋):

import 'dart:async';import 'dart:io';

class HttpEchoServer {

  final int port;  HttpServer httpServer;  // 在 Dart 里面,函数也是 first class object,所以我们可以直接把  // 函数放到 Map 里面  Map<String, void Function(HttpRequest)> routes;

  HttpEchoServer(this.port) {    _initRoutes();  }

  void _initRoutes() {    routes = {      // 我们只支持 path 为 '/history' 和 '/echo' 的请求。      // history 用于获取历史记录;      // echo 则提供 echo 服务。      '/history': _history,      '/echo': _echo,    };  }

  // 返回一个 Future,这样客户端就能够在 start 完成后做一些事  Future start() async {    // 1. 创建一个 HttpServer    httpServer = await HttpServer.bind(InternetAddress.loopbackIPv4, port);    // 2. 开始监听客户请求    return httpServer.listen((request) {      final path = request.uri.path;      final handler = routes[path];      if (handler != null) {        handler(request);      } else {        // 给客户返回一个 404        request.response.statusCode = HttpStatus.notFound;        request.response.close();      }    });  }

  void _history(HttpRequest request) {    // ...  }

  void _echo(HttpRequest request) async {    // ...  }

  void close() async {    var server = httpServer;    httpServer = null;    await server?.close();  }}复制代码

在服务端框架里,我们把支持的所有路径都加到 routes 里面,当收到客户请求的时候,只需要直接从 routes 里取出对应的处理函数,把请求分发给他就可以了。如果读者对服务端编程没有太大兴趣或不太了解,这部分可以不用太关注。

将对象序列化为 JSON

为了把 Message 对象序列化为 JSON,这里我们对 Message 做一些小修改:

class Message {  final String msg;  final int timestamp;

  Message(this.msg, this.timestamp);  Message.create(String msg)      : msg = msg, timestamp = DateTime.now().millisecondsSinceEpoch;

  Map<String, dynamic> toJson() => {    "msg": "$msg",    "timestamp": timestamp  };

  @override  String toString() {    return 'Message{msg: $msg, timestamp: $timestamp}';  }}复制代码

这里我们加入一个 toJson 方法。下面是服务端的 _echo 方法:

class HttpEchoServer {  static const GET = 'GET';  static const POST = 'POST';

  const List<Message> messages = [];

  // ...

  _unsupportedMethod(HttpRequest request) {    request.response.statusCode = HttpStatus.methodNotAllowed;    request.response.close();  }

  void _echo(HttpRequest request) async {    if (request.method != POST) {      _unsupportedMethod(request);      return;    }

    // 获取从客户端 POST 请求的 body,更多的知识,参考    // https://www.dartlang.org/tutorials/dart-vm/httpserver    String body = await request.transform(utf8.decoder).join();    if (body != null) {      var message = Message.create(body);      messages.add(message);      request.response.statusCode = HttpStatus.ok;      // json 是 convert 包里的对象,encode 方法还有第二个参数 toEncodable。当遇到对象不是      // Dart 的内置对象时,如果提供这个参数,就会调用它对对象进行序列化;这里我们没有提供,      // 所以 encode 方法会调用对象的 toJson 方法,这个方法在前面我们已经定义了      var data = json.encode(message);      // 把响应写回给客户端      request.response.write(data);    } else {      request.response.statusCode = HttpStatus.badRequest;    }    request.response.close();  }}复制代码

HTTP 客户端

我们的 echo 服务器使用了 dart:io 包里面 HttpServer 来开发。对应的,我们也可以使用这个包里的 HttpRequest 来执行 HTTP 请求,但这里我们并不打算这么做。第三方库 http 提供了更简单易用的接口。

首先把依赖添加到 pubspec 里:

# pubspec.yamldependencies:  # ...

  http: ^0.11.3+17复制代码

客户端实现如下:

import 'package:http/http.dart' as http;

class HttpEchoClient {  final int port;  final String host;

  HttpEchoClient(this.port): host = 'http://localhost:$port';

  Future<Message> send(String msg) async {    // http.post 用来执行一个 HTTP POST 请求。    // 它的 body 参数是一个 dynamic,可以支持不同类型的 body,这里我们    // 只是直接把客户输入的消息发给服务端就可以了。由于 msg 是一个 String,    // post 方法会自动设置 HTTP 的 Content-Type 为 text/plain    final response = await http.post(host + '/echo', body: msg);    if (response.statusCode == 200) {      Map<String, dynamic> msgJson = json.decode(response.body);      // Dart 并不知道我们的 Message 长什么样,我们需要自己通过      // Map<String, dynamic> 来构造对象      var message = Message.fromJson(msgJson);      return message;    } else {      return null;    }  }}

class Message {  final String msg;  final int timestamp;

  Message.fromJson(Map<String, dynamic> json)    : msg = json['msg'], timestamp = json['timestamp'];

  // ...}复制代码

现在,让我们把他们和上一节的 UI 结合到一起。首先启动服务器,然后创建客户端:

HttpEchoServer _server;HttpEchoClient _client;

class _MessageListState extends State<MessageList> {  final List<Message> messages = [];

  @override  void initState() {    super.initState();

    const port = 6060;    _server = HttpEchoServer(port);    // initState 不是一个 async 函数,这里我们不能直接 await _server.start(),    // future.then(...) 跟 await 是等价的    _server.start().then((_) {      // 等服务器启动后才创建客户端      _client = HttpEchoClient(port);    });  }

  // ...}复制代码
class MessageListScreen extends StatelessWidget {

  @override  Widget build(BuildContext context) {    return Scaffold(      // ...

      floatingActionButton: FloatingActionButton(        onPressed: () async {          final result = await Navigator.push(              context,              MaterialPageRoute(builder: (_) => AddMessageScreen())          );          // 以下是修改了的地方          if (_client == null) return;          // 现在,我们不是直接构造一个 Message,而是通过 _client 把消息          // 发送给服务器          var msg = await _client.send(result);          if (msg != null) {            messageListKey.currentState.addMessage(msg);          } else {            debugPrint('fail to send $result');          }        },        // ...      )    );  }}复制代码

大功告成,在做了这么多工作以后,我们的应用现在是真正的 echo 客户端了,虽然看起来跟之前没什么两样。接下来,我们就做一些跟之前不一样的——把历史记录保存下来。

历史记录存储、恢复

获取应用的存储路径

为了获得应用的文件存储路径,我们引入多一个库:

# pubspec.yamldependencies:  # ...

  path_provider: ^0.4.1复制代码

通过它我们可以拿到应用的 file、cache 和 external storage 的路径:

import 'package:path_provider/path_provider.dart' as path_provider;

class HttpEchoServer {  String historyFilepath;

  Future start() async {    historyFilepath = await _historyPath();

    // ...  }

  Future<String> _historyPath() async {    // 获取应用私有的文件目录    final directory = await path_provider.getApplicationDocumentsDirectory();    return directory.path + '/messages.json';  }}复制代码

保存历史记录

class HttpEchoServer {

  void _echo(HttpRequest request) async {    // ...

    // 原谅我,为了简单,我们就多存几次吧    _storeMessages();  }

  Future<bool> _storeMessages() async {    try {      // json.encode 支持 List、Map      final data = json.encode(messages);      // File 是 dart:io 里的类      final file = File(historyFilepath);      final exists = await file.exists();      if (!exists) {        await file.create();      }      file.writeAsString(data);      return true;    // 虽然文件操作方法都是异步的,我们仍然可以通过这种方式 catch 到    // 他们抛出的异常    } catch (e) {      print('_storeMessages: $e');      return false;    }  }}复制代码

加载历史记录

class HttpEchoServer {

  // ...

  Future start() async {    historyFilepath = await _historyPath();    // 在启动服务器前先加载历史记录    await _loadMessages();    httpServer = await HttpServer.bind(InternetAddress.loopbackIPv4, port);    // ...  }

  Future _loadMessages() async {    try {      var file = File(historyFilepath);      var exists = await file.exists();      if (!exists) return;

      var content = await file.readAsString();      var list = json.decode(content);      for (var msg in list) {        var message = Message.fromJson(msg);        messages.add(message);      }    } catch (e) {      print('_loadMessages: $e');    }  }}复制代码

现在,我们来实现 _history 函数:

class HttpEchoServer {  // ...

  void _history(HttpRequest request) {    if (request.method != GET) {      _unsupportedMethod(request);      return;    }

    String historyData = json.encode(messages);    request.response.write(historyData);    request.response.close();  }}复制代码

_history 的实现很直接,我们只是把 messages 全都返回给客户端。

接下来是客户端部分:

class HttpEchoClient {

  // ...

  Future<List<Message>> getHistory() async {    try {      // http 包的 get 方法用来执行 HTTP GET 请求      final response = await http.get(host + '/history');      if (response.statusCode == 200) {        return _decodeHistory(response.body);      }    } catch (e) {      print('getHistory: $e');    }    return null;  }

  List<Message> _decodeHistory(String response) {    // JSON 数组 decode 出来是一个 <Map<String, dynamic>>[]    var messages = json.decode(response);    var list = <Message>[];    for (var msgJson in messages) {      list.add(Message.fromJson(msgJson));    }    return list;  }}

class _MessageListState extends State<MessageList> {  final List<Message> messages = [];

  @override  void initState() {    super.initState();

    const port = 6060;    _server = HttpEchoServer(port);    _server.start().then((_) {      // 我们等服务器启动后才创建客户端      _client = HttpEchoClient(port);      // 创建客户端后马上拉取历史记录      _client.getHistory().then((list) {        setState(() {          messages.addAll(list);        });      });    });  }

  // ...}复制代码

生命周期

最后需要做的是,在 APP 退出后关闭服务器。这就要求我们能够收到应用生命周期变化的通知。为了达到这个目的,Flutter 为我们提供了 WidgetsBinding 类(虽然没有 Android 的 Lifecycle 那么好用就是啦)。

// 为了使用 WidgetsBinding,我们继承 WidgetsBindingObserver 然后覆盖相应的方法class _MessageListState extends State<MessageList> with WidgetsBindingObserver {

  // ...

  @override  void initState() {    // ...    _server.start().then((_) {      // ...

      // 注册生命周期回调      WidgetsBinding.instance.addObserver(this);    });  }

  @override  void didChangeAppLifecycleState(AppLifecycleState state) {    if (state == AppLifecycleState.paused) {      var server = _server;      _server = null;      server?.close();    }  }}复制代码

现在,我们的应用是这个样子的:

flutter-echo-demo

所有的代码可以在 GitHub 上找到:

git clone https://github.com/Jekton/flutter_demo.gitcd flutter_demogit checkout io-basic复制代码

使用 SQLite 数据库

前面的实现中我们把 echo 服务器的数据存放在了文件里。这一节我们改一改,把数据存到 SQLite 中。

别忘了添加依赖:

dependencies:  sqflite: any复制代码

初始化数据库

import 'package:sqflite/sqflite.dart';

class HttpEchoServer {  // ...

  static const tableName = 'History';  // 这部分常量最好是放到 Message 的定义里。为了方便阅读,就暂且放这里吧  static const columnId = 'id';  static const columnMsg = 'msg';  static const columnTimestamp = 'timestamp';

  Database database;

  Future start() async {    await _initDatabase();

    // ...  }

  Future _initDatabase() async {    var path = await getDatabasesPath() + '/history.db';    database = await openDatabase(      path,      version: 1,      onCreate: (db, version) async {        var sql = '''            CREATE TABLE $tableName (            $columnId INTEGER PRIMARY KEY,            $columnMsg TEXT,            $columnTimestamp INTEGER            )            ''';        await db.execute(sql);      }    );  }}复制代码

加载历史记录

加载历史记录的相关代码在 _loadMessages 方法中,这里我们修改原有的实现,让它从数据库加载数据:

class HttpEchoServer {  // ...

  Future _loadMessages() async {    var list = await database.query(      tableName,      columns: [columnMsg, columnTimestamp],      orderBy: columnId,    );    for (var item in list) {      // fromJson 也适用于使用数据库的场景      var message = Message.fromJson(item);      messages.add(message);    }  }}复制代码

实际上改为使用数据库来存储后,我们并不需要把所有的消息都存放在内存中(也就是这里的 _loadMessage 是不必要的)。客户请求历史记录时,我们再按需从数据库读取数据即可。为了避免修改到程序的逻辑,这里还是继续保持一份数据在内存中。有兴趣的读者可以对程序作出相应的修改。

保存记录

记录的保存很简单,一行代码就可以搞定了:

void _echo(HttpRequest request) async {  // ...

  _storeMessage(message);}

void _storeMessage(Message msg) {  database.insert(tableName, msg.toJson());}复制代码

使用 JSON 的版本,我们每次都需要把所有的数据都保存一遍。对数据库来说,只要把收到的这一条信息存进去即可。读者也应该能够感受到,就我们的需求来说,使用 SQLite 的版本实现起来更简单,也更高效。

关闭数据库

close 方法也要做相应的修改:

void close() async {  // ...

  var db = database;  database = null;  db?.close();}复制代码

这部分代码可以查看 tag echo-db:

git clone https://github.com/Jekton/flutter_demo.gitcd flutter_demogit checkout echo-db复制代码
编程·思维·职场
欢迎扫码关注

Flutter学习指南:文件、存储和网络相关推荐

  1. flutter scrollview_简单易上手的Flutter学习指南App,2020一起来玩转Flutter吧~

    Flutter是谷歌的移动UI框架,可以快速在iOS.Android.Web和PC上构建高质量的原生用户界面. Flutter可以与现有的代码一起工作.在全世界,Flutter正在被越来越多的开发者和 ...

  2. 文件管理学习之文件存储空间分配管理

    磁盘的逻辑组织 一个物理磁盘在逻辑上可分为几个区域,分区信息存放在主引导块分区表中.分区表中保存磁盘各种分区起始和终止的磁头.柱面.扇区.总的扇区数等信息.在主引导块中有三种类型分区:主分区.扩展区和 ...

  3. Flutter学习指南:UI布局和控件,作为Android开发者

    showDialog(                 // 第一个 context 是参数名,第二个 context 是 State 的成员变量                 context: c ...

  4. 块存储、文件存储和对象存储三者的区别

    大家好,学习时间又到啦.今日我们来聊一聊块存储.文件存储和对象存储的区别.这个问题老生常谈了,在网上会出现许多的解释,可是大多数相对比较抽象一些,此次我选择用抽象和简易两种方式给各位分享一下体会心得. ...

  5. 块存储,文件存储,对象存储

    前言:根据不同的分类方式,存储也会被分成不同的类型,但是用途是一致的:存储的最终目的是存放数据. 存储的分类方式: 1.本地存储,外地存储 本地存储:就是你电脑里面内置的存储设备,比如:系统盘,机械盘 ...

  6. 计算机存储的发展(块存储,文件存储,对象存储)

    块存储 DAS SAN 文件存储 块存储和文件存储异同: 对象存储 1.对象 2.对象存储设备 3.元数据服务器(Metadata Server,MDS) 4.对象存储系统的客户端Client 三者之 ...

  7. 块存储、文件存储、对象存储这三者的区别

    参考链接 https://blog.csdn.net/wuxiaobingandbob/article/details/80178502 https://blog.csdn.net/qq_319331 ...

  8. 如何在Python中将数字文件存储在数据库中

    Databases like MySQL and PostgreSQL are all ideal for storing tables with numeric and text data. How ...

  9. 数据存储机制之文件存储

    今天我们来学习下文件存储.文件存储算是Android中最为基础的一种数据存储方式了.它比较适合用于存储一些简单的文本数据后二进制数据. 那么我们就先来看下Android是如何通过文件来保存数据的 将数 ...

最新文章

  1. 高定位精度的交通标志识别----开源了
  2. EMW 性能优化二之---并发配置
  3. java锁的有哪些_「并发编程」Java锁分类和特点有哪些
  4. 什么是mysql的游标_MySQL游标概念是什么 MySQL游标概念与用法介绍
  5. Oracle 20c 新特性:原生的 JSON 数据类型(Native JSON Datatype)
  6. 深入理解JavaScript中的属性和特性
  7. iOS中内存管理的问题——堆和栈
  8. 用shell求两个文件的差集
  9. 基于RabbitMQ RPC实现的主机异步管理
  10. dataframe groupby_python pandas获取groupby之后的数据
  11. C# 操作json 序列化 反序列化
  12. 学生作业信息管理系统
  13. 简单计算机c++代码
  14. android游戏 百度云盘下载安装,百度云盘下载,百度网盘app下载安装
  15. TrueLicense 使用JDK自带的 keytool 工具生成公私钥证书库
  16. 压在redis身上的三座大山
  17. 【破解工具】Hashcat加密破解工具
  18. Tensorflow keras入门教程
  19. 计算机和通信技术对未来的,谈计算机技术与通信技术的发展
  20. python 对excel的函数操作(2)

热门文章

  1. python list 和矩阵的切片
  2. 没解决这个7次方程问题,为何这三个数学家却很开心
  3. 用树莓派的方式打开《Bad Apple!!》原来是这样子的
  4. 皮猜按下谷歌招聘暂停键,疫情之下,「紧日子」来了
  5. 飞桨端到端开发套件揭秘:低成本开发的四大秘密武器
  6. 用飞桨做自然语言处理:神经网络语言模型应用实例
  7. WPF中如何将ListViewItem双击事件绑定到Command
  8. AppStore 拒绝审核原因:PLA 2.3
  9. Gossip算法介绍
  10. 最新 Windows 7 7100安装