java использует сжатие и распаковку 7z
Зависит отFind· Дата выпуска· обновлено27 просмотрено
1. Introduction
Два года назад, когда я хотел написать небольшую функцию распаковки в java-проекте, я использовал7zip bindingпроект, и запись в блоге была выпущена одновременноJava распаковать файл 7z, я не ожидал, что статья хорошо посещается и занимает высокие позиции в результатах поиска Google и Baidu, хотя более вероятная причина в том, что никто ничего не делает в этой области.
Некоторое время назад я работал над проектом, и мне нужно было использовать функцию сжатия и распаковки, но после тщательного изучения я обнаружил, что этот проект давно не обновлялся (хотя на странице проекта указано Последнее обновление: 2017- 07-19, но последнее изменение кода — 2015), и есть довольно много проблем с использованием. Поэтому я обратился за помощью к официальному проекту 7z, и обнаружил, что у 7zip официально есть LZMA SDK, но его Java часть может долго не обновляться по комментариям, а потому что код относительно прост, и примеры примеров есть , я решил использовать его для разработки. .
2. Адрес проекта
Я извлек официальную часть Java и добавил свою молнию.
адрес проекта:LZMA
Написал простой пример того, как разбить большой файл на более мелкие фрагменты и сжать каждый фрагмент вместо сжатия всего файла:
3. Запустите официальный образец кода
В официальном примере LzmaAlone — это независимый класс, содержащий основную функцию.
Код очень короткий. Вы можете видеть, что это в основном парсинг параметров командной строки. Его использование:
"\nUsage: LZMA <e|d> [<switches>...] inputFile outputFile\n" +
" e: encode file\n" +
" d: decode file\n" +
" b: Benchmark\n" +
"<Switches>\n" +
// " -a{N}: set compression mode - [0, 1], default: 1 (max)\n" +
" -d{N}: set dictionary - [0,28], default: 23 (8MB)\n" +
" -fb{N}: set number of fast bytes - [5, 273], default: 128\n" +
" -lc{N}: set number of literal context bits - [0, 8], default: 3\n" +
" -lp{N}: set number of literal pos bits - [0, 4], default: 0\n" +
" -pb{N}: set number of pos bits - [0, 4], default: 2\n" +
" -mf{MF_ID}: set Match Finder: [bt2, bt4], default: bt4\n" +
" -eos: write End Of Stream marker\n"
);
3.1 encode file part
if (params.Command == CommandLine.kEncode)
{
Compression.LZMA.Encoder encoder = new Compression.LZMA.Encoder();
if (!encoder.SetAlgorithm(params.Algorithm))
throw new Exception("Incorrect compression mode");
if (!encoder.SetDictionarySize(params.DictionarySize))
throw new Exception("Incorrect dictionary size");
if (!encoder.SetNumFastBytes(params.Fb))
throw new Exception("Incorrect -fb value");
if (!encoder.SetMatchFinder(params.MatchFinder))
throw new Exception("Incorrect -mf value");
if (!encoder.SetLcLpPb(params.Lc, params.Lp, params.Pb))
throw new Exception("Incorrect -lc or -lp or -pb value");
encoder.SetEndMarkerMode(eos);
//写入5bytes配置参数
encoder.WriteCoderProperties(outStream);
long fileSize;
if (eos)
fileSize = -1;
else
fileSize = inFile.length();
for (int i = 0; i < 8; i++)
outStream.write((int)(fileSize >>> (8 * i)) & 0xFF);
encoder.Code(inStream, outStream, -1, -1, null);
}
Предыдущая часть — установка параметров сжатия.
Раздел алгоритма сжатия
public boolean SetAlgorithm(int algorithm)
{
/*
_fastMode = (algorithm == 0);
_maxMode = (algorithm >= 2);
*/
return true;
}
Может потому что код давно не обновлялся, java версия кода, алгоритм сжатия тоже прямо опущен какmaxMode
. Затем содержимое настройки, непосредственно связанное с параметром, записывается в выходной поток, всего 5 байтов.
fileSize
через файловый потокlength()
полученная функция. И записывайте его байт за байтом в выходной поток. Обратите внимание, что тип здесьlong
, но при записи он принимает int. Здесь нет проблем. потому что>>>
является логическим сдвигом вправо, без знака, даже если принудительно усечен доint
, так как только0xFF
8 бит, потому что не будет проблемы недостаточной точности.
3.2 decode part
int propertiesSize = 5;
byte[] properties = new byte[propertiesSize];
if (inStream.read(properties, 0, propertiesSize) != propertiesSize)
throw new Exception("input .lzma file is too short");
Compression.LZMA.Decoder decoder = new Compression.LZMA.Decoder();
if (!decoder.SetDecoderProperties(properties))
throw new Exception("Incorrect stream properties");
long outSize = 0;
for (int i = 0; i < 8; i++)
{
int v = inStream.read();
if (v < 0)
throw new Exception("Can't read stream size");
outSize |= ((long)v) << (8 * i);
}
if (!decoder.Code(inStream, outStream, outSize))
throw new Exception("Error in data stream");
Первые 5 прочитанных байт — это параметры сжатия, хранящиеся в энкоде, при распаковке указывать их не нужно, их можно прочитать прямо из сжатого файла.
Следующий цикл for определяет размер распакованного исходного файла, сохраняемого перед чтением. Принцип тот же, что и выше.
4. Сжатие реконструкции
Текущее требование состоит в том, чтобы завершить свои собственные функции кодирования и декодирования.byte[]
По мере сжатия контента сжатый контент помещается в другойbyte[]
внутри. Таким образом, он может адаптироваться к сжатию и распаковке данных в нефайловых сценариях.
4.1 Zipper.java
кодировать содержимое:
/**
* 压缩函数
*
* @param inputStream: 要压缩的数据,如果源数据是byte[],那么在外层要ByteArrayInputStream ins = new ByteArrayInputStream(yourbytes[], offset, effective_length)
* @param outputStream: 压缩好的数据,可以通过outputs_bytes.toBytes变成byte数组
* @param len: inputs_bytes中的有效数据长度,用于写入压缩好的数据开头,解压时会先读出来这段大小,以便知道要解压的数据大小。注意数据类型
*/
public void encode(ByteArrayInputStream inputStream, ByteArrayOutputStream outputStream, long len) throws Exception {
Compression.LZMA.Encoder encoder = new Compression.LZMA.Encoder();
//设置压缩参数。默认即可
if (!encoder.SetAlgorithm(2))
throw new Exception("Incorrect compression mode");
if (!encoder.SetDictionarySize(1 << 23))
throw new Exception("Incorrect dictionary size");
if (!encoder.SetNumFastBytes(128))
throw new Exception("Incorrect -fb value");
if (!encoder.SetMatchFinder(1))
throw new Exception("Incorrect -mf value");
if (!encoder.SetLcLpPb(3, 0, 2))
throw new Exception("Incorrect -lc or -lp or -pb value");
encoder.SetEndMarkerMode(false);
//首先会有5bytes的参数信息被写入
encoder.WriteCoderProperties(outputStream);
//接下来8bytes是要压缩的数据的长度,在解压时将被读取。注意这里len是long类型,如果是int,则最大可表示2GB的数据,因此采用long,但是里面每个byte在存储的时候,使用int即可。
for (int j = 0; j < 8; j++)
//无符号右移
outputStream.write((int) (len >>> (8 * j)) & 0xFF);
// inSize、outSize以及progress参数可以这样设置不用理会
encoder.Code(inputStream, outputStream, -1, -1, null);
}
4.2 Тестовое чтение файла и разделение сжатой части
Zipper zipper = new Zipper();
//读取文件
File infile = new File("/home/find/ddown/aa/aa.pptx");
BufferedInputStream ins = new BufferedInputStream(new FileInputStream(infile));
BufferedOutputStream outs = new BufferedOutputStream(new FileOutputStream(new File("/home/find/ddown/aa/aa.lzma")));
// @todo 设置real_len为int,实际限制了每块的大小不能超过2GB
int real_len;
// 要将文件分割的文件块的大小
final int blockSize = 1024 << 3;
// 用来保存每块压缩大小
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < infile.length(); i += real_len) {
//由于压缩可能会导致文件块变大,因此预开辟两倍空间存放,默认文件分块大小为8KB,即1024<<3
byte[] inbytes = new byte[blockSize << 1];
// @todo: 如果实际不是读到1024 × 8,除非到文件尾部,否则应该继续读取,直到读完1024*8长度的块。
real_len = ins.read(inbytes, 0, blockSize);
// @warning: 一定要注意,要以实际大小建stream!!!否则压缩时,会将实际有效数据后面的部分空数据也认为是有效的。!!!
ByteArrayInputStream inputStream = new ByteArrayInputStream(inbytes, 0, real_len);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(blockSize << 1);
System.out.print("实际读取的字节数" + real_len);
zipper.encode(inputStream, outputStream, (long) real_len);
// ByteArrarInputStream.size()是指实际有效数据
queue.offer(outputStream.size());
System.out.println("压缩后大小" + (outputStream.size()));
//将压缩好的数据写入压缩文件
outs.write(outputStream.toByteArray());
}
System.out.println("encode end\n======================================\n");
zipper просто инкапсулирует кодировку.
Как вы можете видеть в тестовом разделе, прочитайте содержимое файла, чтобыbyte[] inbytes
в, сByteArrayInputStream
Упакуйте его в поток и передайте в функцию кодирования.
Обратите внимание, что для хранения сжатого размера каждого блока создается новая очередь, потому что в тесте файл разбивается на данные о размере блока для сжатия, а затем записывается в файл, а часть распаковки читает файл в соответствии с ранее хранимых данных.Создайте новый поток со сжатым размером и распакуйте блок.
4.3 Несколько ключевых вопросов
- читать в файле
read
Содержимое, фактически прочитанное функцией, может не совпадать по размеру с длиной входящего запроса. - в новом
ByteArrayInputStream
время, должноУстановите размер на размер фактического блока сжатых данных, в противном случае функция сжатия исходного звука 7zip будет считать действительными данные часть, превышающую реальный допустимый размер данных! В результате ввод на самом деле больше, чем вы просили для сжатия, и естественный результат сжатия неверен. - При сжатии каждого блока файла параметры сжатия и размер файла в распакованном виде сохраняются.
5. Модернизация и декомпрессия
5.1 Разархивируйте код
/**
* 解压函数
*
* @param inputStream: 要解压的数据。要求同encode。
* @param outputStream: 解压获得的数据。
*/
public void decode(ByteArrayInputStream inputStream, ByteArrayOutputStream outputStream) throws Exception {
Compression.LZMA.Decoder decoder = new Compression.LZMA.Decoder();
//先读取5bytes设置
int propertiesSize = 5;
byte[] properties = new byte[propertiesSize];
if (inputStream.read(properties, 0, propertiesSize) != propertiesSize)
throw new Exception("input .lzma file is too short");
if (!decoder.SetDecoderProperties(properties))
throw new Exception("Incorrect stream properties");
long outSize = 0;
// 读取8bytes的要解压出来的文件长度(单位bytes)
for (int j = 0; j < 8; j++) {
int v = inputStream.read();
if (v < 0)
throw new Exception("Can't read stream size");
outSize |= ((long) v) << (8 * j);
}
if (!decoder.Code(inputStream, outputStream, outSize)) {
throw new Exception("Error in data stream");
}
//@todo: 这里不应该只是打印,应该throw error
if (outputStream.size() != outSize) {
System.out.println("实际解压大小和记录不同 outputstream.size " + outputStream.size() + "\toutsize" + outSize);
}
}
5.2 Тестирование
Zipper zipper = new Zipper();
// decoder part
infile = new File("/home/find/ddown/aa/aa.lzma");
BufferedOutputStream o2 = new BufferedOutputStream(new FileOutputStream(new File("/home/find/ddown/aa/aa_extra.pptx")));
BufferedInputStream i2 = new BufferedInputStream(new FileInputStream(infile));
// 每个压缩块的大小都在queue里。一个一个压缩块的进行读取和解压
while (!queue.isEmpty()) {
byte[] inbytes = new byte[blockSize << 1];
real_len = i2.read(inbytes, 0, queue.peek());
//@todo: 这里应该throw error
if (real_len != queue.peek()) {
System.out.println("读取的大小和队列里的大小(要读的大小)不同" + real_len + "\t" + queue.peek());
}
ByteArrayInputStream inputStream = new ByteArrayInputStream(inbytes, 0, queue.peek());
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(blockSize << 1);
zipper.decode(inputStream, outputStream);
o2.write(outputStream.toByteArray());
queue.poll();
}
o2.flush();
o2.close();
i2.close();
Авторское право на статью FindHao Все 丨 На этом сайте используются настройки по умолчаниюCC-BY-NC-SA
4.0Соглашение об авторизации|Перепечатка должна включать это заявление и гиперссылку на автора. FindHao И оригинальный адрес этой статьи:
Woohoo. Найдите good.net/easy coding/…