2009-09-09 23 views
35

java.util.zipを使ってアーカイブを解凍しようとしているうちに、私は多くの問題に遭遇しました。最終的に出力が得られたので、私は「正しい」出力を得ることに苦労します。私はいくつかの修正を加えたODTファイルを抽出しました(ディレクトリは説明に合っています)。 ODTファイル構造を再作成するために、そのディレクトリを圧縮したいと思います。ディレクトリを圧縮して.odtで終わるように名前を変更すると問題なく動作します。java.util.zip - ディレクトリ構造を再作成する

主な問題は、私はディレクトリの内部構造を失うことです。すべてが「フラット」になり、元の多層構造を維持する方法を見つけられないようです。私は問題を見つけることができないので、これについていくつかの助けに感謝します。ここで

は、関連するコードスニペットです:

ZipOutputStream out = new ZipOutputStream(new FileOutputStream(
    FILEPATH.substring(0, FILEPATH.lastIndexOf(SEPARATOR) + 1).concat("test.zip"))); 
    compressDirectory(TEMPARCH, out); 

SEPARATORは、システムファイルの区切りであるとFILEPATHは、私が優先されますが、テスト目的のためにここで行われていないオリジナルのODTのファイルパスです。私は同じディレクトリにあるtest.zipファイルに書き込むだけです。

private void compressDirectory(String directory, ZipOutputStream out) throws IOException 
{ 
    File fileToCompress = new File(directory); 
    // list contents. 
    String[] contents = fileToCompress.list(); 
    // iterate through directory and compress files. 
    for(int i = 0; i < contents.length; i++) 
    { 
     File f = new File(directory, contents[i]); 
     // testing type. directories and files have to be treated separately. 
     if(f.isDirectory()) 
     { 
      // add empty directory 
      out.putNextEntry(new ZipEntry(f.getName() + SEPARATOR)); 
      // initiate recursive call 
      compressDirectory(f.getPath(), out); 
      // continue the iteration 
      continue; 
     }else{ 
      // prepare stream to read file. 
      FileInputStream in = new FileInputStream(f); 
      // create ZipEntry and add to outputting stream. 
      out.putNextEntry(new ZipEntry(f.getName())); 
      // write the data. 
      int len; 
      while((len = in.read(data)) > 0) 
      { 
       out.write(data, 0, len); 
      } 
      out.flush(); 
      out.closeEntry(); 
      in.close(); 
     } 
    } 
} 

圧縮するファイルを含むディレクトリが作成されたファイルと同じディレクトリにどこかにユーザ空間ではないです。私はこれが問題になるかもしれないと仮定しますが、私は実際にどのように見ることができません。また、同じストリームを出力する際に​​問題が発生する可能性があると考えましたが、やり直しができません。いくつかの例とチュートリアルでは、getName()の代わりにgetPath()を使用していましたが、それを変更すると空のzipファイルが作成されます。

答えて

87

URIクラスは、相対パスで作業する場合に便利です。

File mydir = new File("C:\\mydir"); 
File myfile = new File("C:\\mydir\\path\\myfile.txt"); 
System.out.println(mydir.toURI().relativize(myfile.toURI()).getPath()); 

上記のコードでは、文字列path/myfile.txtが出力されます。

public static void zip(File directory, File zipfile) throws IOException { 
    URI base = directory.toURI(); 
    Deque<File> queue = new LinkedList<File>(); 
    queue.push(directory); 
    OutputStream out = new FileOutputStream(zipfile); 
    Closeable res = out; 
    try { 
     ZipOutputStream zout = new ZipOutputStream(out); 
     res = zout; 
     while (!queue.isEmpty()) { 
     directory = queue.pop(); 
     for (File kid : directory.listFiles()) { 
      String name = base.relativize(kid.toURI()).getPath(); 
      if (kid.isDirectory()) { 
      queue.push(kid); 
      name = name.endsWith("/") ? name : name + "/"; 
      zout.putNextEntry(new ZipEntry(name)); 
      } else { 
      zout.putNextEntry(new ZipEntry(name)); 
      copy(kid, zout); 
      zout.closeEntry(); 
      } 
     } 
     } 
    } finally { 
     res.close(); 
    } 
    } 

は、このコードは、日付を保存していないになり、私はそれがシンボリックリンクのようなものをどのように反応するかわからない:

