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

150

我需要一个小的代码片段,可以从给定的 .zip 文件中解压一些文件,并按照它们在压缩文件中的格式分别提取出这些文件。请分享您的知识并帮助我。


3
你可以在这里获取 Kotlin 的解决方案 - https://stackoverflow.com/a/50990992/1162784 - arsent
16个回答

155

如果对peno的版本进行一些优化,性能提升是可以感知到的。

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;
}

13
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> - Lou Morda
1
我认为可以,因为这是解压缩文件的常用方式。只需确保获取正确的“路径”和“zipname”。我还看到了一些你可能感兴趣的东西(虽然你肯定已经看过了):链接 - Vasily Sochinsky
2
因为如果你的 ze 是一个目录,你需要跳过“仅文件”操作。尝试执行这些操作会导致异常。 - Vasily Sochinsky
1
这个答案不应该有效,因为它没有创建缺失的文件来写入数据! - Omar HossamEldin
1
我同意@Shaw的看法。如果zip文件是没有目录条目创建的,它将无法工作。每个文件都应该检查它所要提取到的目录是否存在。zip -j 不会生成目录条目。编辑:实际上我没注意到,但zapi的下一个回答是正确的。 - Krystian
显示剩余8条评论

112

根据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();
    }
}

显著的区别:

  • public static - 这是一个静态的实用方法,可以放在任何地方。
  • 有 2 个 File 参数,因为对于文件来说,String 不太好处理,而且以前不能指定要将 zip 文件解压缩到哪里。此外,path + filename 的连接方式优于https://dev59.com/p3RC5IYBdhLWcg3wFdFx#412495
  • throws - 因为晚捕获异常- 如果真的不感兴趣,可以添加 try catch 语句。
  • 确保在所有情况下都存在所需的目录。并非每个 zip 文件在文件条目之前都包含所有所需的目录条目。这可能会导致 2 个潜在的错误:
    • 如果 zip 文件包含空目录,并且结果目录中存在一个文件,则会忽略该目录。 mkdirs() 的返回值很重要。
    • 可能会在不包含目录的 zip 文件上崩溃。
  • 增加了写入缓冲区的大小,这应该会稍微提高性能。 存储通常是在 4k 块中进行的,而使用比必要小的块进行写入通常会导致速度变慢。
  • 使用 finally 的魔力来防止资源泄漏。

因此:

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

应该做到原始版本的等效

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

@AndoMasahashi 在 Linux 文件系统中,这应该是一个合法的文件名。你遇到了什么错误?文件名最终应该长成什么样子? - zapl
1
它的功能很好,但是当zip文件中的一个文件名不是UTF8格式时,它会抛出异常。因此,我使用了这段代码,它使用了apache的commons-compress库。 - Ashish Tanna
@AshishTanna 实际上,这是一个已知的问题 https://blogs.oracle.com/xuemingshen/entry/non_utf_8_encoding_in - zapl
@Vijaykumar 评论区不是询问问题或调试的好地方。请发布一个新的问题,展示压缩文件如何传输到设备上。最好提供实际文件的链接,以便其他人可以重现您的错误。“CRC错误”意味着提取文件的校验和与zip文件中所述的不匹配,因此通常是压缩数据中的某些内容出了问题。这意味着您需要查看文件以及在它进入上面的代码之前发生了什么。 - zapl
你可能想要用try-with-resource语句块来创建ZipInputStream对象和关闭它。对于Closeable对象,这样做会更加简洁。FileOutputStream也是同理。 - chksr
显示剩余6条评论

26

这是我使用的解压方法:

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;
}

你认为同样的代码可以用于解压或拆包APK扩展文件obb文件吗? - LOG_TAG

22

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)
                }
            }
        }
    }

}

使用方法

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

1
它在 'output.outputStream()' 行抛出一个异常,提示 "FileNotFoundException ... (不是目录)"。 - Hamza Khan
好的例子,谢谢!运行得很好。 - dajver

13

10
你应该提供一个代码示例。你错过了很多分数。 - Cameron Lowell Palmer
它也适用于7zip吗? - Astha Garg

8
使用以下类
    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(); 
    } 
  } 


 }

使用方法

 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();

权限

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

可以看到文件名,但是在尝试提取文件时,我遇到了FileNotFoundException错误。 - Parth Anjaria

7

虽然这里已经给出的答案很好,但我发现它们的速度略低于我的期望。所以我使用了zip4j,我认为这是最好的解决方案,因为它的速度非常快。它还允许不同的压缩选项,我觉得非常有用。


5
根据 @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();
    }
}

3
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;
}

 }

->助手类(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布局(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>

->在清单文件中的权限:

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

2
我正在处理zip文件,但Java的ZipFile类无法处理。据我了解,Java 8无法处理压缩方法12(我认为是bzip2)。尝试了许多方法,包括使用zip4j(由于其他问题,这些特定文件也失败了),我最终成功地使用了Apache的commons-compress,它支持如此处所述的其他压缩方法
请注意下面的ZipFile类不是java.util.zip中的那个。
实际上,它是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);
}

对于Gradle:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接