【问题标题】:Java program ignoring all the files inside the zip fileJava程序忽略zip文件中的所有文件
【发布时间】:2022-01-30 14:53:37
【问题描述】:

当我通过控制台提供 zip 文件夹路径时,我有程序。它将遍历该文件夹中的每个项目(每个子项目、孩子的孩子等)。但是如果它遇到一个 zip 文件夹,它将忽略 zip 文件夹中的所有内容,我需要读取所有内容,包括 zip 文件夹中的文件。

这是遍历每个项目的方法:

    public static String[] getLogBuffers(String path) throws IOException//path is given via console
  {
    String zipFileName = path;
    String destDirectory = path;
    BufferedInputStream errorLogBuffer = null;
    BufferedInputStream windowLogBuffer = null;
    String strErrorLogFileContents="";
    String strWindowLogFileContents="";
    String[] errorString=new String[2];



    byte[] buffer = new byte[1024];
    ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFileName));
    ZipEntry zipEntry = zis.getNextEntry();
    while (zipEntry != null)
    {
      String filePath = destDirectory + "/" + zipEntry.getName();
      System.out.println("unzipping" + filePath);
      if (!zipEntry.isDirectory())
      {
                if (zipEntry.getName().endsWith("errorlog.txt"))
                {
                  ZipFile zipFile = new ZipFile(path);
                  InputStream errorStream = zipFile.getInputStream(zipEntry);
                  BufferedInputStream bufferedInputStream=new BufferedInputStream(errorStream);
                  byte[] contents = new byte[1024];
                  System.out.println("ERRORLOG NAME"+zipEntry.getName());
                  int bytesRead = 0;
                  while((bytesRead = bufferedInputStream.read(contents)) != -1) {
                    strErrorLogFileContents += new String(contents, 0, bytesRead);
                  }

                }
                if (zipEntry.getName().endsWith("windowlog.txt"))
                { ZipFile zipFile = new ZipFile(path);
                  InputStream windowStream = zipFile.getInputStream(zipEntry);
                  BufferedInputStream bufferedInputStream=new BufferedInputStream(windowStream);
                  byte[] contents = new byte[1024];
                  System.out.println("WINDOWLOG NAME"+zipEntry.getName());

                  int bytesRead = 0;
                  while((bytesRead = bufferedInputStream.read(contents)) != -1) {
                    strWindowLogFileContents += new String(contents, 0, bytesRead);
                  }

                }

      }
    
      zis.closeEntry();
      zipEntry = zis.getNextEntry();

    }
    errorString[0]=strErrorLogFileContents;
    errorString[1]=strWindowLogFileContents;
    zis.closeEntry();
    zis.close();
    System.out.println("Buffers ready");
    return errorString;
  }

在父 zip 文件夹中访问的项目(我的控制台输出):

unzippingC:logFolders/logX3.zip/logX3/
unzippingC:logFolders/logX3.zip/logX3/Anan/
unzippingC:logFolders/logX3.zip/logX3/Anan/errorreports/
unzippingC:logFolders/logX3.zip/logX3/Anan/errorreports/2021-11-23_103518.zip
unzippingC:logFolders/logX3.zip/logX3/Anan/errorreports/errorlog.txt
unzippingC:logX3.zip/logX3/Anan/errorreports/version.txt
unzippingC:logFolders/logX3.zip/logX3/Anan/errorreports/windowlog.txt

如您所见,该程序只运行到 2021-11-23_103518.zip,之后进入另一条路径,但 2021-11-23_103518.zip 有我需要访问的子项(文件) 感谢任何帮助,谢谢

【问题讨论】:

  • 尽管 Windows 使 zip 文件的行为有点像文件夹(包括我自己在内的许多人认为这是一个糟糕的主意),但 zip 文件在任何方面都不是文件夹。要读取另一个 zip 文件中的 zip 文件,您需要一个新的 ZipInputStream 或一个新的 ZipFile。 (从另一个 ZipInputStream 创建一个 ZipInputStream 并从中读取可能会很慢,因此我建议将条目复制到一个临时文件中,从中读取,然后将其删除。)

标签: java zip java-io zipinputstream


【解决方案1】:

zip 文件不是文件夹。虽然 Windows 将 zip 文件视为文件夹,但*它不是文件夹。 .zip 文件是具有内部条目表的单个文件,每个条目都包含压缩数据。

您读取的每个内部 .zip 文件都需要一个新的 ZipFileZipInputStream。没有办法解决这个问题。

您不应该创建新的 ZipFile 实例来读取相同的 .zip 文件的条目。您只需要一个 ZipFile 对象。您可以使用其entries() 方法查看其条目,并且您可以使用ZipFile 的getInputStream 方法读取每个条目。

(如果在 Windows 上使用多个对象读取同一个 zip 文件会遇到文件锁定问题,我不会感到惊讶。)

try (ZipFile zipFile = new ZipFile(path))
{
    Enumeration<? extends ZipEntry> entries = zipFile.entries();
    while (entries.hasMoreElements())
    {
        ZipEntry zipEntry = entries.nextElement();

        if (zipEntry.getName().endsWith("errorlog.txt"))
        {
            try (InputStream errorStream = zipFile.getInputStream(zipEntry))
            {
                // ...
            }
        }
    }
}