は完全を期すため、ここではディレクトリをアーカイブするためのzip方法です。ディレクトリエントリを追加しようとしないので、空のディレクトリは含まれません。

対応unzipコマンド:それらが依存した

public static void unzip(File zipfile, File directory) throws IOException { 
    ZipFile zfile = new ZipFile(zipfile); 
    Enumeration<? extends ZipEntry> entries = zfile.entries(); 
    while (entries.hasMoreElements()) { 
     ZipEntry entry = entries.nextElement(); 
     File file = new File(directory, entry.getName()); 
     if (entry.isDirectory()) { 
     file.mkdirs(); 
     } else { 
     file.getParentFile().mkdirs(); 
     InputStream in = zfile.getInputStream(entry); 
     try { 
      copy(in, file); 
     } finally { 
      in.close(); 
     } 
     } 
    } 
    } 

ユーティリティメソッド:

private static void copy(InputStream in, OutputStream out) throws IOException { 
    byte[] buffer = new byte[1024]; 
    while (true) { 
     int readCount = in.read(buffer); 
     if (readCount < 0) { 
     break; 
     } 
     out.write(buffer, 0, readCount); 
    } 
    } 

    private static void copy(File file, OutputStream out) throws IOException { 
    InputStream in = new FileInputStream(file); 
    try { 
     copy(in, out); 
    } finally { 
     in.close(); 
    } 
    } 

    private static void copy(InputStream in, File file) throws IOException { 
    OutputStream out = new FileOutputStream(file); 
    try { 
     copy(in, out); 
    } finally { 
     out.close(); 
    } 
    } 

バッファサイズは完全に任意です。

+0

ありがとうございました!ああ元のディレクトリ形式が圧縮コードでどのように保持されているかわかりません。 ODTには空のディレクトリがあります。あなたのコードを理解する限り、それらのディレクトリは決して作成されません。多分私は何かを逃していますか – Eric

+2

ディレクトリは '/'で終わる名前の空のエントリです。私はコードを変更しました。 – McDowell

+0

私はあなたのコードの構造に適応し、再帰呼び出しを中止しました。私はこれを見て間違った方法だと思う。 1つの例外を除いて、コードは円滑に実行されます。空でない場合でもほとんどの子ディレクトリに空のフォルダが追加されます。私は、次の行を削除すると問題が解決することがわかりました:name = name.endsWith( "/")?名前:名前+ "/";私は、 "\"を追加してディレクトリを追加すると、内部に空のフォルダも作成されると思う。単にZipEntriesに構造体の世話をさせるだけで、すべてがうまくいくように見えます。あなたの助けをありがとう! – Eric

7

私はそれを取り戻すための方法はありませんので、あなたは、ディレクトリのパスを保存しないあなたのコード内の2つの問題、

  1. を参照してください。
  2. Windowsでは、パス区切り文字として「/」を使用する必要があります。いくつかの解凍プログラムは\が好きではありません。

参考までに自分のバージョンが含まれています。我々はこれを使って写真をダウンロードして解凍し、さまざまな解凍プログラムで動作させます。これは、ディレクトリ構造とタイムスタンプを保持します。

public static void createZipFile(File srcDir, OutputStream out, 
    boolean verbose) throws IOException { 

    List<String> fileList = listDirectory(srcDir); 
    ZipOutputStream zout = new ZipOutputStream(out); 

    zout.setLevel(9); 
    zout.setComment("Zipper v1.2"); 

    for (String fileName : fileList) { 
    File file = new File(srcDir.getParent(), fileName); 
    if (verbose) 
    System.out.println(" adding: " + fileName); 

    // Zip always use/as separator 
    String zipName = fileName; 
    if (File.separatorChar != '/') 
    zipName = fileName.replace(File.separatorChar, '/'); 
    ZipEntry ze; 
    if (file.isFile()) { 
    ze = new ZipEntry(zipName); 
    ze.setTime(file.lastModified()); 
    zout.putNextEntry(ze); 
    FileInputStream fin = new FileInputStream(file); 
    byte[] buffer = new byte[4096]; 
    for (int n; (n = fin.read(buffer)) > 0;) 
    zout.write(buffer, 0, n); 
    fin.close(); 
    } else { 
    ze = new ZipEntry(zipName + '/'); 
    ze.setTime(file.lastModified()); 
    zout.putNextEntry(ze); 
    } 
    } 
    zout.close(); 
} 

