如何在 Java 中将很长的字符串写入 gzip 文件

how to write very long string to a gzip file in java

提问人:Shigure 提问时间:9/14/2022 最后编辑:Shigure 更新时间:9/16/2022 访问量:204

问:

我有一个很长的字符串,想写一个 gzip 文件

我尝试用来编写一个gzip文件GZIPOutputStream

但是当我使用时哪里有异常string.getBytes()

java.lang.OutOfMemoryError: Requested array size exceeds VM limit
        at java.lang.StringCoding.encode(StringCoding.java:350)
        at java.lang.String.getBytes(String.java:941)

有我的代码,我应该怎么做才能成功写入文件?

public static void way1() throws IOException {
    String filePath = "foo";
    String content = "very large string";
    try (OutputStream os = Files.newOutputStream(Paths.get(filePath));
         GZIPOutputStream gos = new GZIPOutputStream(os)) {
        gos.write(content.getBytes(StandardCharsets.UTF_8));
    }
}

public static void way2() throws IOException {
    String filePath = "foo";
    String content = "very large string";
    try (OutputStream os = Files.newOutputStream(Paths.get(filePath));
         GZIPOutputStream gos = new GZIPOutputStream(os);
         WritableByteChannel fc = Channels.newChannel(gos)) {
        fc.write(ByteBuffer.wrap(content.getBytes(StandardCharsets.UTF_8)));
    }
}
java 文件 io nio

评论

2赞 Joachim Sauer 9/14/2022
如果你把你的大文本作为一个单一的文本来处理,那么你已经把自己弄到了一个没有好解决方案的境地。这是从哪里来的?您能否提供它或其他方式来流式传输数据而不是生成单个数据?StringStringReaderString
0赞 Chaosfire 9/14/2022
这个字符串从何而来?如果它很大,你不应该一次处理它(将所有内容加载到内存中),而是分块处理。阅读一些逻辑部分,用它做点什么,如果需要,写下来,冲洗并重复。
0赞 Shigure 9/14/2022
这个字符串来自数据库,我处理了数据并生成为字符串
0赞 Shigure 9/14/2022
但是我需要一个完整的 gzip 文件,如果我对这个字符串进行分块,它将创建多个文件
0赞 Chaosfire 9/14/2022
@Shigure 保持输出流打开并继续写入它,直到完成处理。

答:

0赞 Krzysztof Cichocki 9/14/2022 #1

似乎当您将 String 转换为 byte[] 时(使用 content.getBytes(StandardCharsets.UTF_8)),它只需要 byte[] 的大量内存。与其一次将完整的 String 转换为 byte[],不如使用选择的编码从它创建一个 ByteBuffer,然后将此 ByteBuffer 写入 GZIPOutputStream,这样您就可以将所需的内存大小至少降低一半。要创建 ByteBuffer,您可以使用:

Charset charset = StandardCharsets.UTF_8; 
String content = "very large string";
ByteBuffer  byteBuffer = charset.encode(content );

ByteBuffer 的 API:https://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html 这可能很有用:如何将 ByteBuffer 的内容放入 OutputStream?

或者,您还可以增加 java 堆的内存量: 在 Java 中增加堆大小

总而言之,与您的方式非常相似2,像这样(我没有测试它)

public static void way2() throws IOException {
    String filePath = "foo";
    String content = "very large string";
    try (OutputStream os = Files.newOutputStream(Paths.get(filePath));
         GZIPOutputStream gos = new GZIPOutputStream(os);
         WritableByteChannel fc = Channels.newChannel(gos)) {
        Charset charset = StandardCharsets.UTF_8; 
       
        ByteBuffer  byteBuffer = charset.encode(content );
        fc.write(byteBuffer );
    }
}

评论

0赞 g00se 9/15/2022
我很困惑:a.你没有字符串,你在数据库中有一个值,b.该代码可能使用NIO,但它没有解决内存问题
0赞 Krzysztof Cichocki 9/16/2022
该代码处理 String,它确实解决了内存问题,为什么你认为它没有?
0赞 g00se 9/16/2022
因为字符串的原点在数据库中,根本不需要保存在内存中。
0赞 Krzysztof Cichocki 9/17/2022
但他只说了一些话,表明字符串是以某种方式从数据库中的数据中产生的,而不是直接读取的。
0赞 g00se 9/17/2022
这个字符串来自数据库,我处理了数据并生成为字符串(我的强调)
1赞 g00se 9/15/2022 #2

如果有,请尝试以下操作:ResultSet

public static void string2Zipfile(ResultSet rs, int columnIndex, Path outputFile) throws SQLException, IOException {
    try (InputStream os = rs.getBinaryStream(columnIndex)) {
        try (GZIPOutputStream gos = new GZIPOutputStream(Files.newOutputStream(outputFile))) {
            os.transferTo(gos);
        }
    }
}

评论

0赞 VGR 9/15/2022
是否可以保证使用什么字符集将数据库值转换为字节?我会使用而不是 getBinaryStream,然后写入包装 GZIPOutputStream 的 OutputStreamWriter。getCharacterStream
0赞 g00se 9/15/2022
是否可以保证使用什么字符集将数据库值转换为字节?很难知道。我们需要知道列是如何定义的,以及使用了什么 RDBMS。不会进行这样的“转换”——列值中的字节将与数据库中的字节相同。我们也不知道如果应用字符编码,哪种编码最方便。
0赞 g00se 9/15/2022
仅供参考,如果您在基于 Unix 的系统上使用 MySql,这是一个可以使用正确参数的行:mysql -p -ss -e "SELECT description FROM job WHERE id = 102" music_work | gzip -c >q.gz
0赞 VGR 9/15/2022
"...列值中的字节将与数据库中的字节相同。仅当它们一开始就处于 (var) 二进制文件中时。如果它是 (var)char 或 text 列,则有多种因素会影响字符变成字节的方式:在数据库级别和 JDBC 驱动程序中。这是一个令人困惑的混乱,最好避免。
0赞 g00se 9/15/2022
是的。我们需要知道细节
0赞 Shigure 9/15/2022 #3

我使用@Chaosfire建议,像这样编辑代码,它成功写入文件

public static void way1(List<String> originContent) throws IOException {
    String filePath = "foo";
    try (OutputStream os = Files.newOutputStream(Paths.get(filePath));
         GZIPOutputStream gos = new GZIPOutputStream(os)) {
        Lists.partition(originContent, 1000000).stream().map(part -> String.join("\r\n", part)).forEach(str -> {
            try {
                gos.write(str.getBytes(StandardCharsets.UTF_8));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });
    }
}

评论

0赞 g00se 9/15/2022
这没有意义。什么都没有List
0赞 Shigure 9/16/2022
我编辑代码,仅举例originContent
0赞 g00se 9/16/2022
同样,这并没有真正的意义,因为代码没有解决问题。巨大的字符串仍然(不必要地)在内存中List