简体   繁体   English

如何在 Android 中以编程方式解压缩文件?

[英]How to unzip files programmatically in Android?

I need a small code snippet which unzips a few files from a given .zip file and gives the separate files according to the format they were in the zipped file.我需要一个小代码片段,它从给定的 .zip 文件中解压缩一些文件,并根据它们在压缩文件中的格式给出单独的文件。 Please post your knowledge and help me out.请发布您的知识并帮助我。

Had peno's version optimised a bit.对 peno 的版本进行了一些优化。 The increase in performance is perceptible.性能的提升是显而易见的。

private boolean unpackZip(String path, String zipname)
{       
     InputStream is;
     ZipInputStream zis;
     try 
     {
         String filename;
         is = new FileInputStream(path + zipname);
         zis = new ZipInputStream(new BufferedInputStream(is));          
         ZipEntry ze;
         byte[] buffer = new byte[1024];
         int count;

         while ((ze = zis.getNextEntry()) != null) 
         {
             filename = ze.getName();

             // Need to create directories if not exists, or
             // it will generate an Exception...
             if (ze.isDirectory()) {
                File fmd = new File(path + filename);
                fmd.mkdirs();
                continue;
             }

             FileOutputStream fout = new FileOutputStream(path + filename);

             while ((count = zis.read(buffer)) != -1) 
             {
                 fout.write(buffer, 0, count);             
             }

             fout.close();               
             zis.closeEntry();
         }

         zis.close();
     } 
     catch(IOException e)
     {
         e.printStackTrace();
         return false;
     }

    return true;
}

Based on Vasily Sochinsky's answer a bit tweaked & with a small fix:基于 Vasily Sochinsky 的回答,稍作调整并进行了小修正:

public static void unzip(File zipFile, File targetDirectory) throws IOException {
    ZipInputStream zis = new ZipInputStream(
            new BufferedInputStream(new FileInputStream(zipFile)));
    try {
        ZipEntry ze;
        int count;
        byte[] buffer = new byte[8192];
        while ((ze = zis.getNextEntry()) != null) {
            File file = new File(targetDirectory, ze.getName());
            File dir = ze.isDirectory() ? file : file.getParentFile();
            if (!dir.isDirectory() && !dir.mkdirs())
                throw new FileNotFoundException("Failed to ensure directory: " +
                        dir.getAbsolutePath());
            if (ze.isDirectory())
                continue;
            FileOutputStream fout = new FileOutputStream(file);
            try {
                while ((count = zis.read(buffer)) != -1)
                    fout.write(buffer, 0, count);
            } finally {
                fout.close();
            }
            /* if time should be restored as well
            long time = ze.getTime();
            if (time > 0)
                file.setLastModified(time);
            */
        }
    } finally {
        zis.close();
    }
}

Notable differences显着差异

  • public static - this is a static utility method that can be anywhere. public static - 这是一个可以在任何地方的静态实用方法。
  • 2 File parameters because String are :/ for files and one could not specify where the zip file is to be extracted before. 2 File参数,因为String是 :/ 用于文件,并且之前无法指定要提取 zip 文件的位置。 Also path + filename concatenation > https://stackoverflow.com/a/412495/995891还有path + filename连接> https://stackoverflow.com/a/412495/995891
  • throws - because catch late - add a try catch if really not interested in them. throws - 因为捕捉晚了- 如果真的对它们不感兴趣,请添加一个尝试捕捉。
  • actually makes sure that the required directories exist in all cases.实际上确保在所有情况下都存在所需的目录。 Not every zip contains all the required directory entries in advance of file entries.并非每个 zip 在文件条目之前都包含所有必需的目录条目。 This had 2 potential bugs:这有两个潜在的错误:
    • if the zip contains an empty directory and instead of the resulting directory there is an existing file, this was ignored.如果 zip 包含一个空目录,并且存在一个现有文件而不是生成的目录,则忽略该文件。 The return value of mkdirs() is important. mkdirs()的返回值很重要。
    • could crash on zip files that don't contain directories.可能会在不包含目录的 zip 文件上崩溃。
  • increased write buffer size, this should improve performance a bit.增加写入缓冲区大小,这应该会稍微提高性能。 Storage is usually in 4k blocks and writing in smaller chunks is usually slower than necessary.存储通常以 4k 块为单位,以较小的块写入通常比必要的要慢。
  • uses the magic of finally to prevent resource leaks.使用finally的魔法来防止资源泄漏。

So所以

unzip(new File("/sdcard/pictures.zip"), new File("/sdcard"));

should do the equivalent of the original应该做相当于原来的