public static List<String> listDirectory(File directory) 
    throws IOException { 

    Stack<String> stack = new Stack<String>(); 
    List<String> list = new ArrayList<String>(); 

    // If it's a file, just return itself 
    if (directory.isFile()) { 
    if (directory.canRead()) 
    list.add(directory.getName()); 
    return list; 
    } 

    // Traverse the directory in width-first manner, no-recursively 
    String root = directory.getParent(); 
    stack.push(directory.getName()); 
    while (!stack.empty()) { 
    String current = (String) stack.pop(); 
    File curDir = new File(root, current); 
    String[] fileList = curDir.list(); 
    if (fileList != null) { 
    for (String entry : fileList) { 
    File f = new File(curDir, entry); 
    if (f.isFile()) { 
     if (f.canRead()) { 
     list.add(current + File.separator + entry); 
     } else { 
     System.err.println("File " + f.getPath() 
     + " is unreadable"); 
     throw new IOException("Can't read file: " 
     + f.getPath()); 
     } 
    } else if (f.isDirectory()) { 
     list.add(current + File.separator + entry); 
     stack.push(current + File.separator + f.getName()); 
    } else { 
     throw new IOException("Unknown entry: " + f.getPath()); 
    } 
    } 
    } 
    } 
    return list; 
} 
} 
+0

ご協力いただきありがとうございます。私はあなたのコード例には感謝していますが、私はそれが私のコードでエラーを見つけるのを助ける方法がわかりません。関数への最初の呼び出しは関数内で引き渡される完全なディレクトリパスを使用します。私のSEPARATOR定数は、OSのデフォルトファイルセパレータを与えるSystem.getProperty( "file.separator")で初期化されます。区切り文字をハードコードすることは決してありません。なぜなら、あなたのコードは特定のOSにのみデプロイされることを前提としているからです。 – Eric

+4

ZIPでFile.separatorを使用しないでください。セパレータは、仕様に従って "/"でなければなりません。 Windowsの場合、ファイルを "D:¥dir¥subdir¥file"として開く必要がありますが、ZIPエントリは "dir/subdir/file"でなければなりません。 –

+1

私は参照してください。これを指摘していただきありがとうございます。 ZIPがとてもかわいかったことを知らなかった! :) – Eric

4

ちょうどjava.util.zip.ZipEntryのソースを参照してください。その名前が "/"文字で終わる場合、ZipEntryをディレクトリとして扱います。ディレクトリ名の末尾に "/"を付けてください。 また、ドライブ接頭辞を削除して相対パスにする必要があります。ちょうど空のディレクトリをビュンため、この例

チェック、限り、あなたはZIPファイル内の空&非空のディレクトリの両方を作成することができますよう

http://bethecoder.com/applications/tutorials/showTutorials.action?tutorialId=Java_ZipUtilities_ZipEmptyDirectory

は、ディレクトリ構造はそのままです。

幸運。

1

私はここで提案/リマインダーを追加したいと思います:

あなたが入力したものと同じである出力ディレクトリを定義する場合は、出力.zipファイルのファイル名と、各ファイルの名前を比較したいと思いますこれは、ファイル自体を圧縮しないで、望ましくない動作を引き起こすためです。これはどんな助けでもいいと思います。フォルダの内容およびWindowsのサブフォルダを圧縮する

1

を置き換える、ここ

