Làm cách nào để liệt kê đệ quy tất cả các tệp trong một thư mục trong Java? Liệu khung cung cấp bất kỳ tiện ích?
Tôi đã thấy rất nhiều triển khai hacky. Nhưng không ai trong khuôn khổ hoặc nio
Làm cách nào để liệt kê đệ quy tất cả các tệp trong một thư mục trong Java? Liệu khung cung cấp bất kỳ tiện ích?
Tôi đã thấy rất nhiều triển khai hacky. Nhưng không ai trong khuôn khổ hoặc nio
Câu trả lời:
Java 8 cung cấp một luồng tốt để xử lý tất cả các tệp trong cây.
Files.walk(Paths.get(path))
.filter(Files::isRegularFile)
.forEach(System.out::println);
Điều này cung cấp một cách tự nhiên để duyệt qua các tập tin. Vì đó là luồng nên bạn có thể thực hiện tất cả các thao tác luồng tốt trên kết quả như giới hạn, nhóm, ánh xạ, thoát sớm, v.v.
CẬP NHẬT : Tôi có thể chỉ ra rằng đó cũng là Files.find có BiPredicate có thể hiệu quả hơn nếu bạn cần kiểm tra các thuộc tính tệp.
Files.find(Paths.get(path),
Integer.MAX_VALUE,
(filePath, fileAttr) -> fileAttr.isRegularFile())
.forEach(System.out::println);
Lưu ý rằng mặc dù JavaDoc cho rằng phương thức này có thể hiệu quả hơn Files.walk giống hệt nhau, nhưng sự khác biệt về hiệu suất có thể được quan sát nếu bạn cũng đang truy xuất các thuộc tính tệp trong bộ lọc của mình. Cuối cùng, nếu bạn cần lọc các thuộc tính, hãy sử dụng Files.find , nếu không thì sử dụng Files.walk , chủ yếu là vì có quá tải và thuận tiện hơn.
KIỂM TRA : Theo yêu cầu, tôi đã cung cấp một so sánh hiệu suất của nhiều câu trả lời. Kiểm tra dự án Github có chứa kết quả và trường hợp thử nghiệm .
Files.walk
với một luồng song song là tốt nhất, theo sát với Files.walkFileTree
nó chỉ chậm hơn một chút. Câu trả lời được chấp nhận bằng commons-io là chậm nhất bởi các bài kiểm tra của tôi chậm hơn 4 lần.
Exception in thread "main" java.io.UncheckedIOException: java.nio.file.AccessDeniedException
. Làm thế nào tôi có thể sửa nó
FileUtils có iterateFiles
và listFiles
phương thức. Hãy cho họ một thử. (từ commons-io )
Chỉnh sửa: Bạn có thể kiểm tra ở đây để biết điểm chuẩn của các phương pháp khác nhau. Có vẻ như cách tiếp cận commons-io rất chậm, vì vậy hãy chọn một số cách nhanh hơn từ đây (nếu có vấn đề)
FileUtils.listFiles(dir, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE)
, đâu dir
là một đối tượng Tệp trỏ đến thư mục cơ sở.
listFilesAndDirs()
, vì listFiles()
không trả lại các thư mục trống.
FileUtils.listFiles(dir, true, true)
. bằng cách sử dụng FileUtils.listFiles(dir, null, true)
sẽ ném Ngoại lệ, trong khi FileUtils.listFiles(dir, true, null)
sẽ liệt kê tất cả các tệp mà không cần xem xét các thư mục con.
// Sẵn sàng để chạy
import java.io.File;
public class Filewalker {
public void walk( String path ) {
File root = new File( path );
File[] list = root.listFiles();
if (list == null) return;
for ( File f : list ) {
if ( f.isDirectory() ) {
walk( f.getAbsolutePath() );
System.out.println( "Dir:" + f.getAbsoluteFile() );
}
else {
System.out.println( "File:" + f.getAbsoluteFile() );
}
}
}
public static void main(String[] args) {
Filewalker fw = new Filewalker();
fw.walk("c:\\" );
}
}
-> .
.
"/"
, "./"
hoặc "../"
cho thư mục gốc, thư mục làm việc hiện tại và thư mục mẹ, tương ứng
Java 7 sẽ có có Files.walkFileTree :
Nếu bạn cung cấp một điểm bắt đầu và một khách truy cập tệp, nó sẽ gọi các phương thức khác nhau trên khách truy cập tệp khi nó đi qua tệp trong cây tệp. Chúng tôi hy vọng mọi người sẽ sử dụng điều này nếu họ đang phát triển một bản sao đệ quy, di chuyển đệ quy, xóa đệ quy hoặc thao tác đệ quy đặt quyền hoặc thực hiện một thao tác khác trên mỗi tệp.
Hiện tại có toàn bộ hướng dẫn của Oracle về câu hỏi này .
Không cần thư viện bên ngoài.
Trả về Bộ sưu tập để bạn có thể làm bất cứ điều gì bạn muốn với nó sau cuộc gọi.
public static Collection<File> listFileTree(File dir) {
Set<File> fileTree = new HashSet<File>();
if(dir==null||dir.listFiles()==null){
return fileTree;
}
for (File entry : dir.listFiles()) {
if (entry.isFile()) fileTree.add(entry);
else fileTree.addAll(listFileTree(entry));
}
return fileTree;
}
Tôi sẽ đi với một cái gì đó như:
public void list(File file) {
System.out.println(file.getName());
File[] children = file.listFiles();
for (File child : children) {
list(child);
}
}
System.out.println chỉ ở đó để chỉ ra để làm một cái gì đó với tệp. không cần phân biệt giữa các tệp và thư mục, vì một tệp bình thường sẽ không có con.
listFiles()
: Viking Nếu tên đường dẫn trừu tượng này không biểu thị một thư mục, thì phương thức này trả về null
.
Tôi thích sử dụng một hàng đợi trên đệ quy cho loại chuyển đổi đơn giản này:
List<File> allFiles = new ArrayList<File>();
Queue<File> dirs = new LinkedList<File>();
dirs.add(new File("/start/dir/"));
while (!dirs.isEmpty()) {
for (File f : dirs.poll().listFiles()) {
if (f.isDirectory()) {
dirs.add(f);
} else if (f.isFile()) {
allFiles.add(f);
}
}
}
chỉ cần viết nó bằng cách sử dụng đệ quy đơn giản:
public List<File> addFiles(List<File> files, File dir)
{
if (files == null)
files = new LinkedList<File>();
if (!dir.isDirectory())
{
files.add(dir);
return files;
}
for (File file : dir.listFiles())
addFiles(files, file);
return files;
}
Tôi nghĩ rằng điều này nên làm công việc:
File dir = new File(dirname);
String[] files = dir.list();
Bằng cách này bạn có tập tin và thư mục. Bây giờ sử dụng đệ quy và làm tương tự cho các thư mục ( File
lớp có isDirectory()
phương thức).
Với Java 7, bạn có thể sử dụng lớp sau:
import java.io.IOException;
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;
public class MyFileIterator extends SimpleFileVisitor<Path>
{
public MyFileIterator(String path) throws Exception
{
Files.walkFileTree(Paths.get(path), this);
}
@Override
public FileVisitResult visitFile(Path file,
BasicFileAttributes attributes) throws IOException
{
System.out.println("File: " + file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir,
BasicFileAttributes attributes) throws IOException
{
System.out.println("Dir: " + dir);
return FileVisitResult.CONTINUE;
}
}
Trong Java 8, bây giờ chúng ta có thể sử dụng tiện ích Tệp để đi bộ cây tệp. Rất đơn giản.
Files.walk(root.toPath())
.filter(path -> !Files.isDirectory(path))
.forEach(path -> System.out.println(path));
Mã này đã sẵn sàng để chạy
public static void main(String... args) {
File[] files = new File("D:/").listFiles();
if (files != null)
getFiles(files);
}
public static void getFiles(File[] files) {
for (File file : files) {
if (file.isDirectory()) {
getFiles(file.listFiles());
} else {
System.out.println("File: " + file);
}
}
}
Ngoài phương thức đệ quy, người ta cũng có thể sử dụng cách tiếp cận dựa trên Khách truy cập.
Dưới đây mã được sử dụng phương pháp tiếp cận dựa trên khách truy cập cho traversal. Dự kiến rằng đầu vào của chương trình là thư mục gốc để duyệt qua.
public interface Visitor {
void visit(DirElement d);
void visit(FileElement f);
}
public abstract class Element {
protected File rootPath;
abstract void accept(Visitor v);
@Override
public String toString() {
return rootPath.getAbsolutePath();
}
}
public class FileElement extends Element {
FileElement(final String path) {
rootPath = new File(path);
}
@Override
void accept(final Visitor v) {
v.visit(this);
}
}
public class DirElement extends Element implements Iterable<Element> {
private final List<Element> elemList;
DirElement(final String path) {
elemList = new ArrayList<Element>();
rootPath = new File(path);
for (File f : rootPath.listFiles()) {
if (f.isDirectory()) {
elemList.add(new DirElement(f.getAbsolutePath()));
} else if (f.isFile()) {
elemList.add(new FileElement(f.getAbsolutePath()));
}
}
}
@Override
void accept(final Visitor v) {
v.visit(this);
}
public Iterator<Element> iterator() {
return elemList.iterator();
}
}
public class ElementWalker {
private final String rootDir;
ElementWalker(final String dir) {
rootDir = dir;
}
private void traverse() {
Element d = new DirElement(rootDir);
d.accept(new Walker());
}
public static void main(final String[] args) {
ElementWalker t = new ElementWalker("C:\\temp");
t.traverse();
}
private class Walker implements Visitor {
public void visit(final DirElement d) {
System.out.println(d);
for(Element e:d) {
e.accept(this);
}
}
public void visit(final FileElement f) {
System.out.println(f);
}
}
}
Bạn có thể sử dụng mã dưới đây để nhận danh sách các tệp của thư mục hoặc thư mục cụ thể theo cách đệ quy.
public static void main(String args[]) {
recusiveList("D:");
}
public static void recursiveList(String path) {
File f = new File(path);
File[] fl = f.listFiles();
for (int i = 0; i < fl.length; i++) {
if (fl[i].isDirectory() && !fl[i].isHidden()) {
System.out.println(fl[i].getAbsolutePath());
recusiveList(fl[i].getAbsolutePath());
} else {
System.out.println(fl[i].getName());
}
}
}
Các câu trả lời được chấp nhận là rất tốt, tuy nhiên nó bị phá vỡ khi bạn muốn làm IO bên trong lambda.
Dưới đây là những gì bạn có thể làm nếu hành động của bạn tuyên bố IOExceptions.
Bạn có thể coi luồng được lọc là một Iterable
, và sau đó thực hiện hành động của mình trong một vòng lặp cho mỗi vòng lặp thông thường. Bằng cách này, bạn không phải xử lý ngoại lệ trong lambda.
try (Stream<Path> pathStream = Files.walk(Paths.get(path))
.filter(Files::isRegularFile)) {
for (Path file : (Iterable<Path>) pathStream::iterator) {
// something that throws IOException
Files.copy(file, System.out);
}
}
Tìm thấy mánh khóe đó tại đây: https://stackoverflow.com/a/32668807/1207791
BFS không đệ quy với một danh sách (ví dụ cụ thể là tìm kiếm tệp * .eml):
final FileFilter filter = new FileFilter() {
@Override
public boolean accept(File file) {
return file.isDirectory() || file.getName().endsWith(".eml");
}
};
// BFS recursive search
List<File> queue = new LinkedList<File>();
queue.addAll(Arrays.asList(dir.listFiles(filter)));
for (ListIterator<File> itr = queue.listIterator(); itr.hasNext();) {
File file = itr.next();
if (file.isDirectory()) {
itr.remove();
for (File f: file.listFiles(filter)) itr.add(f);
}
}
Phiên bản của tôi (tất nhiên tôi có thể đã sử dụng bản dựng sẵn trong Java 8 ;-)):
public static List<File> findFilesIn(File rootDir, Predicate<File> predicate) {
ArrayList<File> collected = new ArrayList<>();
walk(rootDir, predicate, collected);
return collected;
}
private static void walk(File dir, Predicate<File> filterFunction, List<File> collected) {
Stream.of(listOnlyWhenDirectory(dir))
.forEach(file -> walk(file, filterFunction, addAndReturn(collected, file, filterFunction)));
}
private static File[] listOnlyWhenDirectory(File dir) {
return dir.isDirectory() ? dir.listFiles() : new File[]{};
}
private static List<File> addAndReturn(List<File> files, File toAdd, Predicate<File> filterFunction) {
if (filterFunction.test(toAdd)) {
files.add(toAdd);
}
return files;
}
Đây là một giải pháp đơn giản nhưng hoàn hảo làm việc bằng cách sử dụng recursion
:
public static List<Path> listFiles(String rootDirectory)
{
List<Path> files = new ArrayList<>();
listFiles(rootDirectory, files);
return files;
}
private static void listFiles(String path, List<Path> collectedFiles)
{
File root = new File(path);
File[] files = root.listFiles();
if (files == null)
{
return;
}
for (File file : files)
{
if (file.isDirectory())
{
listFiles(file.getAbsolutePath(), collectedFiles);
} else
{
collectedFiles.add(file.toPath());
}
}
}
private void fillFilesRecursively(File file, List<File> resultFiles) {
if (file.isFile()) {
resultFiles.add(file);
} else {
for (File child : file.listFiles()) {
fillFilesRecursively(child, resultFiles);
}
}
}
Tôi đã đưa ra điều này để in tất cả các tệp / tên tệp đệ quy.
private static void printAllFiles(String filePath,File folder) {
if(filePath==null) {
return;
}
File[] files = folder.listFiles();
for(File element : files) {
if(element.isDirectory()) {
printAllFiles(filePath,element);
} else {
System.out.println(" FileName "+ element.getName());
}
}
}
Ví dụ đầu ra các tệp * .csv trong thư mục tìm kiếm đệ quy thư mục con bằng Files.find () từ java.nio:
String path = "C:/Daten/ibiss/ferret/";
logger.debug("Path:" + path);
try (Stream<Path> fileList = Files.find(Paths.get(path), Integer.MAX_VALUE,
(filePath, fileAttr) -> fileAttr.isRegularFile() && filePath.toString().endsWith("csv"))) {
List<String> someThingNew = fileList.sorted().map(String::valueOf).collect(Collectors.toList());
for (String t : someThingNew) {
t.toString();
logger.debug("Filename:" + t);
}
}
Đăng ví dụ này, vì tôi gặp khó khăn trong việc hiểu cách vượt qua tham số tên tệp trong ví dụ số 1 do Bryan đưa ra, sử dụng foreach trên Stream-result -
Hi vọng điêu nay co ich.
Kotlin có FileTreeWalk
mục đích này. Ví dụ:
dataDir.walkTopDown().filter { !it.isDirectory }.joinToString("\n") {
"${it.toRelativeString(dataDir)}: ${it.length()}"
}
Sẽ tạo ra một danh sách văn bản của tất cả các tệp không phải thư mục dưới một gốc nhất định, một tệp trên mỗi dòng có đường dẫn liên quan đến gốc và độ dài.
Dựa trên câu trả lời stacker. Đây là một giải pháp hoạt động trong JSP mà không cần bất kỳ thư viện bên ngoài nào để bạn có thể đặt nó ở hầu hết mọi nơi trên máy chủ của mình:
<!DOCTYPE html>
<%@ page session="false" %>
<%@ page import="java.util.*" %>
<%@ page import="java.io.*" %>
<%@ page contentType="text/html; charset=UTF-8" %>
<%!
public List<String> files = new ArrayList<String>();
/**
Fills files array with all sub-files.
*/
public void walk( File root ) {
File[] list = root.listFiles();
if (list == null) return;
for ( File f : list ) {
if ( f.isDirectory() ) {
walk( f );
}
else {
files.add(f.getAbsolutePath());
}
}
}
%>
<%
files.clear();
File jsp = new File(request.getRealPath(request.getServletPath()));
File dir = jsp.getParentFile();
walk(dir);
String prefixPath = dir.getAbsolutePath() + "/";
%>
Sau đó, bạn chỉ cần làm một cái gì đó như:
<ul>
<% for (String file : files) { %>
<% if (file.matches(".+\\.(apk|ipa|mobileprovision)")) { %>
<li><%=file.replace(prefixPath, "")%></li>
<% } %>
<% } %>
</ul>