unpackZip("/sdcard/", "pictures.zip")

This is my unzip method, which I use:这是我使用的解压缩方法:

private boolean unpackZip(String path, String zipname)
{       
     InputStream is;
     ZipInputStream zis;
     try 
     {
         is = new FileInputStream(path + zipname);
         zis = new ZipInputStream(new BufferedInputStream(is));          
         ZipEntry ze;

         while((ze = zis.getNextEntry()) != null) 
         {
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             byte[] buffer = new byte[1024];
             int count;

             String filename = ze.getName();
             FileOutputStream fout = new FileOutputStream(path + filename);

             // reading and writing
             while((count = zis.read(buffer)) != -1) 
             {
                 baos.write(buffer, 0, count);
                 byte[] bytes = baos.toByteArray();
                 fout.write(bytes);             
                 baos.reset();
             }

             fout.close();               
             zis.closeEntry();
         }

         zis.close();
     } 
     catch(IOException e)
     {
         e.printStackTrace();
         return false;
     }

    return true;
}

The Kotlin way Kotlin 方式

//FileExt.kt

data class ZipIO (val entry: ZipEntry, val output: File)

fun File.unzip(unzipLocationRoot: File? = null) {

    val rootFolder = unzipLocationRoot ?: File(parentFile.absolutePath + File.separator + nameWithoutExtension)
    if (!rootFolder.exists()) {
       rootFolder.mkdirs()
    }

    ZipFile(this).use { zip ->
        zip
        .entries()
        .asSequence()
        .map {
            val outputFile = File(rootFolder.absolutePath + File.separator + it.name)
            ZipIO(it, outputFile)
        }
        .map {
            it.output.parentFile?.run{
                if (!exists()) mkdirs()
            }
            it
        }
        .filter { !it.entry.isDirectory }
        .forEach { (entry, output) ->
            zip.getInputStream(entry).use { input ->
                output.outputStream().use { output ->
                    input.copyTo(output)
                }
            }
        }
    }

}

Usage用法

val zipFile = File("path_to_your_zip_file")
file.unzip()

Android has build-in Java API. Android 有内置的 Java API。 Check out java.util.zip package.查看java.util.zip包。

The class ZipInputStream is what you should look into.您应该研究ZipInputStream类。 Read ZipEntry from the ZipInputStream and dump it into filesystem/folder.从 ZipInputStream 读取 ZipEntry 并将其转储到文件系统/文件夹中。 Check similar example to compress into zip file.检查类似的示例以压缩为 zip文件。

While the answers that are already here work well, I found that they were slightly slower than I had hoped for.虽然这里的答案效果很好,但我发现它们比我希望的要慢一些。 Instead I used zip4j , which I think is the best solution because of its speed.相反,我使用了zip4j ,我认为这是最好的解决方案,因为它的速度。 It also allowed for different options for the amount of compression, which I found useful.它还允许压缩量的不同选项,我发现这很有用。

use the following class使用以下类

    import java.io.BufferedOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.util.zip.ZipEntry;
    import java.util.zip.ZipInputStream;
    import android.util.Log;

    public class DecompressFast {



 private String _zipFile; 
  private String _location; 
 
  public DecompressFast(String zipFile, String location) { 
    _zipFile = zipFile; 
    _location = location; 
 
    _dirChecker(""); 
  } 
 
  public void unzip() { 
    try  { 
      FileInputStream fin = new FileInputStream(_zipFile); 
      ZipInputStream zin = new ZipInputStream(fin); 
      ZipEntry ze = null; 
      while ((ze = zin.getNextEntry()) != null) { 
        Log.v("Decompress", "Unzipping " + ze.getName()); 
 
        if(ze.isDirectory()) { 
          _dirChecker(ze.getName()); 
        } else { 
          FileOutputStream fout = new FileOutputStream(_location + ze.getName()); 
         BufferedOutputStream bufout = new BufferedOutputStream(fout);
          byte[] buffer = new byte[1024];
          int read = 0;
          while ((read = zin.read(buffer)) != -1) {
              bufout.write(buffer, 0, read);
          }

          
          
          
          bufout.close();
          
          zin.closeEntry(); 
          fout.close(); 
        } 
         
      } 
      zin.close(); 
      
      
      Log.d("Unzip", "Unzipping complete. path :  " +_location );
    } catch(Exception e) { 
      Log.e("Decompress", "unzip", e); 
      
      Log.d("Unzip", "Unzipping failed");
    } 
 
  } 
 
  private void _dirChecker(String dir) { 
    File f = new File(_location + dir); 
 
    if(!f.isDirectory()) { 
      f.mkdirs(); 
    } 
  } 


 }