out.putNextEntry(new ZipEntry(files[i]).replace(inFolder+"\\,"")); 
2

out.putNextEntry(new ZipEntry(files[i])); 

はまた、あなたが含めることができます別の例(再帰的)です/ジップから収納フォルダを除外する:

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; 

public class ZipUtil { 

    private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; 

    public static void main(String[] args) throws Exception { 
    zipFile("C:/tmp/demo", "C:/tmp/demo.zip", true); 
    } 

    public static void zipFile(String fileToZip, String zipFile, boolean excludeContainingFolder) 
    throws IOException {   
    ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFile));  

    File srcFile = new File(fileToZip); 
    if(excludeContainingFolder && srcFile.isDirectory()) { 
     for(String fileName : srcFile.list()) { 
     addToZip("", fileToZip + "/" + fileName, zipOut); 
     } 
    } else { 
     addToZip("", fileToZip, zipOut); 
    } 

    zipOut.flush(); 
    zipOut.close(); 

    System.out.println("Successfully created " + zipFile); 
    } 

    private static void addToZip(String path, String srcFile, ZipOutputStream zipOut) 
    throws IOException {   
    File file = new File(srcFile); 
    String filePath = "".equals(path) ? file.getName() : path + "/" + file.getName(); 
    if (file.isDirectory()) { 
     for (String fileName : file.list()) {    
     addToZip(filePath, srcFile + "/" + fileName, zipOut); 
     } 
    } else { 
     zipOut.putNextEntry(new ZipEntry(filePath)); 
     FileInputStream in = new FileInputStream(srcFile); 

     byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; 
     int len; 
     while ((len = in.read(buffer)) != -1) { 
     zipOut.write(buffer, 0, len); 
     } 

     in.close(); 
    } 
    } 
} 
2

バイト入力ストリーム、バッファサイズ、およびその他の低レベルの詳細を扱いたくない場合は、 JavaコードからAntのZipライブラリを使用できます(依存関係はhereです)。

public static void createZip(File zipFile, List<String> fileList) { 

    Project project = new Project(); 
    project.init(); 

    Zip zip = new Zip(); 
    zip.setDestFile(zipFile); 
    zip.setProject(project); 

    for(String relativePath : fileList) { 

     //noramalize the path (using commons-io, might want to null-check) 
     String normalizedPath = FilenameUtils.normalize(relativePath); 

     //create the file that will be used 
     File fileToZip = new File(normalizedPath); 
     if(fileToZip.isDirectory()) { 
      ZipFileSet fileSet = new ZipFileSet(); 
      fileSet.setDir(fileToZip); 
      fileSet.setPrefix(fileToZip.getPath()); 
      zip.addFileset(fileSet); 
     } else { 
      FileSet fileSet = new FileSet(); 
      fileSet.setDir(new File(".")); 
      fileSet.setIncludes(normalizedPath); 
      zip.addFileset(fileSet); 
     } 
    } 

    Target target = new Target(); 
    target.setName("ziptarget"); 
    target.addTask(zip); 
    project.addTarget(target); 
    project.executeTarget("ziptarget"); 
} 
0

このコードは、私のために作品を切り取ら:ここで私は&、ディレクトリのファイルのリストからなるジッパーを作り、今です。サードパーティのライブラリは必要ありません。

public static void zipDir(final Path dirToZip, final Path out) { 
    final Stack<String> stackOfDirs = new Stack<>(); 
    final Function<Stack<String>, String> createPath = stack -> stack.stream().collect(Collectors.joining("/")) + "/"; 
    try(final ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(out.toFile()))) { 
     Files.walkFileTree(dirToZip, new FileVisitor<Path>() { 

      @Override 
      public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) throws IOException { 
       stackOfDirs.push(dir.toFile().getName()); 
       final String path = createPath.apply(stackOfDirs); 
       final ZipEntry zipEntry = new ZipEntry(path); 
       zipOut.putNextEntry(zipEntry); 
       return FileVisitResult.CONTINUE; 
      } 

      @Override 
      public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { 
       final String path = String.format("%s%s", createPath.apply(stackOfDirs), file.toFile().getName()); 
       final ZipEntry zipEntry = new ZipEntry(path); 
       zipOut.putNextEntry(zipEntry); 
       Files.copy(file, zipOut); 
       zipOut.closeEntry(); 
       return FileVisitResult.CONTINUE; 
      } 

      @Override 
      public FileVisitResult visitFileFailed(final Path file, final IOException exc) throws IOException { 
       final StringWriter stringWriter = new StringWriter(); 
       try(final PrintWriter printWriter = new PrintWriter(stringWriter)) { 
        exc.printStackTrace(printWriter); 
        System.err.printf("Failed visiting %s because of:\n %s\n", 
          file.toFile().getAbsolutePath(), printWriter.toString()); 
       } 
       return FileVisitResult.CONTINUE; 
      } 

      @Override 
      public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException { 
       stackOfDirs.pop(); 
       return FileVisitResult.CONTINUE; 
      } 
     }); 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 
} 
関連する問題