在android上压缩/压缩一个充满文件的文件夹

Posted

技术标签:

【中文标题】在android上压缩/压缩一个充满文件的文件夹【英文标题】:zip/compress a folder full of files on android 【发布时间】:2011-10-04 17:52:48 【问题描述】:

我需要压缩一个“项目”文件夹以允许用户通过电子邮件共享项目。我找到了一个将多个文件压缩到一个 zip 中的类,但我需要将文件夹结构保留在我的 zip 中。有没有办法在android上实现这一点?提前致谢。

【问题讨论】:

澄清一下,你是指Android开发项目还是app中开发的项目? 在应用程序中开发的项目,对此感到抱歉。另外我认为这就是我需要的:***.com/questions/1399126/… 但是当我复制它时,我尽了最大努力,但找不到该行的方法: Deque queue = new LinkedList();去工作。我知道 Deque 是一个接口,而 LinkedList 实现了它,但是 eclipes 总是给我错误。 没关系,找了好久才知道怎么做:crazysquirrel.com/computing/java/basics/… 【参考方案1】:

这段代码应该可以解决问题。

注意:您必须通过将 WRITE_EXTERNAL_STORAGE 权限添加到 manifest.xml 文件来为您的应用添加文件写入权限。

/*
 * 
 * Zips a file at a location and places the resulting zip file at the toLocation
 * Example: zipFileAtPath("downloads/myfolder", "downloads/myFolder.zip");
 */

public boolean zipFileAtPath(String sourcePath, String toLocation) 
    final int BUFFER = 2048;

    File sourceFile = new File(sourcePath);
    try 
        BufferedInputStream origin = null;
        FileOutputStream dest = new FileOutputStream(toLocation);
        ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(
                dest));
        if (sourceFile.isDirectory()) 
            zipSubFolder(out, sourceFile, sourceFile.getParent().length());
         else 
            byte data[] = new byte[BUFFER];
            FileInputStream fi = new FileInputStream(sourcePath);
            origin = new BufferedInputStream(fi, BUFFER);
            ZipEntry entry = new ZipEntry(getLastPathComponent(sourcePath));
            entry.setTime(sourceFile.lastModified()); // to keep modification time after unzipping
            out.putNextEntry(entry);
            int count;
            while ((count = origin.read(data, 0, BUFFER)) != -1) 
                out.write(data, 0, count);
            
        
        out.close();
     catch (Exception e) 
        e.printStackTrace();
        return false;
    
    return true;


/*
 * 
 * Zips a subfolder
 * 
 */

private void zipSubFolder(ZipOutputStream out, File folder,
        int basePathLength) throws IOException 

    final int BUFFER = 2048;

    File[] fileList = folder.listFiles();
    BufferedInputStream origin = null;
    for (File file : fileList) 
        if (file.isDirectory()) 
            zipSubFolder(out, file, basePathLength);
         else 
            byte data[] = new byte[BUFFER];
            String unmodifiedFilePath = file.getPath();
            String relativePath = unmodifiedFilePath
                    .substring(basePathLength);
            FileInputStream fi = new FileInputStream(unmodifiedFilePath);
            origin = new BufferedInputStream(fi, BUFFER);
            ZipEntry entry = new ZipEntry(relativePath);
            entry.setTime(file.lastModified()); // to keep modification time after unzipping
            out.putNextEntry(entry);
            int count;
            while ((count = origin.read(data, 0, BUFFER)) != -1) 
                out.write(data, 0, count);
            
            origin.close();
        
    


/*
 * gets the last path component
 * 
 * Example: getLastPathComponent("downloads/example/fileToZip");
 * Result: "fileToZip"
 */
public String getLastPathComponent(String filePath) 
    String[] segments = filePath.split("/");
    if (segments.length == 0)
        return "";
    String lastPathComponent = segments[segments.length - 1];
    return lastPathComponent;

【讨论】:

嘿@HailZeon!很棒的代码,真的很有帮助。 getLastPathComponent(sourcePath) 做什么以及它是如何定义的?谢谢! 嘿@RaymondMachira。我添加了 getLastPathComponent 的定义。它基本上采用路径(“folder1/subfolder/example.txt”)并返回“folder1/subfolder/”。 zip 文件中可能没有定义包含文件夹,因此我们需要删除子路径并添加它。 我在 zip 文件中得到了空的命名文件夹,解决方法是将 sourceFile.getParent().length() 替换为 sourceFile.getParent().length()+1。 你应该用 filePath.substring(filePath.lastIndexOf("/")) 替换 getLastPathComponent(String filePath) 内容 很棒的代码——我正在为我发现的一个错误添加一个修复程序——当尝试在 Windows 上解压缩时,由于解析给 ZipEntry 对象的相对路径上的第一个“/”问题而失败。 ===== 代码修复 ==== String relativePath = unmodifiedFilePath .substring(basePathLength+1);【参考方案2】:

这就是我的做法:

private static void zipFolder(String inputFolderPath, String outZipPath) 
    try 
        FileOutputStream fos = new FileOutputStream(outZipPath);
        ZipOutputStream zos = new ZipOutputStream(fos);
        File srcFile = new File(inputFolderPath);
        File[] files = srcFile.listFiles();
        Log.d("", "Zip directory: " + srcFile.getName());
        for (int i = 0; i < files.length; i++) 
            Log.d("", "Adding file: " + files[i].getName());
            byte[] buffer = new byte[1024];
            FileInputStream fis = new FileInputStream(files[i]);
            zos.putNextEntry(new ZipEntry(files[i].getName()));
            int length;
            while ((length = fis.read(buffer)) > 0) 
                zos.write(buffer, 0, length);
            
            zos.closeEntry();
            fis.close();
        
        zos.close();
     catch (IOException ioe) 
        Log.e("", ioe.getMessage());
    

【讨论】:

如果源文件夹包含另一个文件夹,则无法正常工作 压缩给定的文件夹。我压缩了一个充满图像的文件夹,给定的文件夹被压缩了,但是解压缩时文件夹中的图像不可读。怎么可能解决? 如果要解压,只需在文件夹名称末尾添加扩展名.zip,即可轻松解压。【参考方案3】:

我已经修改了来自 HailZeon 的代码以在 Windows 下正常工作。 Zip 条目必须在启动新条目之前关闭,并且条目名称(如“/file.txt”)处的起始“/”也会产生问题

/**
 * Zips a Folder to "[Folder].zip"
 * @param toZipFolder Folder to be zipped
 * @return the resulting ZipFile
 */
public static File zipFolder(File toZipFolder) 
    File ZipFile = new File(toZipFolder.getParent(), format("%s.zip", toZipFolder.getName()));
    try 
        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(ZipFile));
        zipSubFolder(out, toZipFolder, toZipFolder.getPath().length());
        out.close();
        return ZipFile;
     catch (Exception ex) 
        ex.printStackTrace();
        return null;
    


/**
 * Main zip Function
 * @param out Target ZipStream
 * @param folder Folder to be zipped
 * @param basePathLength Length of original Folder Path (for recursion)
 */
private static void zipSubFolder(ZipOutputStream out, File folder, int basePathLength) throws IOException 

    final int BUFFER = 2048;

    File[] fileList = folder.listFiles();
    BufferedInputStream origin = null;
    for (File file : fileList) 
        if (file.isDirectory()) 
            zipSubFolder(out, file, basePathLength);
         else 
            byte data[] = new byte[BUFFER];

            String unmodifiedFilePath = file.getPath();
            String relativePath = unmodifiedFilePath.substring(basePathLength + 1);

            FileInputStream fi = new FileInputStream(unmodifiedFilePath);
            origin = new BufferedInputStream(fi, BUFFER);

            ZipEntry entry = new ZipEntry(relativePath);
            entry.setTime(file.lastModified()); // to keep modification time after unzipping
            out.putNextEntry(entry);

            int count;
            while ((count = origin.read(data, 0, BUFFER)) != -1) 
                out.write(data, 0, count);
            
            origin.close();
            out.closeEntry();
        
    

【讨论】:

【参考方案4】:

