在Java中将文件解压到TreeMap中

4
我的问题相对简单。有没有人知道一个能够将压缩文件结构解压成TreeMap(或类似的可迭代结构)的免费库(LGPL)供Java使用?
问题是,我可以自己做到这一点,但我不想重复造好的轮子:)
提前感谢!
我的情况是,我有一个包含多个文件和可能包含更多文件的目录的zip文件。我要找的是一种方便的方法,如何将这个树结构提取到对象图中,无论它是TreeMap还是其他什么类型。例如:一个HashMap:{'root' => 'HashMap:{'file1.png' => byte [] content}}
3个回答

2
我的问题是,我有一个zip文件,其中包含多个文件和目录,这些目录可能包含更多的文件。 我想要的是一种方便的方法,将这个树形结构提取到对象图中,无论它是TreeMap还是其他什么。 例如:一个HashMap:{'root','HashMap:{'file1.png' => byte [] content}}。
正如我之前在另一个问题上回答的(链接)那样,在Java API中没有单一的“树”数据结构(树接口),因为每个使用都需要其他功能。例如,您提出的HashMap-tree不能以类型安全的方式实现-您需要在某个地方使用包装器对象。
我不知道是否已经有了一个类似于zip文件的树状视图,以回答您的问题,但只要定义所需的树形接口,就不难创建它。
所以,这里有一个示例类可以做到你想要的(根据我理解的)。
import java.io.*;
import java.util.*;
import java.util.zip.*;

/**
 * A immutable wrapper around {@link ZipEntry} allowing
 * simple access of the childs of directory entries.
 */
public class ZipNode {

    private ZipNode parent;
    private Map<String,ZipNode> children;
    private boolean directory;

    /**
     * the corresponding Zip entry. If null, this is the root entry.
     */
    private ZipEntry entry;

    /** the ZipFile from where the nodes came. */
    private ZipFile file;


    private ZipNode(ZipFile f, ZipEntry entry) {
        this.file = f;
        this.entry = entry;
        if(entry == null || entry.isDirectory()) {
            directory = true;
            children = new LinkedHashMap<String, ZipNode>();
        }
        else {
            directory = false;
            children = Collections.emptyMap();
        }
    }

    /**
     * returns the last component of the name of
     * the entry, i.e. the file name. If this is a directory node,
     * the name ends with '/'. If this is the root node, the name
     * is simply "/".
     */
    public String getName() {
        if(entry == null)
            return "/";
        String longName = entry.getName();
        return longName.substring(longName.lastIndexOf('/',
                                                       longName.length()-2)+1);
    }

    /**
     * gets the corresponding ZipEntry to this node.
     * @return {@code null} if this is the root node (which has no
     *    corresponding entry), else the corresponding ZipEntry.
     */
    public ZipEntry getEntry() {
        return entry;
    }

    /**
     * Gets the ZipFile, from where this ZipNode came.
     */
    public ZipFile getZipFile() {
        return file;
    }

    /**
     * returns true if this node is a directory node.
     */
    public boolean isDirectory() {
        return directory;
    }

    /**
     * returns this node's parent node (null, if this is the root node).
     */
    public ZipNode getParent() {
        return parent;
    }

    /**
     * returns an unmodifiable map of the children of this node,
     * mapping their relative names to the ZipNode objects.
     * (Names of subdirectories end with '/'.)
     * The map is empty if this node is not a directory node.
     */
    public Map<String,ZipNode> getChildren() {
        return Collections.unmodifiableMap(children);
    }

    /**
     * opens an InputStream on this ZipNode. This only works when
     * this is not a directory node, and only before the corresponding
     * ZipFile is closed.
     */
    public InputStream openStream()
        throws IOException
    {
        return file.getInputStream(entry);
    }

    /**
     * a string representation of this ZipNode.
     */
    public String toString() {
        return "ZipNode [" + entry.getName() + "] in [" + file.getName() + "]";
    }



    /**
     * creates a ZipNode tree from a ZipFile
     * and returns the root node.
     *
     * The nodes' {@link #openStream()} methods are only usable until the
     * ZipFile is closed, but the structure information remains valid.
     */
    public static ZipNode fromZipFile(ZipFile zf) {
        return new ZipFileReader(zf).process();
    }


    /**
     * Helper class for {@link ZipNode#fromZipFile}.
     * It helps creating a tree of ZipNodes from a ZipFile.
     */
    private static class ZipFileReader {
        /**
         * The file to be read.
         */
        private ZipFile file;

        /**
         * The nodes collected so far.
         */
        private Map<String, ZipNode> collected;

        /**
         * our root node.
         */
        private ZipNode root;

        /**
         * creates a new ZipFileReader from a ZipFile.
         */
        ZipFileReader(ZipFile f) {
            this.file = f;
            this.collected = new HashMap<String, ZipNode>();
            collected.put("", root);
            root = new ZipNode(f, null);
        }

        /**
         * reads all entries, creates the corresponding Nodes and
         * returns the root node.
         */
        ZipNode process() {
            for(Enumeration<? extends ZipEntry> entries = file.entries();
                entries.hasMoreElements(); ) {
                this.addEntry(entries.nextElement());
            }
            return root;
        }

        /**
         * adds an entry to our tree.
         *
         * This may create a new ZipNode and then connects
         * it to its parent node.
         * @returns the ZipNode corresponding to the entry.
         */
        private ZipNode addEntry(ZipEntry entry) {
            String name = entry.getName();
            ZipNode node = collected.get(name);
            if(node != null) {
                // already in the map
                return node;
            }
            node = new ZipNode(file, entry);
            collected.put(name, node);
            this.findParent(node);
            return node;
        }

        /**
         * makes sure that the parent of a
         * node is in the collected-list as well, and this node is
         * registered as a child of it.
         * If necessary, the parent node is first created
         * and added to the tree.
         */
        private void findParent(ZipNode node) {
            String nodeName = node.entry.getName();
            int slashIndex = nodeName.lastIndexOf('/', nodeName.length()-2);
            if(slashIndex < 0) {
                // top-level-node
                connectParent(root, node, nodeName);
                return;
            }
            String parentName = nodeName.substring(0, slashIndex+1);
            ZipNode parent = addEntry(file.getEntry(parentName));
            connectParent(parent, node, nodeName.substring(slashIndex+1));
        }

        /**
         * connects a parent node with its child node.
         */
        private void connectParent(ZipNode parent, ZipNode child,
                                   String childName) {
            child.parent = parent;
            parent.children.put(childName, child);
        }


    }  // class ZipFileReader

    /**
     * test method. Give name of zip file as command line argument.
     */
    public static void main(String[] params)
        throws IOException
    {
        if(params.length < 1) {
            System.err.println("Invocation:  java ZipNode zipFile.zip");
            return;
        }
        ZipFile file = new ZipFile(params[0]);
        ZipNode root = ZipNode.fromZipFile(file);
        file.close();
        root.printTree("", " ", "");
    }

    /**
     * prints a simple tree view of this ZipNode.
     */
    private void printTree(String prefix,
                           String self,
                           String sub) {
        System.out.println(prefix + self + this.getName());
        String subPrefix = prefix + sub;
        // the prefix strings for the next level.
        String nextSelf = " ├─ ";
        String nextSub =  " │ ";
        Iterator<ZipNode> iterator =
            this.getChildren().values().iterator();
        while(iterator.hasNext()) {
            ZipNode child = iterator.next();
            if(!iterator.hasNext() ) {
                // last item, without the "|"
                nextSelf = " ╰─ ";
                nextSub =  "   ";
            }
            child.printTree(subPrefix, nextSelf, nextSub);
        }
    }
}

它有一个主方法用于测试,其中一个我的jar文件的输出如下:
 /
 ├─ META-INF/
 │  ╰─ MANIFEST.MF
 ╰─ de/
    ╰─ fencing_game/
       ├─ start/
       │  ├─ Runner.class
       │  ├─ ServerMain$1.class
       │  ├─ ServerMain.class
       │  ╰─ package-info.class
       ├─ log/
       │  ├─ Log$1.class
       │  ├─ Log.class
       │  ├─ LogImplClient.class
       │  ├─ Loggable.class
       │  ╰─ package-info.class
       ╰─ tools/
          ╰─ load/
             ├─ ServiceTools$1.class
             ├─ ServiceTools$2.class
             ├─ ServiceTools$3.class
             ├─ ServiceTools.class
             ╰─ TwoParentClassLoader.class

(不过,您需要一个支持Unicode的终端和一个使用Unicode编码的System.out。)

好的,现在我看到了实际问题。谢谢!只是想到可能有一些通用库可以提供带有例如文件包装器的HashMap-tree。问题是,我需要任何子目录按正确顺序创建它们在虚拟文件系统中(否则我会使用File#mkdirs())。 - Markus
我感到有点无聊,于是给你写了一个类...我不确定“正确的顺序”,但现在你可以按照自己的意愿遍历树了。 - Paŭlo Ebermann
1
现在我很惊讶。非常感谢!感激不尽!从未想过会有人提出实际的实现方法。我会研究一下! - Markus
我真的很困惑,为什么这个基本功能没有库功能。如果我正在处理文件,通常会关注文件树结构。想到必须重新发明轮子来实现这一点... - r0estir0bbe

0
您可以使用java.util.ZipFile来使用ZipFile,获取其内容并将当前内容强制转换为java.io.File,检查它是否为Dir,如果是,则在其中迭代,您可以随时将这些存储在TreeMap中。
ZipFile myzip = new ZipFile (new File("pathToMyZipFile.zip"));
Enumeration zipEnumerator = myzip.entries();
while(zipEnumerator.hasMoreElements())
{
    ZipENtry ze= zipEnumerator.nextElement();
    if(ze.isDirectory())
    {
       // recurse
    }
    else {
       // add it to treeMap
    }
}

1
当然可以,但整个任务听起来像是一个经常使用的任务。因此,我想知道是否有任何类似于Apache Commons的实用程序库可用于这种情况。 - Markus
“递归”点是复杂性开始的地方 - ZipEntry 不提供对其子条目的访问权限(如果提供了,您已经在那里拥有您的树 API)。 - Paŭlo Ebermann

0

java.util.zip.ZipFile 怎么样?它听起来像是一个相当简单的包装器,应该可以满足你的需求。


ZipFile会给我一个扁平的ZipEntry对象集合,但是我想要一个与zip归档中的目录结构相对应的结构。 - Markus
1
哦,我明白了。那你可能需要澄清一下问题。另外,作为参考,Java的TreeMap只是一个key=>value映射;它不会暴露任何类似树形的接口。 - NPE
好的,抱歉描述不太准确。我的问题是,我有一个包含多个文件和目录(可能包含更多文件)的zip文件。我正在寻找一种方便的方法将这个树形结构提取到对象图中,无论它是TreeMap还是其他类型。例如:HashMap:{'root','HashMap:{'file1.png'=> byte [] content}}。 - Markus

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