任务是压缩/解压缩非常大的数据> 2G,单个String或ByteArray无法容纳。我的解决方案是将压缩/解压缩的数据 block 逐 block 写入文件。它有效,但速度不够快。
压缩:纯文本文件 -> gzip -> base64 编码 -> 压缩文件
解压:压缩文件 -> base64 解码 -> gunzip -> 纯文本文件
笔记本电脑测试结果,16G内存。
Created compressed file, takes 571346 millis
Created decompressed file, takes 378441 millis
代码块
public static void compress(final InputStream inputStream, final Path outputFile) throws IOException {
try (final OutputStream outputStream = new FileOutputStream(outputFile.toString());
final OutputStream base64Output = Base64.getEncoder().wrap(outputStream);
final GzipCompressorOutputStream gzipOutput = new GzipCompressorOutputStream(base64Output);
final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
reader.lines().forEach(line -> {
try {
gzipOutput.write(line.getBytes());
gzipOutput.write(System.getProperty("line.separator").getBytes());
} catch (final IOException e) {
e.printStackTrace();
}
});
}
}
public static void decompress(final InputStream inputStream, final Path outputFile) throws IOException {
try (final OutputStream outputStream = new FileOutputStream(outputFile.toString());
final GzipCompressorInputStream gzipStream = new GzipCompressorInputStream(Base64.getDecoder().wrap(inputStream));
final BufferedReader reader = new BufferedReader(new InputStreamReader(gzipStream))) {
reader.lines().forEach(line -> {
try {
outputStream.write(line.getBytes());
outputStream.write(System.getProperty("line.separator").getBytes());
} catch (final IOException e) {
e.printStackTrace();
}
});
}
}
此外,我尝试在将数据发送到文件时进行批量写入,但没有看到太大的改进。
# batch write
public static void compress(final InputStream inputStream, final Path outputFile) throws IOException {
try (final OutputStream outputStream = new FileOutputStream(outputFile.toString());
final OutputStream base64Output = Base64.getEncoder().wrap(outputStream);
final GzipCompressorOutputStream gzipOutput = new GzipCompressorOutputStream(base64Output);
final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
StringBuilder stringBuilder = new StringBuilder();
final int chunkSize = Integer.MAX_VALUE / 1000;
String line;
int counter = 0;
while((line = reader.readLine()) != null) {
counter++;
stringBuilder.append(line).append(System.getProperty("line.separator"));
if(counter >= chunkSize) {
gzipOutput.write(stringBuilder.toString().getBytes());
counter = 0;
stringBuilder = new StringBuilder();
}
}
if (counter > 0) {
gzipOutput.write(stringBuilder.toString().getBytes());
}
}
}
问题
- 寻求有关如何加快整个过程的建议
- 瓶颈是什么?
10/2/2019 更新
我又做了一些测试,结果显示base64编码是瓶颈。
public static void compress(final InputStream inputStream, final Path outputFile) throws IOException {
try (final OutputStream outputStream = new FileOutputStream(outputFile.toString());
final OutputStream base64Output = Base64.getEncoder().wrap(outputStream);
final GzipCompressorOutputStream gzipOutput = new GzipCompressorOutputStream(base64Output)) {
final byte[] buffer = new byte[4096];
int n = 0;
while (-1 != (n = inputStream.read(buffer))) {
gzipOutput.write(buffer, 0, n);
}
}
}
- 2.2G测试文件,2150万行
- 仅复制文件:~ 2 秒
- 仅 Gzip 文件:~ 12 秒
- Gzip + base64:~ 500 秒
最佳答案
首先:永远不要默认字符集,因为它不可移植。
String s = ...;
byte[] b = ...;
b = s.getBytes(StandardCharsets.UTF_8);
s = new String(b, StandardCharsets.UTF_8);
对于文本压缩,不涉及阅读器,因为它将给定一些字符集的字节转换为字符串(保存 Unicode),然后再转换回来。此外,字符串的 char 需要 2 个字节 (UTF-16),而基本 ASCII 符号需要 1 个字节。
Base64 将二进制转换为 64 个 ASCII 符号的字母表,需要 4/3 的空间。当数据必须以 XML 等格式打包传输时,不要这样做。
可以(解)压缩大文件。
final int BUFFER_SIZE = 1024 * 64;
Path textFile = Paths.get(".... .txt");
Path gzFile = textFile.resolveSibling(textFile.getFileName().toString() + ".gz");
try (OutputStream out = new GzipOutputStream(Files.newOutputStream(gzFile), BUFFER_SIZE))) {
Files.copy(textFile, out);
}
try (InputStream in = new GzipInputStream(Files.newInputStream(gzFile), BUFFER_SIZE))) {
Files.copy(in, textFile);
}
通常会忽略可选参数 BUFFER_SIZE,这可能会降低性能。
copy
可以有额外的参数来处理文件冲突。
关于java - 如何在 Java 中加速读写 base64 编码的 gzip 大文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58192991/