Asier Gomez
Asier Gomez

Reputation: 6558

Java 7zip compression of a String

I would like to compress an String manually defined to 7z in Java. So then I could convert it to base64. I found many examples compressing files to 7z and then saving into new File.

I just try the next code, and it takes correctly the file and compress it:

private static void addToArchiveCompression(SevenZOutputFile out, File file, String dir) throws IOException {
        String name = dir + File.separator + file.getName();
        if (file.isFile()){
            SevenZArchiveEntry entry = out.createArchiveEntry(file, name);
            out.putArchiveEntry(entry);

            FileInputStream in = new FileInputStream(file);
            byte[] b = new byte[1024];
            int count = 0;
            while ((count = in.read(b)) > 0) {
                out.write(b, 0, count);
            }
            out.closeArchiveEntry();

        } else if (file.isDirectory()) {
            File[] children = file.listFiles();
            if (children != null){
                for (File child : children){
                    addToArchiveCompression(out, child, name);
                }
            }
        } else {
            System.out.println(file.getName() + " is not supported");
        }
    }  

But how can I compress an manually defined String to 7z and convert it to byte[]? So then I can convert the byte[] to base64 and print it, without generating or reading new Files?

Upvotes: 5

Views: 1747

Answers (4)

zebo zhuang
zebo zhuang

Reputation: 636

I found that someone had implemented the code.

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile;
import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;

/**
 * the SevenZ Util
 *
 * @author ph3636
 */
public class SevenZUtil {

    private static final int BUFFER_SIZE = 8192;

    public static byte[] compress(byte[] bytes) {
        if (bytes == null) {
            throw new NullPointerException("bytes is null");
        }
        SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel();
        try (SevenZOutputFile z7z = new SevenZOutputFile(channel)) {
            SevenZArchiveEntry entry = new SevenZArchiveEntry();
            entry.setName("sevenZip");
            entry.setSize(bytes.length);
            z7z.putArchiveEntry(entry);
            z7z.write(bytes);
            z7z.closeArchiveEntry();
            z7z.finish();
            return channel.array();
        } catch (IOException e) {
            throw new RuntimeException("SevenZ compress error", e);
        }
    }

    public static byte[] decompress(byte[] bytes) {
        if (bytes == null) {
            throw new NullPointerException("bytes is null");
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(bytes);
        try (SevenZFile sevenZFile = new SevenZFile(channel)) {
            byte[] buffer = new byte[BUFFER_SIZE];
            while (sevenZFile.getNextEntry() != null) {
                int n;
                while ((n = sevenZFile.read(buffer)) > -1) {
                    out.write(buffer, 0, n);
                }
            }
            return out.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException("SevenZ decompress error", e);
        }
    }
}
<commons-compress.version>1.22</commons-compress.version>
<xz.version>1.9</xz.version>
<dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-compress</artifactId>
            <version>${commons-compress.version}</version>
</dependency>
<dependency>
            <groupId>org.tukaani</groupId>
            <artifactId>xz</artifactId>
            <version>${xz.version}</version>
</dependency>

reference: SevenZUtil.java

Upvotes: 0

Stepan Yakovenko
Stepan Yakovenko

Reputation: 9206

This is a bit offtopic, but assuming that LZMA is part of 7zip, this might help you:

public static byte[]compress(byte[]arr,int level){
    try {
        ByteArrayOutputStream compr = new ByteArrayOutputStream();
        LZMA2Options options = new LZMA2Options();
        options.setPreset(level); // play with this number: 6 is default but 7 works better for mid sized archives ( > 8mb)
        XZOutputStream out = new XZOutputStream(compr, options);
        out.write(arr);
        out.finish();
        return compr.toByteArray();
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

public static byte[]decompress(byte[]bts){
    try {
        ByteArrayInputStream bis = new ByteArrayInputStream(bts);
        XZInputStream is = new XZInputStream(bis);
        ByteArrayInputStream decomp = new ByteArrayInputStream(is.readAllBytes());
        ObjectInputStream ois = new ObjectInputStream(decomp);
        byte data[]= (byte[]) ois.readObject();
        return data;
    } catch (IOException | ClassNotFoundException e) {
        throw new RuntimeException(e);
    }
}

maven dependency:

    <dependency>
        <groupId>org.tukaani</groupId>
        <artifactId>xz</artifactId>
        <version>1.8</version>
    </dependency>

convert your String to byte[] with s.getBytes(StandardCharsets.UTF_8);

Upvotes: 1

Karol Dowbecki
Karol Dowbecki

Reputation: 44932

Since you are already using commons-compress for 7zip compression you can create SevenZOutputFile(SeekableByteChannel) instance with SeekableInMemoryByteChannel that wraps a byte array. As per javadoc:

A SeekableByteChannel implementation that wraps a byte[].

When this channel is used for writing an internal buffer grows to accommodate incoming data. A natural size limit is the value of Integer.MAX_VALUE. Internal buffer can be accessed via array().

Something like:

SeekableInMemoryByteChannel channel = new SeekableInMemoryByteChannel(new byte[1024]);
SevenZOutputFile out = new SevenZOutputFile(channel);
// modified addToArchiveCompression(out, ...); for String
// encode channel.array() to Base64

Upvotes: 4

Filippo Possenti
Filippo Possenti

Reputation: 1410

You will of course have to apply several changes to the code you posted. That code is meant to compress a file or directory whereas your case is much simpler. You definitely won't need a for loop, for example.

I break down the various parts you'll have to look into and leave the coding to you.

Converting a String to data for 7z:

One of the options is to use a ByteArrayInputStream instead of a FileInputStream. The ByteArrayInputStream will have to be initialised with the bytes corresponding to the String.

See the following article for an example on how to do this conversion:

https://www.baeldung.com/convert-string-to-input-stream

Converting output bytes to Base64:

There are several approaches, detailed in this StackOverflow thread:

How do I convert a byte array to Base64 in Java?

Outputting 7z to memory instead of File:

You will have to use the SevenZOutputFile constructor taking a SeekableByteChannel interface as input. Your implementation of SeekableByteChannel will have to be backed by a byte array or stream of sorts. You can use the following implementation:

https://commons.apache.org/proper/commons-compress/apidocs/org/apache/commons/compress/utils/SeekableInMemoryByteChannel.html

Obtaining a SevenZArchiveEntry from something other than a file:

Although the SevenZOutputFile class doesn't seem to provide a facility to do that, if you look at its sourcecode you'll notice that you can manually create a SevenZArchiveEntry without any intermediation, as it has an empty constructor. You'll have to "pretend" it's still an actual file, but that shouldn't be a problem.

Sourcecode of SevenZArchiveEntry:

https://commons.apache.org/proper/commons-compress/apidocs/src-html/org/apache/commons/compress/archivers/sevenz/SevenZOutputFile.html

Upvotes: 0

Related Questions