Dart: 開源了一個支援 stream 介面的 GBK codec, fast_gbk

TonyBuilder發表於2019-09-20

  Dart 原生SDK預設支援UTF-8編碼,無論是File 還是HttpClient介面,預設提供的encoding 都是utf-8。

  例如,file.dart 中,openWrite方法預設encoding就是utf8,這樣生成的檔案預設就是utf-8編碼的。

  //file.dart
  IOSink openWrite({FileMode mode: FileMode.write, Encoding encoding: utf8});
複製程式碼

  本文提供了一個gbk codec, 相容原生API的stream介面,讀寫檔案和Http的 Stream API更為方便。

例如,使用stream api 生成一個gbk檔案流程如下:

import 'dart:io';
import 'package:fast_gbk/fast_gbk.dart';
void main() async {
  File output = File("gbk.txt");
  var stream = output.openWrite(encoding: gbk);
  stream.write("123");
  stream.writeln("456");
  stream.writeCharCode(0x41);
  await stream.close();
}
複製程式碼

使用方法

  • 新增依賴

pubspec.yaml 中新增依賴

dependencies: fast_gbk: ^0.1.1

  • 更新pubcache

flutter pub get

  • dart檔案中新增引用

import 'package:fast_gbk/fast_gbk.dart';

Demo

  • 最簡單的使用場景,直接編碼解碼String:
import 'package:fast_gbk/fast_gbk.dart';
void main() async {
  var encoded = gbk.encode("白日依山盡,黃河入海流");
  var decoded = gbk.decode([176, 215, 200, 213, 210, 192, 201, 189, 190, 161, 163,
                            172, 187, 198, 186, 211, 200, 235, 186, 163, 193, 247]);
}
複製程式碼
  • 讀GBK編碼的檔案
import 'dart:convert';
import 'dart:io';
import 'package:fast_gbk/fast_gbk.dart';
void main() {
  File gbkFile = File("gbkFile.txt");
  var stream = gbkFile.openRead();
  stream.transform(gbk.decoder)
      .transform(const LineSplitter())
      .listen((line) {
    stdout.writeln(line);
  });
}

複製程式碼
  • 寫GBK編碼的檔案
import 'dart:io';
import 'package:fast_gbk/fast_gbk.dart';
void main() async {
  File output = File("gbk.txt");
  var stream = output.openWrite(encoding: gbk);
  stream.write("123");
  stream.writeln("456");
  stream.writeCharCode(0x41);
  await stream.close();
}
複製程式碼
  • 解碼GBK的 HttpClient response
import 'dart:io';
import 'package:fast_gbk/fast_gbk.dart';
void main() async {
  var gbkWebUrl = "http://www.newsmth.net/nForum/#!mainpage";
  var httpClient = HttpClient();
  HttpClientRequest request = await httpClient.getUrl(Uri.parse(gbkWebUrl));
  HttpClientResponse response = await request.close();
  var responseBody = await response.transform(gbk.decoder).join();
  print(responseBody);
  httpClient.close();
}
複製程式碼
  • DIO 中使用 Gbk Codec 解碼 response:

options 設定responseDecoder

      BaseOptions options = BaseOptions();
      options.responseDecoder = gbkDecoder;
      _client = Dio(options);
複製程式碼

定義gbk Decoder

  String gbkDecoder (List<int> responseBytes, RequestOptions options,
      ResponseBody responseBody) {
    String result =  gbk.decode(responseBytes);
    return result;
  }
複製程式碼

Q&A

  • Q:又造了一個輪子?和現有的gbk codec有什麼區別?

  • A:在功能方面,現有的gbk_codec和gbk2utf8都可以支援encode和decode介面,但不支援Stream介面。

例如:response.transform(gbk.decoder), gbk_codec和gbk2utf8不能這樣使用。

在效率方面,本文作者在使用 gbk_codec 和 gbk2utf8 解析HttpResponse時都產生了卡頓,因此才重寫了這個模組:

gbk_codec 在解碼時大量使用了 String+= 拼接字元,使得介面重新整理產生了卡頓,如下火焰圖顯示重新整理介面主頁時解碼耗時了3.8s:

gbk_codec

gbk2utf8 的效率略高,同樣介面耗費240ms, 跟蹤發現gbk解碼時先轉為unicode, 然後unicode轉為utf8, utf8再轉為String。

實際上,Dart String就是按照UTF-16編碼實現的,支援unicode。gbk2utf8實際上浪費了一半的解碼時間,因此效率不高。

gbk2utf8

fast_gbk 測試結果如下,同樣介面耗時150ms,是三個codec中解碼最快速的,介面明顯不再卡頓。

fast_gbk

由於fast_gbk支援Stream介面,在大量資料編解碼時比直接encode,decode效率會更高些,使用也更方便。

  • Q:穩定麼?有沒有bug?

  • A:目前測試用例已經覆蓋了所有utf-8字元和GBK字元,如果發現有bug,程式碼已經在Github上開源,歡迎提交 issuepull request。pub連結為: pub.dev

相關文章