decodeBytes method
Decompress the given bytes with the xz format.
A malformed or truncated archive yields whatever was decoded before the
failure, with nothing to say that it is not the whole file. Set
throwOnError to get an ArchiveException instead, which is the only
way this method can report a failure: unlike decodeStream it has no
return value to spare for one.
verify checks the checksum stored with each block, catching damage that
decodes without complaint. It costs time and changes only whether a
failure is noticed, never what a successful decode returns. A block whose
check does not match is a failure like any other, so without
throwOnError the result is still the bytes up to and including it,
exactly as decodeStream would have left them in its output.
throwOnError governs how a bad archive is reported and nothing else.
Arguments that cannot be honoured throw ArgumentError whichever way it
is set, and so does maxPreallocateSize on this class.
Pass multithread to decode on isolates. The call then returns
immediately, the return value is an empty list, and the result
arrives through XZMultithreadOptions.onDone:
final completer = Completer<Uint8List>();
XZDecoder().decodeBytes(compressed,
multithread: XZMultithreadOptions(onDone: completer.complete));
final data = await completer.future;
throwOnError keeps working there. The exception cannot be thrown at the
caller, whose stack is long gone by then, so it is handed to
XZMultithreadOptions.onError instead, and XZMultithreadOptions.onDone
is not called at all. Without throwOnError a failed decode reaches
XZMultithreadOptions.onDone as the partial output, exactly as it is
returned here.
Multithreading only pays off for an archive written in several blocks,
which is what xz --block-size=... produces; a single block archive
cannot be split and is simply decoded on one isolate. Measured on a 1.1
GB archive of six 192 MB blocks:
| call | peak memory | time |
|---|---|---|
no multithread |
3.0 GB | 16.7 s |
with multithread |
3.9 GB | 8.0 s |
with multithread, six workers |
4.4 GB | 5.1 s |
This is the more expensive of the two methods, and by some distance: the
archive and the decoded output both stay in memory, and every worker holds
a copy of the block it is decoding, because bytes handed over cannot be
read from anywhere else. decodeStream over an InputFileStream and an
OutputFileStream decodes the same archive in the same time using well
under a third of the memory, and is worth preferring whenever the data is
on disk anyway.
The default XZMultithreadOptions.memoryBudget allowed three workers here; raising it buys the six worker row.
With maxOutputSize, an ArchiveException is thrown as soon as the
decoded data would pass that many bytes, whatever throwOnError is. With
multithread it goes to XZMultithreadOptions.onError.
Implementation
Uint8List decodeBytes(List<int> data,
{bool verify = false,
bool throwOnError = false,
int? maxOutputSize,
XZMultithreadOptions<Uint8List>? multithread}) {
final bytes = data is Uint8List ? data : Uint8List.fromList(data);
_checkMaxOutputSize(maxOutputSize);
if (multithread == null) {
return _decodeBytes(bytes, verify, throwOnError, maxOutputSize);
}
_checkOptions(multithread, throwOnError);
if (!xzIsolatesSupported) {
// No isolates here, so this blocks the caller, but the result is still
// delivered the way the caller asked for it.
_report(
multithread,
() => _decodeBytes(bytes, verify, throwOnError, maxOutputSize),
Uint8List(0));
return Uint8List(0);
}
_reportAsync(
multithread,
() => _decodeBytesOnIsolates(
bytes, verify, throwOnError, maxOutputSize, multithread),
Uint8List(0));
return Uint8List(0);
}