How to use如何使用

 String zipFile = Environment.getExternalStorageDirectory() + "/the_raven.zip"; //your zip file location
    String unzipLocation = Environment.getExternalStorageDirectory() + "/unzippedtestNew/"; // destination folder location
  DecompressFast df= new DecompressFast(zipFile, unzipLocation);
    df.unzip();

Permissions权限

 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

According to @zapl answer,Unzip with progress report:根据@zapl 的回答,解压进度报告:

public interface UnzipFile_Progress
{
    void Progress(int percent, String FileName);
}

// unzip(new File("/sdcard/pictures.zip"), new File("/sdcard"));
public static void UnzipFile(File zipFile, File targetDirectory, UnzipFile_Progress progress) throws IOException,
        FileNotFoundException
{
    long total_len = zipFile.length();
    long total_installed_len = 0;

    ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(zipFile)));
    try
    {
        ZipEntry ze;
        int count;
        byte[] buffer = new byte[1024];
        while ((ze = zis.getNextEntry()) != null)
        {
            if (progress != null)
            {
                total_installed_len += ze.getCompressedSize();
                String file_name = ze.getName();
                int percent = (int)(total_installed_len * 100 / total_len);
                progress.Progress(percent, file_name);
            }

            File file = new File(targetDirectory, ze.getName());
            File dir = ze.isDirectory() ? file : file.getParentFile();
            if (!dir.isDirectory() && !dir.mkdirs())
                throw new FileNotFoundException("Failed to ensure directory: " + dir.getAbsolutePath());
            if (ze.isDirectory())
                continue;
            FileOutputStream fout = new FileOutputStream(file);
            try
            {
                while ((count = zis.read(buffer)) != -1)
                    fout.write(buffer, 0, count);
            } finally
            {
                fout.close();
            }

            // if time should be restored as well
            long time = ze.getTime();
            if (time > 0)
                file.setLastModified(time);
        }
    } finally
    {
        zis.close();
    }
}
public class MainActivity extends Activity {

private String LOG_TAG = MainActivity.class.getSimpleName();

private File zipFile;
private File destination;

private TextView status;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    status = (TextView) findViewById(R.id.main_status);
    status.setGravity(Gravity.CENTER);

    if ( initialize() ) {
        zipFile = new File(destination, "BlueBoxnew.zip");
        try {
            Unzipper.unzip(zipFile, destination);
            status.setText("Extracted to \n"+destination.getAbsolutePath());
        } catch (ZipException e) {
            Log.e(LOG_TAG, e.getMessage());
        } catch (IOException e) {
            Log.e(LOG_TAG, e.getMessage());
        }
    } else {
        status.setText("Unable to initialize sd card.");
    }
}

public boolean initialize() {
    boolean result = false;
     File sdCard = new File(Environment.getExternalStorageDirectory()+"/zip/");
    //File sdCard = Environment.getExternalStorageDirectory();
    if ( sdCard != null ) {
        destination = sdCard;
        if ( !destination.exists() ) {
            if ( destination.mkdir() ) {
                result = true;
            }
        } else {
            result = true;
        }
    }

    return result;
}

 }

->Helper Class(Unzipper.java) ->Helper 类(Unzipper.java)

    import java.io.File;
    import java.io.FileInputStream;
   import java.io.FileOutputStream;
    import java.io.IOException;
       import java.util.zip.ZipEntry;
    import java.util.zip.ZipException;
    import java.util.zip.ZipInputStream;
     import android.util.Log;

   public class Unzipper {

private static String LOG_TAG = Unzipper.class.getSimpleName();

public static void unzip(final File file, final File destination) throws ZipException, IOException {
    new Thread() {
        public void run() {
            long START_TIME = System.currentTimeMillis();
            long FINISH_TIME = 0;
            long ELAPSED_TIME = 0;
            try {
                ZipInputStream zin = new ZipInputStream(new FileInputStream(file));
                String workingDir = destination.getAbsolutePath()+"/";

                byte buffer[] = new byte[4096];
                int bytesRead;
                ZipEntry entry = null;
                while ((entry = zin.getNextEntry()) != null) {
                    if (entry.isDirectory()) {
                        File dir = new File(workingDir, entry.getName());
                        if (!dir.exists()) {
                            dir.mkdir();
                        }
                        Log.i(LOG_TAG, "[DIR] "+entry.getName());
                    } else {
                        FileOutputStream fos = new FileOutputStream(workingDir + entry.getName());
                        while ((bytesRead = zin.read(buffer)) != -1) {
                            fos.write(buffer, 0, bytesRead);
                        }
                        fos.close();
                        Log.i(LOG_TAG, "[FILE] "+entry.getName());
                    }
                }
                zin.close();

                FINISH_TIME = System.currentTimeMillis();
                ELAPSED_TIME = FINISH_TIME - START_TIME;
                Log.i(LOG_TAG, "COMPLETED in "+(ELAPSED_TIME/1000)+" seconds.");
            } catch (Exception e) {
                Log.e(LOG_TAG, "FAILED");
            }
        };
    }.start();
}

   }

