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

我需要一个小的代码片段,解压缩几个文件从一个给定的。并根据压缩文件中的格式给出单独的文件。请张贴你的知识,并帮助我出来。

128329 次浏览

Android 有内置的 JavaAPI。查看 Util.zip包。

ZipInputStream是您应该研究的内容。从 ZipInputStream 读取 ZipEntry 并将其转储到文件系统/文件夹中。检查 类似的例子压缩成拉链文件。

这是我的 unzip 方法,我使用:

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

下面是一个 ZipFileIterator (类似于 java Iterator,但用于 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
}
}
}

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

虽然这里已经有了很好的答案,但是我发现它们比我预期的要慢一些。相反,我使用 Zip4j,我认为这是最好的解决方案,因为它的速度。它还允许不同的压缩量选项,我发现这很有用。

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)

    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>

- > Menifest 的许可证档案:

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

根据瓦西里•索钦斯基(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是:/for 文件,而且之前无法指定解压缩压缩文件的位置。也是 path + filename级联 > https://stackoverflow.com/a/412495/995891
  • 因为 迟到了-添加一个尝试捕捉,如果真的不感兴趣。
  • 确保在所有情况下都存在所需的目录。并非每个 zip 都在文件项之前包含所有必需的目录项。这里有两个潜在的漏洞:
    • 如果 zip 包含一个空目录,并且结果目录中存在一个现有的文件,则忽略该文件。mkdirs()的返回值很重要。
    • 可能会在不包含目录的 zip 文件上崩溃。
  • 增加写缓冲区大小,这应该会提高性能一点点。存储通常是在4k 块和写在较小的块通常比必要的慢。
  • 使用 finally的魔力来防止资源泄漏。

那么

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

应该做的相当于原来的

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

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

使用以下类

    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" />

最小的例子,我用来解压缩特定的文件从我的 zipfile 到我的应用程序缓存文件夹。然后使用不同的方法读取清单文件。

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


}

受密码保护的压缩文件

如果你想用密码压缩文件,你可以看看 这个图书馆,它可以很容易地用密码压缩文件:

邮编:

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

解开拉链:

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

答案:

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

这个库的文档已经足够好了,我只是从中添加了一些示例。 它是完全免费的,而且是专门为机器人编写的。

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

我使用的是 Java 的 ZipFile 类无法处理的 zip 文件。Java8显然不能处理压缩方法12(我认为是 bzip2)。在尝试了包括 zip4j (由于另一个问题,这些特定文件也会失败)在内的许多方法之后,我在使用支持 这里提到的其他压缩方法阿帕奇的公共空间-压缩时取得了成功。

注意,下面的 ZipFile 类不是 java.util.zip 中的类。

它实际上是 压缩文件压缩文件压缩文件压缩文件压缩文件压缩文件压缩文件压缩文件压缩文件压缩文件压缩文件压缩文件压缩文件压缩文件压缩文件所以小心进口。

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

格拉德尔:

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

根据 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
}
}

使用从 C# .NET 4 流。复制得到的 Constant.DefaultBufferSize(65536) ,来自 Jon Skeet 的答案: Https://stackoverflow.com/a/411605/1876355

我总是看到帖子使用 byte[1024]byte[4096]缓冲区,从来不知道它可以更大,提高性能,仍然工作完全正常。

下面是 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;

然而,为了安全起见,我拨回了 65536,它也是 4096的倍数。

以下是@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) }
}
}
}
}