使用来自location 的 zip4j 库。将 jar 文件导入您的 "app/libs/" 文件夹。并使用以下代码压缩您的目录/文件...

try 
    File input = new File("path/to/your/input/fileOrFolder");
    String destinationPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "zippedItem.zip";
    ZipParameters parameters = new ZipParameters();
    parameters.setCompressionMethod(Zip4jConstants.COMP_STORE);
    parameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL);
    File output = new File(destinationPath);
    ZipFile zipFile = new ZipFile(output);
    // .addFolder or .addFile depending on your input
    if (sourceFile.isDirectory())
        zipFile.addFolder(input, parameters);
    else
        zipFile.addFile(input, parameters);
    // Your input file/directory has been zipped at this point and you
    // can access it as a normal file using the following line of code
    File zippedFile = zipFile.getFile();
 catch (ZipException e) 
    Log.e(TAG, Log.getStackTraceString(e));

【讨论】:

【参考方案5】:

如果您使用 java.util.zip 对象,那么您可以编写不修改目录结构的脚本。

【讨论】:

【参考方案6】:
public static boolean zip(File sourceFile, File zipFile) 
    List<File> fileList = getSubFiles(sourceFile, true);
    ZipOutputStream zipOutputStream = null;
    try 
        zipOutputStream = new ZipOutputStream(new FileOutputStream(zipFile));
        int bufferSize = 1024;
        byte[] buf = new byte[bufferSize];
        ZipEntry zipEntry;
        for(int i = 0; i < fileList.size(); i++) 
            File file = fileList.get(i);
            zipEntry = new ZipEntry(sourceFile.toURI().relativize(file.toURI()).getPath());
            zipOutputStream.putNextEntry(zipEntry);
            if (!file.isDirectory()) 
                InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
                int readLength;
                while ((readLength = inputStream.read(buf, 0, bufferSize)) != -1) 
                    zipOutputStream.write(buf, 0, readLength);
                
            
        
     catch (Exception e) 
        e.printStackTrace();
        return false;
     finally 
        IoUtils.closeOS(zipOutputStream);
    
    return true;
    

public static List<File> getSubFiles(File baseDir, boolean isContainFolder) 
    List<File> fileList = new ArrayList<>();
    File[] tmpList = baseDir.listFiles();
    for (File file : tmpList) 
        if (file.isFile()) 
            fileList.add(file);
        
        if (file.isDirectory()) 
            if (isContainFolder) 
                fileList.add(file); //key code
            
            fileList.addAll(getSubFiles(file));
        
    
    return fileList;

【讨论】:

【参考方案7】:

只需将@HailZeon 的答案转换为 Kotlin:

import java.io.BufferedInputStream
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.ZipOutputStream
import java.util.zip.ZipException

private const val BUFFER = 2048

/**
 * Compresses a file into a zip file
 * @author Arnau Mora
 * @since 20210318
 * @param file The source file
 * @param target The target file
 *
 * @throws NullPointerException If the entry name is null
 * @throws IllegalArgumentException If the entry name is longer than 0xFFFF byte
 * @throws SecurityException If a security manager exists and its SecurityManager.checkRead(String)
 * method denies read access to the file
 * @throws ZipException If a ZIP format error has occurred
 * @throws IOException If an I/O error has occurre
 */
@Throws(
    NullPointerException::class,
    IllegalArgumentException::class,
    SecurityException::class,
    ZipException::class,
    IOException::class
)
fun zipFile(file: File, target: File) 
    val origin: BufferedInputStream
    val dest: FileOutputStream
    var zipOutput: ZipOutputStream? = null
    try 
        dest = target.outputStream()
        zipOutput = ZipOutputStream(dest.buffered(BUFFER))
        if (file.isDirectory)
            zipSubFolder(zipOutput, file, file.parent!!.length)
        else 
            val data = ByteArray(BUFFER)
            val fi = file.inputStream()
            origin = fi.buffered(BUFFER)
            val entry = ZipEntry(getLastPathComponent(file.path))
            entry.time = file.lastModified()
            zipOutput.putNextEntry(entry)
            var count = origin.read(data, 0, BUFFER)
            while (count != -1) 
                zipOutput.write(data, 0, count)
                count = origin.read(data, 0, BUFFER)
            
        
     finally 
        zipOutput?.close()
    