请注意,没有创建其他 ZipFile 或 ZipInputStream 对象。只有zipFile 读取并遍历文件。还要注意使用try-with-resources 语句隐式关闭ZipFile 和InputStream。

您不应该使用+= 来构建字符串。 这样做会创建大量的中间字符串对象,这些对象必须被垃圾回收,这会损害您的程序性能。您应该将每个 zip 条目的 InputStream 包装在 InputStreamReader 中,然后使用该 Reader 的 transferTo 方法附加到包含您的组合日志的单个 StringWriter

String strErrorLogFileContents = new StringWriter();
String strWindowLogFileContents = new StringWriter();

try (ZipFile zipFile = new ZipFile(path))
{
    Enumeration<? extends ZipEntry> entries = zipFile.entries();
    while (entries.hasMoreElements())
    {
        ZipEntry zipEntry = entries.nextElement();

        if (zipEntry.getName().endsWith("errorlog.txt"))
        {
            try (Reader entryReader = new InputStreamReader(
                zipFile.getInputStream(zipEntry),
                StandardCharsets.UTF_8))
            {
                entryReader.transferTo(strErrorLogFileContents);
            }
        }
    }
}

注意 StandardCharsets.UTF_8 的使用。 在不指定 Charset 的情况下从字节创建字符串几乎是不正确的。如果您不提供字符集,Java 将使用系统的默认字符集,这意味着您的程序在 Windows 中的行为与在其他操作系统中的行为不同。

如果你被 Java 8 卡住,你将没有 Reader 的 transferTo 方法,所以你必须自己做:

        if (zipEntry.getName().endsWith("errorlog.txt"))
        {
            try (Reader entryReader = new BufferedReader(
                new InputStreamReader(
                    zipFile.getInputStream(zipEntry),
                    StandardCharsets.UTF_8)))
            {
                int c;
                while ((c = entryReader.read()) >= 0)
                {
                    strErrorLogFileContents.write(c);
                }
            }
        }

使用 BufferedReader 意味着您不需要创建自己的数组并自己实现批量读取。 BufferedReader 已经为您做到了。

如上所述,一个本身就是一个内部 zip 文件的 zip 条目需要一个全新的 ZipFile 或 ZipInputStream 对象来读取它。我推荐 copyingtemporary file 的条目,因为已知从另一个 ZipInputStream 生成的 ZipInputStream 读取速度很慢,然后在完成读取后删除临时文件。

try (ZipFile zipFile = new ZipFile(path))
{
    Enumeration<? extends ZipEntry> entries = zipFile.entries();
    while (entries.hasMoreElements())
    {
        ZipEntry zipEntry = entries.nextElement();

        if (zipEntry.getName().endsWith(".zip"))
        {
            Path tempZipFile = Files.createTempFile(null, ".zip");
            try (InputStream errorStream = zipFile.getInputStream(zipEntry))
            {
                Files.copy(errorStream, tempZipFile,
                    StandardCopyOption.REPLACE_EXISTING);
            }

            String[] logsFromZip = getLogBuffers(tempZipFile.toString());

            strErrorLogFileContents.write(logsFromZip[0]);
            strWindowLogFileContents.write(logsFromZip[1]);

            Files.delete(tempZipFile);
        }
    }
}

最后,考虑为您的返回值创建一个有意义的类。字符串数组很难理解。调用者不会知道它总是包含两个元素,也不会知道这两个元素是什么。自定义返回类型会很短:

public class Logs {
    private final String errorLog;

    private final String windowLog;

    public Logs(String errorLog,
                String windowLog)
    {
        this.errorLog = errorLog;
        this.windowLog = windowLog;
    }

    public String getErrorLog()
    {
        return errorLog;
    }

    public String getWindowLog()
    {
        return windowLog;
    }
}

从 Java 16 开始,您可以使用 record 来缩短声明:

public record Logs(String errorLog,
                   String windowLog)
{ }

无论是使用记录还是写出类,都可以在方法中使用它作为返回类型:

public static Logs getLogBuffers(String path) throws IOException
{
    // ...

    return new Logs(
        strErrorLogFileContents.toString(),
        strWindowLogFileContents.toString());
}

* Windows 资源管理器外壳将 zip 文件视为文件夹的做法是一个非常糟糕的用户界面。我知道我不是唯一一个这么想的人。它通常最终使用户的事情变得更加困难而不是更容易。

【讨论】:

  • 嘿,感谢您为完成这一切所付出的努力。只需稍加调整,我就能让它工作,我不得不调整,因为我们使用 Java 8 并且 transferTo() 不可用,不是因为你的代码有什么问题。欣赏它,非常感谢! p.s-关于在 Java 8 中使用什么代替 transferTo() 有什么想法吗?
  • @kanishkamalpana 使用 Java 8 替代 Reader.transferTo 更新了答案。
  • 非常感谢!你帮了大忙
猜你喜欢
  • 1970-01-01
  • 2018-03-07
  • 1970-01-01
  • 2019-07-14
  • 1970-01-01
  • 1970-01-01
  • 2018-02-23
  • 2013-02-08
  • 2015-12-25
相关资源
最近更新 更多