【问题标题】:Unzip into TreeMap in Java在 Java 中解压成 TreeMap
【发布时间】:2011-03-01 18:53:15
【问题描述】:

我的问题相对简单。有人知道一个免费库 (LGPL),它能够将压缩文件结构解压缩为 Java 的 TreeMap(或类似的可迭代结构)吗?

重点是,我可以自己做,但我不想重新发明已经很好的骑行轮 :)

提前致谢!


所以我的事情是,我有一个 zip 文件,其中包含多个文件和目录,其中可能包含更多文件。我正在寻找的是一种方便的方法,如何将此树结构提取到对象图中,无论它是否是 TreeMap。例如:HashMap : {'root' => 'HashMap : {'file1.png' => byte[] content}}

【问题讨论】:

    标签: java tree zip directory-structure


    【解决方案1】:

    所以我的事情是,我有一个 zip 文件,其中包含多个文件和目录,其中可能包含更多文件。 我正在寻找的是一种方便的方法,如何将此树结构提取到对象图中,无论它是否是 TreeMap。 例如:一个 HashMap : {'root', 'HashMap : {'file1.png' => byte[] content}}

    As I answered some time ago 在另一个问题上,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 的终端和一个用于 System.out 的 Unicode 编码。)

    【讨论】:

    • 好的,现在我看到了实际问题。谢谢!只是认为可能有某种通用库可以提供带有例如文件包装器的 HashMap-tree。关键是,我需要以正确的顺序在虚拟文件系统中创建它们的任何子目录(否则我会使用 File#mkdirs() )。
    • 我觉得有点无聊,给你上了一堂课……我不确定“正确的顺序”,但你现在可以随意遍历树了。
    • 现在我惊呆了。非常感谢!欣赏它!从来没有想过有人会想出一个实际的实现。我会调查的!
    • 我真的很困惑,这个基本功能没有库功能。如果我正在处理文件,我通常对文件树结构感兴趣。认为必须为此重新发明轮子......
    【解决方案2】:

    java.util.zip.ZipFile 怎么样?这听起来像是一个非常微不足道的包装器,应该可以满足您的需求。

    【讨论】:

    • 嗯,ZipFile 会给我一组平面的 ZipEntry 对象,但我想要一个映射到 zip 存档本身的目录结构的结构。
    • 哦,我明白了。你可能想澄清这个问题。另外,作为参考,Java TreeMap 只是一个 key=&gt;value 映射;它不公开任何树状接口。
    • 好的,很抱歉描述不当。所以我的事情是,我有一个 zip 文件,其中包含多个文件和目录,其中可能包含更多文件。我正在寻找的是一种方便的方法,如何将此树结构提取到对象图中,无论它是否是 TreeMap。例如:一个 HashMap : {'root', 'HashMap : {'file1.png' => byte[] content}}
    【解决方案3】:

    您可以使用 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
        }
    }
    

    【讨论】:

    • 当然,但整个事情听起来像是一个相当常用的任务。所以我想知道这种情况下是否有任何 util 库(如 apache-commons)。
    • “递归”点是复杂性开始的地方 - ZipEntry 不允许访问他的子条目(如果允许,您已经在那里拥有树 API)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-18
    • 1970-01-01
    相关资源
    最近更新 更多