簡體   English   中英

保存和讀取大文件的最佳方式?

[英]Best way to save and read large files?

我正在嘗試為移動 + Windows 系統創建一個白標流媒體/雲文件管理工具。 目標是能夠查看遠程內容並使用充當文件“代理”的應用程序下載它——只有應用程序可以打開它們,下載時它會在本地加密和保存數據。 目前我已經嘗試過Hive SQLite3 ,現在我正在從頭開始Isar

以上解決方案均不適用於超過 2GB 的文件。 Hive 需要 7 分鍾來保存和加密 3GB 數據塊。 更糟糕的是,保存的文件總計超過 9GB。 SQLite 在 3.5 分鍾時做得更好,同時還減小了最終文件的大小,但這似乎也是一個太長的過程。

另一件事是在加密和保存數據處理器時,memory 和 Windows 機器上的磁盤使用率始終為 99%。 我不想想象這個過程會對 2017 年的手機造成多大的影響。

  1. 有沒有辦法在限制硬件使用的同時優化整個保存過程?
  2. 如何更快、更高效或更慢地完成這項工作,但使用率在 70% 左右,以便用戶可以在等待的同時舒適地在機器上執行其他任務?
  3. 在這種情況下,noSQL 盒解決方案是否比 SQL 結構更好?
  4. 我可以將保存數據拆分成塊,以便在不損壞文件的情況下進行更多強度較低的保存操作嗎?
  5. Netflix 或 Disney+ 如何毫不費力地保存超過 3 小時的帶有幾種字幕語言的高清電影?
  6. 關於保存下載操作以及如何用外部軟件攔截它有什么好的來源嗎? 經典下載會在下載時立即保存數據我只是不知道如何通過額外的步驟來做同樣的事情。

試試這段代碼(它使用blowfish_ecb加密算法,但也可以使用任何其他算法),注意解密時我們不必包裝原始 stream 並直接使用它( return (encrypt? makeBigChunksAndPadZeros(inStream, inStreamLength, outMap): inStream) )

class FooWidget1 extends StatefulWidget {
  @override
  State<FooWidget1> createState() => _FooWidget1State();
}

class _FooWidget1State extends State<FooWidget1> {
  final notifier = ValueNotifier(0.0);
  Duration timeSpan = Duration.zero;
  Map<String, dynamic> out = {};
  String status = '';

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.topCenter,
      child: Column(
        children: [
          ElevatedButton(
            onPressed: () async {
              final start = DateTime.now();
              int length;
              File inFile;
              File outFile;

          // case 1: encrypting from network
              // final uri = Uri.parse('http://0.0.0.0:8000/open.mp4');
              // final response = await http.Request('get', uri).send();
              // // print(response.headers);
              // length = response.contentLength!;
              // outFile = File('secret.file');
              // setState(() {
              //   status = 'encrypting\n\nsrc: [$uri]\ndst: [$outFile]\nlength: $length bytes';
              // });
              // out = await crypt(
              //   encrypt: true,
              //   key: 'foo bar key',
              //   inStream: response.stream,
              //   inStreamLength: length,
              //   outFile: outFile,
              //   notifier: notifier,
              // );
          // end of case 1

          // case 2: encrypting from file
              // inFile = File('open.mp4');
              // length = await inFile.length();
              // outFile = File('secret.file');
              // setState(() {
              //   status = 'encrypting\n\nsrc: [$inFile]\ndst: [$outFile]\nlength: $length bytes';
              // });
              // out = await crypt(
              //   encrypt: true,
              //   key: 'foo bar key',
              //   inStream: inFile.openRead(),
              //   inStreamLength: length,
              //   outFile: outFile,
              //   notifier: notifier,
              // );
          // end of case 2

          // case 3: decrypting from file
              inFile = File('secret.file');
              length = await inFile.length();
              outFile = File('open1.mp4');
              setState(() {
                status = 'decrypting\n\nsrc: [$inFile]\ndst: [$outFile]\nlength: $length bytes';
              });
              out = await crypt(
                encrypt: false,
                key: 'foo bar key',
                inStream: inFile.openRead(),
                inStreamLength: length,
                outFile: outFile,
                notifier: notifier,
                pad: 7,
              );
          // end of case 3

              timeSpan = DateTime.now().difference(start);
              setState(() {
                status = status + '\n\ntook: ${timeSpan.inMilliseconds / 1000}s\npad: ${out['pad']}';
              });
            },
            child: const Text('start crypting'),
          ),
          SizedBox.fromSize(
            size: const Size.square(100),
            child: Padding(
              padding: const EdgeInsets.all(8),
              child: AnimatedBuilder(
                animation: notifier,
                builder: (ctx, child) => CircularProgressIndicator(value: notifier.value),
              ),
            ),
          ),
          Text(status, textScaleFactor: 1.5),
        ],
      ),
    );
  }

  Future<Map<String, dynamic>> crypt({
    bool encrypt = true,
    required String key,
    required Stream<List<int>> inStream,
    required int inStreamLength,
    required File outFile,
    ValueNotifier<double>? notifier,
    int pad = 0,
  }) async {
    int readBytes = 0;
    double oldValue = 0;

    // it needs: import 'package:blowfish_ecb/blowfish_ecb.dart';
    final blowfishECB = BlowfishECB(Uint8List.fromList(utf8.encode(key)));

    List<int> mapper(List<int> data) {
      if (notifier != null && inStreamLength > 0) {
        readBytes += data.length;
        final currentValue = readBytes / inStreamLength;
        if (currentValue > oldValue + 0.01) {
          oldValue = currentValue;
          notifier.value = currentValue;
        }
      }

      if (encrypt) {
        data = blowfishECB.encode(data);
      } else {
        data = blowfishECB.decode(data);
        if (readBytes == inStreamLength && pad != 0) {
          // the last chunk with non zero [pad]
          print('crypt: removing $pad byte(s)');
          data = data.sublist(0, data.length - pad);
        }
      }
      return data;
    }

    final outMap = {
      'size': 0,
      'pad': 0,
    };

    return (encrypt? makeBigChunksAndPadZeros(inStream, inStreamLength, outMap) : inStream)
      .map(mapper)
      .pipe(outFile.openWrite())
      .then((value) => outMap..['size'] = readBytes);
  }

  Stream<List<int>> makeBigChunksAndPadZeros(Stream<List<int>> inStream, int inStreamLength, Map outMap) async* {
    // TODO make it bigger / smaller
    const chunkSize = 1 * 1024 * 1024;

    int index = 0;
    final reader = ChunkedStreamReader(inStream);
    while (true) {
      var chunk = await reader.readChunk(chunkSize);
      index += chunk.length;
      if (index != inStreamLength) {
        // normal chunk
        yield chunk;
      } else {
        // the last chunk
        if (chunk.isNotEmpty) {
          // not empty chunk: add zeroes if needed
          final pad = 8 - chunk.length % 8;
          if (pad != 8) {
            print('makeBigChunksAndPadZeros: adding $pad zero(s)');
            chunk = List.of(chunk.followedBy(List.filled(pad, 0)));
            outMap['pad'] = pad;
          }
          yield chunk;
        }
        break;
      }
    }
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM