Я заметил, что разархивировать средство в Java является чрезвычайно медленным по сравнению с использованием собственного инструмента, такого как WinZip.
Существует ли сторонняя библиотека, доступная для Java, который более эффективен? Открытый исходный код предпочтен.
Править
Вот сравнение скорости с помощью Java встроенное решение по сравнению с 7zip. Я добавил, потоки ввода-вывода с буферизацией в моем исходном решении (благодарит Jim, это действительно имело большое значение).
Размер zip-файла: Решение для Java 800K: 2,7 секунды 7Zip решение: 204 мс
Вот измененный код с помощью встроенной распаковки Java:
/** Unpacks the give zip file using the built in Java facilities for unzip. */
@SuppressWarnings("unchecked")
public final static void unpack(File zipFile, File rootDir) throws IOException
{
ZipFile zip = new ZipFile(zipFile);
Enumeration<ZipEntry> entries = (Enumeration<ZipEntry>) zip.entries();
while(entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
java.io.File f = new java.io.File(rootDir, entry.getName());
if (entry.isDirectory()) { // if its a directory, create it
continue;
}
if (!f.exists()) {
f.getParentFile().mkdirs();
f.createNewFile();
}
BufferedInputStream bis = new BufferedInputStream(zip.getInputStream(entry)); // get the input stream
BufferedOutputStream bos = new BufferedOutputStream(new java.io.FileOutputStream(f));
while (bis.available() > 0) { // write contents of 'is' to 'fos'
bos.write(bis.read());
}
bos.close();
bis.close();
}
}
Проблема не в разархивировании, а в неэффективном способе записи разархивированных данных обратно на диск. Мои тесты показывают, что использование
InputStream is = zip.getInputStream(entry); // get the input stream
OutputStream os = new java.io.FileOutputStream(f);
byte[] buf = new byte[4096];
int r;
while ((r = is.read(buf)) != -1) {
os.write(buf, 0, r);
}
os.close();
is.close();
вместо этого сокращает время выполнения метода в 5 раз (с 5 до 1 секунды для zip-файла размером 6 МБ).
Вероятная виновница - использование вами bis.available ()
. Помимо того, что он неверен (available возвращает количество байтов до тех пор, пока вызов для чтения не заблокируется, а не до конца потока), это обходит буферизацию, предоставляемую BufferedInputStream, требуя собственного системного вызова для каждого байта, скопированного в выходной файл.
Обратите внимание, что обертывание в BufferedStream не требуется, если вы используете методы массового чтения и записи, как я делаю выше, и что код для закрытия ресурсов не является безопасным для исключений (если чтение или запись не удается по какой-либо причине, ни - это
, ни os
не будет закрыта). Наконец, если у вас есть IOUtils в пути к классам, я рекомендую использовать их хорошо протестированные IOUtils.copy
вместо того, чтобы катить свои собственные.
Я нашел «неизящное» решение. Существует бесплатная утилита с открытым исходным кодом 7zip (www.7-zip.org). Вы можете скачать версию для командной строки ( http://www.7-zip.org/download.html ). 7-zip поддерживается только в Windows, но похоже, что он был перенесен на другие платформы (p7zip).
Очевидно, что это решение не идеально, поскольку оно зависит от платформы и зависит от исполняемого файла. Однако скорость по сравнению с распаковкой в Java невероятна.
Вот код служебной функции, которую я создал для взаимодействия с этой служебной программой. Есть возможности для улучшения, поскольку приведенный ниже код относится к Windows.
/** Unpacks the zipfile to the output directory. Note: this code relies on 7-zip
(specifically the cmd line version, 7za.exe). The exeDir specifies the location of the 7za.exe utility. */
public static void unpack(File zipFile, File outputDir, File exeDir) throws IOException, InterruptedException
{
if (!zipFile.exists()) throw new FileNotFoundException(zipFile.getAbsolutePath());
if (!exeDir.exists()) throw new FileNotFoundException(exeDir.getAbsolutePath());
if (!outputDir.exists()) outputDir.mkdirs();
String cmd = exeDir.getAbsolutePath() + "/7za.exe -y e " + zipFile.getAbsolutePath();
ProcessBuilder builder = new ProcessBuilder(new String[] { "cmd.exe", "/C", cmd });
builder.directory(outputDir);
Process p = builder.start();
int rc = p.waitFor();
if (rc != 0) {
log.severe("Util::unpack() 7za process did not complete normally. rc: " + rc);
}
}
Убедитесь, что вы передаете методу unzip поток BufferedInputStream в вашем Java-приложении. Если вы допустили ошибку, используя небуферизованный входной поток, ваша производительность ввода-вывода гарантированно снизится.