1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

FileVisitor でツリーっぽい表示

Posted at

きっかけ

Javaを使ってディレクトリの階層構造をツリー形式で表示する
by @ukiuki

を見て、FileVisitor でなんとかならんかなーと思ったのがきっかけ。

結論から言うと、なんとかなったけどあまり綺麗にならない…
綺麗にするのはあきらめたけどここで供養

(File/Directory に visitor パターンはしっくりこないことが多いのでそもそもトライしてません)

コード

jdk1.8.0 & pleiades neon で開発。

自前 walk 処理版

出力例
C:\Users\heignamerican\testroom\filewalk
├─dir  0\
│  ├─123.txt
│  ├─456.txt
│  └─dir01\
│      └─dir02\
├─dir1\
│  ├─dir1-1\
│  │  └─file1-1-1.txt
│  └─file1.txt
├─dir2\
│  ├─file  2.txt
│  ├─file1.txt
│  └─ほげほげ.txt
├─dir3\
├─dir4\
│  └─dir1-1 - ショートカット.lnk
└─file0.txt

C:\Users\heignamerican\testroom\filewalk\file0.txt

C:\Users\heignamerican\testroom\filewalk\dir4\dir1-1 - ショートカット.lnk

Exception in thread "main" java.lang.NullPointerException
	at java.util.Objects.requireNonNull(Unknown Source)
	at heignamerican.files.Walking$MyFileWalker.start(Walking.java:42)
	at heignamerican.files.Walking.main(Walking.java:17)

Walking.java
package heignamerican.files;

import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public class Walking {
    public static void main(final String[] args) throws IOException {
        new MyFileWalker(System.out).start(Paths.get("C:/Users/heignamerican/testroom/filewalk"));
        new MyFileWalker(System.out).start(Paths.get("C:/Users/heignamerican/testroom/filewalk/file0.txt"));
        new MyFileWalker(System.out).start(Paths.get("C:/Users/heignamerican/testroom/filewalk/dir4/dir1-1 - ショートカット.lnk"));
        new MyFileWalker(System.out).start(null);
    }

    public static class MyFileWalker {
        public MyFileWalker(final PrintStream out) {
            this.out = out;
        }

        private final PrintStream out;
        private StringBuilder indenter;

        /**
         * <p>
         * 指定された root 以下を tree っぽく表示する。
         *
         * <p>
         * コンストラクタで指定された {@code PrintStream} に出力する。<br>
         * SymbolicLink はたどらない(通常のファイル扱い)。<br>
         * スレッドセーフではない。<br>
         *
         * @param root
         *            null 禁止
         * @throws IOException
         */
        public void start(final Path root) throws IOException {
            Objects.requireNonNull(root);
            if (!Files.exists(root)) {
                out.printf("'%s' not found.%n", root);
                return;
            }
            out.println(root.toString());
            if (Files.isDirectory(root)) {
                indenter = new StringBuilder();
                walk(root);
            }
        }

        private static final String FILE_SEPARATOR = System.getProperty("file.separator");
        private static final String PREFIX_FOR_MIDDLE = "├─";
        private static final String PREFIX_FOR_LAST = "└─";
        private static final String INDENT_FOR_MIDDLE = "│  ";
        private static final String INDENT_FOR_LAST = "    ";

        private void walk(final Path path) throws IOException {
            final List<Path> children = Files.list(path).collect(Collectors.toList());

            final int lastIndex = children.size() - 1;
            for (int index = 0; index <= lastIndex; index++) {
                final Path child = children.get(index);
                final boolean isDirectory = Files.isDirectory(child);
                final boolean isMiddle = index != lastIndex;

                out.println(indenter.toString() + (isMiddle ? PREFIX_FOR_MIDDLE : PREFIX_FOR_LAST) + child.getFileName() + (isDirectory ? FILE_SEPARATOR : ""));

                if (isDirectory) {
                    final int oldLength = indenter.length();
                    indenter.append(isMiddle ? INDENT_FOR_MIDDLE : INDENT_FOR_LAST);
                    walk(child);
                    // if (notLast) { out.println(indenter); } // cmd の "tree /F" っぽくするなら入れたい
                    indenter.setLength(oldLength);
                }
            }
        }
    }
}

SimpleFileVisitor 版

