是否有一种良好的“ scala式”(我想是功能性的)方式在目录中递归列出文件?如何匹配特定模式?
例如递归的所有文件匹配"a*.foo"
在c:\temp
。
Answers:
Scala代码通常使用Java类来处理I / O,包括读取目录。因此,您必须执行以下操作:
import java.io.File
def recursiveListFiles(f: File): Array[File] = {
val these = f.listFiles
these ++ these.filter(_.isDirectory).flatMap(recursiveListFiles)
}
您可以收集所有文件,然后使用正则表达式进行过滤:
myBigFileArray.filter(f => """.*\.html$""".r.findFirstIn(f.getName).isDefined)
或者,您可以将正则表达式合并到递归搜索中:
import scala.util.matching.Regex
def recursiveListFiles(f: File, r: Regex): Array[File] = {
val these = f.listFiles
val good = these.filter(f => r.findFirstIn(f.getName).isDefined)
good ++ these.filter(_.isDirectory).flatMap(recursiveListFiles(_,r))
}
listFiles
回报率null
,如果f
不指向一个目录,或者如果有一个IO错误(至少根据Java规范)。对于生产用途,添加空检查可能是明智的。
f.isDirectory
返回true,但f.listFiles
可以返回return null
。例如,如果您无权读取文件,则会得到一个null
。与其同时进行两个检查,不如仅添加一个空检查。
f.listFiles
当时返回null !f.isDirectory
。
我更喜欢使用Streams的解决方案,因为您可以迭代无限的文件系统(Streams是惰性求值的集合)
import scala.collection.JavaConversions._
def getFileTree(f: File): Stream[File] =
f #:: (if (f.isDirectory) f.listFiles().toStream.flatMap(getFileTree)
else Stream.empty)
搜索示例
getFileTree(new File("c:\\main_dir")).filter(_.getName.endsWith(".scala")).foreach(println)
def getFileTree(f: File): Stream[File] = f #:: Option(f.listFiles()).toStream.flatten.flatMap(getFileTree)
从Java 1.7开始,所有人都应该使用java.nio。它提供接近本地的性能(java.io非常慢),并且具有一些有用的帮助器
但是Java 1.8完全引入了您要寻找的内容:
import java.nio.file.{FileSystems, Files}
import scala.collection.JavaConverters._
val dir = FileSystems.getDefault.getPath("/some/path/here")
Files.walk(dir).iterator().asScala.filter(Files.isRegularFile(_)).foreach(println)
您还要求文件匹配。尝试java.nio.file.Files.find
也java.nio.file.Files.newDirectoryStream
请参阅此处的文档:http : //docs.oracle.com/javase/tutorial/essential/io/walk.html
Scala是一种多范式语言。迭代目录的一种好方法是重用现有代码!
我会考虑使用commons-io是一种完美的Scala式的目录迭代方式。您可以使用一些隐式转换使其更容易。喜欢
import org.apache.commons.io.filefilter.IOFileFilter
implicit def newIOFileFilter (filter: File=>Boolean) = new IOFileFilter {
def accept (file: File) = filter (file)
def accept (dir: File, name: String) = filter (new java.io.File (dir, name))
}
我喜欢yura的流解决方案,但是(和其他)递归到隐藏目录中。我们还可以利用listFiles
为非目录返回null 的事实进行简化。
def tree(root: File, skipHidden: Boolean = false): Stream[File] =
if (!root.exists || (skipHidden && root.isHidden)) Stream.empty
else root #:: (
root.listFiles match {
case null => Stream.empty
case files => files.toStream.flatMap(tree(_, skipHidden))
})
现在我们可以列出文件
tree(new File(".")).filter(f => f.isFile && f.getName.endsWith(".html")).foreach(println)
或实现整个流以供以后处理
tree(new File("dir"), true).toArray
Apache Commons Io的FileUtils可以放在一行上,并且可读性很强:
import scala.collection.JavaConversions._ // important for 'foreach'
import org.apache.commons.io.FileUtils
FileUtils.listFiles(new File("c:\temp"), Array("foo"), true).foreach{ f =>
}
还没有人提到https://github.com/pathikrit/better-files
val dir = "src"/"test"
val matches: Iterator[File] = dir.glob("**/*.{java,scala}")
// above code is equivalent to:
dir.listRecursively.filter(f => f.extension ==
Some(".java") || f.extension == Some(".scala"))
这是@DuncanMcGregor的流解决方案与@ Rick-777的过滤器的混合:
def tree( root: File, descendCheck: File => Boolean = { _ => true } ): Stream[File] = {
require(root != null)
def directoryEntries(f: File) = for {
direntries <- Option(f.list).toStream
d <- direntries
} yield new File(f, d)
val shouldDescend = root.isDirectory && descendCheck(root)
( root.exists, shouldDescend ) match {
case ( false, _) => Stream.Empty
case ( true, true ) => root #:: ( directoryEntries(root) flatMap { tree( _, descendCheck ) } )
case ( true, false) => Stream( root )
}
}
def treeIgnoringHiddenFilesAndDirectories( root: File ) = tree( root, { !_.isHidden } ) filter { !_.isHidden }
这样,您就可以得到Stream [File]而不是(可能是非常大且非常慢的)List [File],同时让您决定使用DescendCheck()函数递归到哪种目录。
怎么样
def allFiles(path:File):List[File]=
{
val parts=path.listFiles.toList.partition(_.isDirectory)
parts._2 ::: parts._1.flatMap(allFiles)
}
我个人喜欢@Rex Kerr提出的解决方案的优雅和简单。但是,这是尾递归版本的样子:
def listFiles(file: File): List[File] = {
@tailrec
def listFiles(files: List[File], result: List[File]): List[File] = files match {
case Nil => result
case head :: tail if head.isDirectory =>
listFiles(Option(head.listFiles).map(_.toList ::: tail).getOrElse(tail), result)
case head :: tail if head.isFile =>
listFiles(tail, head :: result)
}
listFiles(List(file), Nil)
}
这是与Rex Kerr相似的解决方案,但包含了文件过滤器:
import java.io.File
def findFiles(fileFilter: (File) => Boolean = (f) => true)(f: File): List[File] = {
val ss = f.list()
val list = if (ss == null) {
Nil
} else {
ss.toList.sorted
}
val visible = list.filter(_.charAt(0) != '.')
val these = visible.map(new File(f, _))
these.filter(fileFilter) ++ these.filter(_.isDirectory).flatMap(findFiles(fileFilter))
}
该方法返回List [File],它比Array [File]稍微方便些。它还会忽略所有隐藏的目录(即以“。”开头)。
使用您选择的文件过滤器部分地应用了它,例如:
val srcDir = new File( ... )
val htmlFiles = findFiles( _.getName endsWith ".html" )( srcDir )
最简单的仅基于Scala的解决方案(如果您不介意需要Scala编译器库):
val path = scala.reflect.io.Path(dir)
scala.tools.nsc.io.Path.onlyFiles(path.walk).foreach(println)
否则,@ Renaud的解决方案简短而有趣(如果您不介意引入Apache Commons FileUtils):
import scala.collection.JavaConversions._ // enables foreach
import org.apache.commons.io.FileUtils
FileUtils.listFiles(dir, null, true).foreach(println)
dir
java.io.File 在哪里:
new File("path/to/dir")
似乎没有人提到scala-io
scala孵化器中的库...
import scalax.file.Path
Path.fromString("c:\temp") ** "a*.foo"
或搭配 implicit
import scalax.file.ImplicitConversions.string2path
"c:\temp" ** "a*.foo"
或者,如果您要implicit
明确...
import scalax.file.Path
import scalax.file.ImplicitConversions.string2path
val dir: Path = "c:\temp"
dir ** "a*.foo"
可在此处找到文档:http : //jesseeichar.github.io/scala-io-doc/0.4.3/index.html#!/file/glob_based_path_sets
您可以使用尾递归:
object DirectoryTraversal {
import java.io._
def main(args: Array[String]) {
val dir = new File("C:/Windows")
val files = scan(dir)
val out = new PrintWriter(new File("out.txt"))
files foreach { file =>
out.println(file)
}
out.flush()
out.close()
}
def scan(file: File): List[File] = {
@scala.annotation.tailrec
def sc(acc: List[File], files: List[File]): List[File] = {
files match {
case Nil => acc
case x :: xs => {
x.isDirectory match {
case false => sc(x :: acc, xs)
case true => sc(acc, xs ::: x.listFiles.toList)
}
}
}
}
sc(List(), List(file))
}
}
为什么使用Java的File而不是Scala的AbstractFile?
通过Scala的AbstractFile,迭代器支持允许编写更简洁的James Moore解决方案版本:
import scala.reflect.io.AbstractFile
def tree(root: AbstractFile, descendCheck: AbstractFile => Boolean = {_=>true}): Stream[AbstractFile] =
if (root == null || !root.exists) Stream.empty
else
(root.exists, root.isDirectory && descendCheck(root)) match {
case (false, _) => Stream.empty
case (true, true) => root #:: root.iterator.flatMap { tree(_, descendCheck) }.toStream
case (true, false) => Stream(root)
}