private fun zipSubFolder(zipOutput: ZipOutputStream, folder: File, basePathLength: Int) 
    val files = folder.listFiles() ?: return
    var origin: BufferedInputStream? = null
    try 
        for (file in files) 
            if (file.isDirectory)
                zipSubFolder(zipOutput, folder, basePathLength)
            else 
                val data = ByteArray(BUFFER)
                val unmodifiedFilePath = file.path
                val relativePath = unmodifiedFilePath.substring(basePathLength)
                val fi = FileInputStream(unmodifiedFilePath)
                origin = fi.buffered(BUFFER)
                val entry = ZipEntry(relativePath)
                entry.time = file.lastModified()
                zipOutput.putNextEntry(entry)
                var count = origin.read(data, 0, BUFFER)
                while (count != -1) 
                    zipOutput.write(data, 0, count)
                    count = origin.read(data, 0, BUFFER)
                
            
        
     finally 
        origin?.close()
    


/*
 * gets the last path component
 *
 * Example: getLastPathComponent("downloads/example/fileToZip");
 * Result: "fileToZip"
 */
private fun getLastPathComponent(filePath: String): String 
    val segments = filePath.split("/").toTypedArray()
    return if (segments.isEmpty()) "" else segments[segments.size - 1]

【讨论】:

【参考方案8】:

如果有人来到这里试图找到 @HailZeon 代码的 java8 更清洁的重构版本。这里是:

    private static final int BUFFER_SIZE = 2048;

    public File zip(File source, String zipFileName) throws IOException 
        File zipFile = null;
        if(source != null) 
            File zipFileDestination = new File(getCacheDir(), zipFileName);
            if (zipFileDestination.exists()) 
                zipFileDestination.delete();
            
            zipFile = zip(source, zipFileDestination);
        
        return zipFile;
    

    public File zip(File source, File zipFile) throws IOException 
        int relativeStartingPathIndex = zipFile.getAbsolutePath().lastIndexOf("/") + 1;
        if (source != null && zipFile != null) 
            try (ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream( new FileOutputStream(zipFile)))) 
                if (source.isDirectory()) 
                    zipSubDir(out, source, relativeStartingPathIndex);
                 else 
                    try (BufferedInputStream origin = new BufferedInputStream(new FileInputStream(source))) 
                        zipEntryFile(origin, out, source, relativeStartingPathIndex);
                    
                
            
        

        return zipFile;
    

    private void zipSubDir(ZipOutputStream out, File dir, int relativeStartingPathIndex) throws IOException 

        File[] files = dir.listFiles();
        if (files != null) 

            for(File file : files) 
                if(file.isDirectory()) 
                    zipSubDir(out, file, relativeStartingPathIndex);
                 else 
                    try (BufferedInputStream origin = new BufferedInputStream(new FileInputStream(file))) 
                        zipEntryFile(origin, out, file, relativeStartingPathIndex);
                    
                
            

        
    

    private void zipEntryFile(BufferedInputStream origin, ZipOutputStream out, File file, int relativeStartingPathIndex) throws IOException 
        String relativePath = file.getAbsolutePath().substring(relativeStartingPathIndex);
        ZipEntry entry = new ZipEntry(relativePath);
        entry.setTime(file.lastModified()); // to keep modification time after unzipping
        out.putNextEntry(entry);
        byte[] data = new byte[BUFFER_SIZE];
        int count;
        while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1) 
            out.write(data, 0, count);
        
    

【讨论】:

以上是关于在android上压缩/压缩一个充满文件的文件夹的主要内容,如果未能解决你的问题,请参考以下文章

在 Android 应用程序中解压缩 sd 卡上的压缩文件

如何使用 Eclipse 压缩 .apk 文件?

在 Linux 上压缩文件:zip 命令的各种变体及用法 | Linux 中国

从压缩文本文件中获取文本行的阅读器

Android - 解压缩文件夹?

无法使用 Powershell 2.0 压缩文件