出力例
C:\Users\heignamerican\testroom\filewalk
├─dir  0\
│ ├─123.txt
│ ├─456.txt
│ └─dir01\
│   └─dir02\
├─dir1\
│ ├─dir1-1\
│ │ └─file1-1-1.txt
│ └─file1.txt
├─dir2\
│ ├─file  2.txt
│ ├─file1.txt
│ └─ほげほげ.txt
├─dir3\
├─dir4\
│ └─dir1-1 - ショートカット.lnk
└─file0.txt

C:\Users\heignamerican\testroom\filewalk\file0.txt

C:\Users\heignamerican\testroom\filewalk\dir4\dir1-1 - ショートカット.lnk

Exception in thread "main" java.lang.NullPointerException
	at java.util.Objects.requireNonNull(Unknown Source)
	at heignamerican.files.Walking2$MyFileVisitor.start(Walking2.java:43)
	at heignamerican.files.Walking2.main(Walking2.java:19)
Walking2.java
package heignamerican.files;

import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Objects;
import java.util.Stack;

public class Walking2 {
	public static void main(final String[] args) throws IOException {
		new MyFileVisitor(System.out).start(Paths.get("C:/Users/heignamerican/testroom/filewalk"));
		new MyFileVisitor(System.out).start(Paths.get("C:/Users/heignamerican/testroom/filewalk/file0.txt"));
		new MyFileVisitor(System.out).start(Paths.get("C:/Users/heignamerican/testroom/filewalk/dir4/dir1-1 - ショートカット.lnk"));
		new MyFileVisitor(System.out).start(null);
	}

	public static class MyFileVisitor extends SimpleFileVisitor<Path> {
		public MyFileVisitor(final PrintStream out) {
			this.out = out;
		}

		private final PrintStream out;

		/**
		 * <p>
		 * 指定された root 以下を tree っぽく表示する。
		 *
		 * <p>
		 * コンストラクタで指定された {@code PrintStream} に出力する。<br>
		 * SymbolicLink はたどらない(通常のファイル扱い)。<br>
		 * スレッドセーフではない。<br>
		 *
		 * @param root
		 *            null 禁止
		 * @throws IOException
		 */
		public void start(final Path path) throws IOException {
			Objects.requireNonNull(path);
			if (!Files.exists(path)) {
				out.printf("'%s' not found.%n", path);
				return;
			}

			root = path;
			childrenCounts = new Stack<>();
			indenter = new StringBuilder();

			Files.walkFileTree(root, this);
		}

		private static final String FILE_SEPARATOR = System.getProperty("file.separator");
		private Path root;
		private Stack<Long> childrenCounts;
		private StringBuilder indenter;

		@Override
		public FileVisitResult visitFile(final Path path, final BasicFileAttributes attributes) throws IOException {
			if (path == root) {
				out.println(path);
			} else {
				out.println(indenter.toString() + getHereIndent() + path.getFileName());
				countdownChildren();
			}
			return FileVisitResult.CONTINUE;
		}

		@Override
		public FileVisitResult preVisitDirectory(final Path path, final BasicFileAttributes attributes) throws IOException {
			if (path == root) {
				out.println(path);
				listupChildrenCount(path);
			} else {
				out.println(indenter.toString() + getHereIndent() + path.getFileName() + FILE_SEPARATOR);
				increseIndent();
				listupChildrenCount(path);
			}
			return FileVisitResult.CONTINUE;
		}

		@Override
		public FileVisitResult postVisitDirectory(final Path path, final IOException attributes) throws IOException {
			if (path != root) {
				decreseIndent();
				countdownChildren();
			}
			return FileVisitResult.CONTINUE;
		}

		private void listupChildrenCount(final Path path) throws IOException {
			final long newer = Files.list(path).count();
			if (newer > 0) {
				childrenCounts.push(newer);
			}
		}

		private void countdownChildren() {
			final long rest = childrenCounts.pop() - 1;
			if (rest > 0) {
				childrenCounts.push(rest);
			}
		}

		private String getHereIndent() {
			return (childrenCounts.peek() > 1L) ? "├─" : "└─"; // インデントの文字数を 2 で固定する
		}

		private void increseIndent() {
			indenter.append((childrenCounts.peek() > 1L) ? "│ " : "  "); // インデントの文字数を 2 で固定する
		}

		private void decreseIndent() {
			indenter.setLength(indenter.length() - 2); // インデントの文字数を 2 で固定する
		}
	}
}

反省

  • ちょっと最後の方力尽きてます…
  • 「最後じゃない」っていう英語表現が分からず、最後まで分からない変数名
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?