->xml layout(activity_main.xml): -> xml 布局(activity_main.xml):

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
 android:layout_height="match_parent"
 tools:context=".MainActivity" >

<TextView
    android:id="@+id/main_status"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true"
    android:text="@string/hello_world" />

</RelativeLayout>

->permission in Menifest file: -> 在 Menifest 文件中的权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

Here is a ZipFileIterator (like a java Iterator, but for zip files):这是一个 ZipFileIterator(类似于 java 迭代器,但用于 zip 文件):

package ch.epfl.bbp.io;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public class ZipFileIterator implements Iterator<File> {

    private byte[] buffer = new byte[1024];

    private FileInputStream is;
    private ZipInputStream zis;
    private ZipEntry ze;

    public ZipFileIterator(File file) throws FileNotFoundException {
    is = new FileInputStream(file);
    zis = new ZipInputStream(new BufferedInputStream(is));
    }

    @Override
    public boolean hasNext() {
    try {
        return (ze = zis.getNextEntry()) != null;
    } catch (IOException e) {
        e.printStackTrace();
    }
    return false;
    }

    @Override
    public File next() {
    try {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int count;

        String filename = ze.getName();
        File tmpFile = File.createTempFile(filename, "tmp");
        tmpFile.deleteOnExit();// TODO make it configurable
        FileOutputStream fout = new FileOutputStream(tmpFile);

        while ((count = zis.read(buffer)) != -1) {
        baos.write(buffer, 0, count);
        byte[] bytes = baos.toByteArray();
        fout.write(bytes);
        baos.reset();
        }
        fout.close();
        zis.closeEntry();

        return tmpFile;

    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    }

    @Override
    public void remove() {
    throw new RuntimeException("not implemented");
    }

    public void close() {
    try {
        zis.close();
        is.close();
    } catch (IOException e) {// nope
    }
    }
}

Minimal example I used to unzip a specific file from my zipfile into my applications cache folder.我曾经将特定文件从 zipfile 解压缩到我的应用程序缓存文件夹中的最小示例。 I then read the manifest file using a different method.然后我使用不同的方法读取清单文件。

private void unzipUpdateToCache() {
    ZipInputStream zipIs = new ZipInputStream(context.getResources().openRawResource(R.raw.update));
    ZipEntry ze = null;

    try {

        while ((ze = zipIs.getNextEntry()) != null) {
            if (ze.getName().equals("update/manifest.json")) {
                FileOutputStream fout = new FileOutputStream(context.getCacheDir().getAbsolutePath() + "/manifest.json");

                byte[] buffer = new byte[1024];
                int length = 0;

                while ((length = zipIs.read(buffer))>0) {
                    fout.write(buffer, 0, length);
                }
                zipIs .closeEntry();
                fout.close();
            }
        }
        zipIs .close();

    } catch (IOException e) {
        e.printStackTrace();
    }

}

I'm working with zip files which Java's ZipFile class isn't able to handle.我正在处理 Java 的 ZipFile 类无法处理的 zip 文件。 Java 8 apparently can't handle compression method 12 (bzip2 I believe). Java 8 显然无法处理压缩方法 12(我相信是 bzip2)。 After trying a number of methods including zip4j (which also fails with these particular files due to another issue), I had success with Apache's commons-compress which supports additional compression methods as mentioned here .在尝试了包括 zip4j 在内的多种方法后(由于另一个问题,这些特定文件也失败了),我成功使用了Apache 的 commons-compress ,它支持此处提到的其他压缩方法

Note that the ZipFile class below is not the one from java.util.zip.请注意,下面的 ZipFile 类不是来自 java.util.zip 的类。

It's actually org.apache.commons.compress.archivers.zip.ZipFile so be careful with the imports.它实际上是org.apache.commons.compress.archivers.zip.ZipFile所以要小心导入。

try (ZipFile zipFile = new ZipFile(archiveFile)) {
    Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
    while (entries.hasMoreElements()) {
        ZipArchiveEntry entry = entries.nextElement();
        File entryDestination = new File(destination, entry.getName());
        if (entry.isDirectory()) {
            entryDestination.mkdirs();
        } else {
            entryDestination.getParentFile().mkdirs();
            try (InputStream in = zipFile.getInputStream(entry); OutputStream out = new FileOutputStream(entryDestination)) {
                IOUtils.copy(in, out);
            }
        }
    }
} catch (IOException ex) {
    log.debug("Error unzipping archive file: " + archiveFile, ex);
}

For Gradle:对于摇篮:

compile 'org.apache.commons:commons-compress:1.18'

Based on zapl's answer, adding try() around Closeable 's closes the streams automatically after use.根据 zapl 的回答,在Closeable周围添加try()在使用后自动关闭流。

public static void unzip(File zipFile, File targetDirectory) {
    try (FileInputStream fis = new FileInputStream(zipFile)) {
        try (BufferedInputStream bis = new BufferedInputStream(fis)) {
            try (ZipInputStream zis = new ZipInputStream(bis)) {
                ZipEntry ze;
                int count;
                byte[] buffer = new byte[Constant.DefaultBufferSize];
                while ((ze = zis.getNextEntry()) != null) {
                    File file = new File(targetDirectory, ze.getName());
                    File dir = ze.isDirectory() ? file : file.getParentFile();
                    if (!dir.isDirectory() && !dir.mkdirs())
                        throw new FileNotFoundException("Failed to ensure directory: " + dir.getAbsolutePath());
                    if (ze.isDirectory())
                        continue;
                    try (FileOutputStream fout = new FileOutputStream(file)) {
                        while ((count = zis.read(buffer)) != -1)
                            fout.write(buffer, 0, count);
                    }
                }
            }
        }
    } catch (Exception ex) {
        //handle exception
    }
}

Using Constant.DefaultBufferSize ( 65536 ) gotten from C# .NET 4 Stream.CopyTo from Jon Skeet's answer here: https://stackoverflow.com/a/411605/1876355使用从C# .NET 4 Stream.CopyTo获得的Constant.DefaultBufferSize ( 65536 ) 来自 Jon Skeet 的回答: https : //stackoverflow.com/a/411605/1876355

I always just see posts using byte[1024] or byte[4096] buffer, never knew it can be much larger which improves performance and is still working perfectly normal.我总是只看到使用byte[1024]byte[4096]缓冲区的帖子,从不知道它可以更大,从而提高性能并且仍然完全正常工作。

Here is the Stream Source code: https://referencesource.microsoft.com/#mscorlib/system/io/stream.cs这是Stream源代码: https : //referencesource.microsoft.com/#mscorlib/system/io/stream.cs

 //We pick a value that is the largest multiple of 4096 that is still smaller than the large object heap threshold (85K). // The CopyTo/CopyToAsync buffer is short-lived and is likely to be collected at Gen0, and it offers a significant // improvement in Copy performance. private const int _DefaultCopyBufferSize = 81920;

However, I dialed it back to 65536 which is also a multiple of 4096 just to be safe.但是,为了安全起见,我将其拨回了65536 ,这也是4096的倍数。

Password Protected Zip File受密码保护的 Zip 文件

if you want to compress files with password you can take a look at this library that can zip files with password easily:如果你想用密码压缩文件,你可以看看这个库,它可以轻松地用密码压缩文件:

Zip:压缩:

ZipArchive zipArchive = new ZipArchive();
zipArchive.zip(targetPath,destinationPath,password);

Unzip:解压:

ZipArchive zipArchive = new ZipArchive();
zipArchive.unzip(targetPath,destinationPath,password);

Rar: .rar:

RarArchive rarArchive = new RarArchive();
rarArchive.extractArchive(file archive, file destination);

The documentation of this library is good enough, I just added a few examples from there.这个库的文档足够好,我只是从那里添加了一些例子。 It's totally free and wrote specially for android.它是完全免费的,专为 android 编写。

Here is more concise version of @arsent solution:这是@arsent 解决方案的更简洁版本:

fun File.unzip(to: File? = null) {
    val destinationDir = to ?: File(parentFile, nameWithoutExtension)
    destinationDir.mkdirs()

    ZipFile(this).use { zipFile ->
        zipFile
            .entries()
            .asSequence()
            .filter { !it.isDirectory }
            .forEach { zipEntry ->
                val currFile = File(destinationDir, zipEntry.name)
                currFile.parentFile?.mkdirs()
                zipFile.getInputStream(zipEntry).use { input ->
                    currFile.outputStream().use { output -> input.copyTo(output) }
                }
            }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM