如何在 Scala 中使用 java.nio.file.Files.walkFileTree

发布于 2024-12-28 07:35:36 字数 678 浏览 0 评论 0原文

我想在 Scala 中使用新的 java.nio.file.Files.walkFileTree 。我什至成功了:

class Visitor
   extends
      java.nio.file.SimpleFileVisitor [java.nio.file.Path]
   {
   override def visitFile(
      File : java.nio.file.Path,
      Attrs : java.nio.file.attribute.BasicFileAttributes) : java.nio.file.FileVisitResult =
   {
      if (! File.toString.contains(".svn"))
      {
         System.out.println(File);
      } // if

      java.nio.file.FileVisitResult.CONTINUE;
   } // visitFile
} // Visitor

java.nio.file.Files.walkFileTree (Project_Home, new Visitor)

虽然这段代码运行良好,但我感觉有点像将 Java 范例引入 Scala。因此,向真正的 Scala 大师提出一个问题:有什么我可以改进的地方,还是仅此而已?

I want to use the new java.nio.file.Files.walkFileTree in Scala. And I was even successful:

class Visitor
   extends
      java.nio.file.SimpleFileVisitor [java.nio.file.Path]
   {
   override def visitFile(
      File : java.nio.file.Path,
      Attrs : java.nio.file.attribute.BasicFileAttributes) : java.nio.file.FileVisitResult =
   {
      if (! File.toString.contains(".svn"))
      {
         System.out.println(File);
      } // if

      java.nio.file.FileVisitResult.CONTINUE;
   } // visitFile
} // Visitor

java.nio.file.Files.walkFileTree (Project_Home, new Visitor)

But while this code works fine I feels a bit like carrying Java paradigms into Scala. So a question to the true Scala Gurus: Is there anything I could improve or is this just it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

烟酒忠诚 2025-01-04 07:35:36

Visitor 实际上是一个没有函数优势的 foreach,所以让我们创建一个 foreach。该方法是静态的,但它采用 Path 作为第一个参数,因此我们将使用 foreach 方法丰富 Path ,该方法是通过像这样的东西:

import java.nio.file._
import java.nio.file.attribute.BasicFileAttributes

implicit def fromNioPath(path: Path): TraverseFiles = new TraversePath(path)

其他所有内容都在 TraversePath 类中,看起来有点像这样:

class TraversePath(path: Path) {
  def foreach(f: (Path, BasicFileAttributes) => Unit) {
    // ...
  }
}

这足以让你这样写:

ProjectHome foreach ((file, _) => if (!file.toString.contains(".svn")) println(File))

当然,它实际上不会做任何事情,所以让我们得到它做某事:

class TraversePath(path: Path) {
  def foreach(f: (Path, BasicFileAttributes) => Unit) {
    class Visitor extends SimpleFileVisitor[Path] {
      override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = try { 
        f(file, attrs)
        FileVisitResult.CONTINUE
      } catch { 
        case _ => FileVisitResult.TERMINATE
      }
    }
    Files.walkFileTree(path, new Visitor)
  }
}

现在那条线将做同样的事情你的代码做到了!然而,我们可以进一步改进它。碰巧 foreachTraversable 所需的唯一方法,因此我们可以扩展该类,并获取 Scala 集合的所有方法!

唯一的问题是 Traversable.foreach 函数只接受一个参数,而这里我们接受两个参数。不过,我们可以将其更改为接收元组。这是完整的代码:

import java.nio.file._
import java.nio.file.attribute.BasicFileAttributes
import scala.collection.Traversable

// Make it extend Traversable
class TraversePath(path: Path) extends Traversable[(Path, BasicFileAttributes)] {

  // Make foreach receive a function from Tuple2 to Unit
  def foreach(f: ((Path, BasicFileAttributes)) => Unit) {
    class Visitor extends SimpleFileVisitor[Path] {
      override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = try {
        // Pass a tuple to f
        f(file -> attrs)
        FileVisitResult.CONTINUE
      } catch { 
        case _ => FileVisitResult.TERMINATE
      }
    }
    Files.walkFileTree(path, new Visitor)
  }
}

ProjectHome foreach {
  // use case to seamlessly deconstruct the tuple
  case (file, _) => if (!file.toString.contains(".svn")) println(File)
}

免责声明:我没有测试过这些代码,因为我没有安装 Java 7。可能存在一些错误。

A Visitor is really a foreach without the benefit of functions, so let's make a foreach. The method is static, but it takes as first argument a Path, so we'll enrich Path with a foreach method, which is done with something like this:

import java.nio.file._
import java.nio.file.attribute.BasicFileAttributes

implicit def fromNioPath(path: Path): TraverseFiles = new TraversePath(path)

And everything else is inside the TraversePath class, which looks somewhat like this:

class TraversePath(path: Path) {
  def foreach(f: (Path, BasicFileAttributes) => Unit) {
    // ...
  }
}

This is enough for you to write this:

ProjectHome foreach ((file, _) => if (!file.toString.contains(".svn")) println(File))

Of course, it won't actually do anything, so let's get it to do something:

class TraversePath(path: Path) {
  def foreach(f: (Path, BasicFileAttributes) => Unit) {
    class Visitor extends SimpleFileVisitor[Path] {
      override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = try { 
        f(file, attrs)
        FileVisitResult.CONTINUE
      } catch { 
        case _ => FileVisitResult.TERMINATE
      }
    }
    Files.walkFileTree(path, new Visitor)
  }
}

There, now that line will do the same thing as your code did! However, we can improve it further. It happens that foreach is the only method required of Traversable, so we can extend that class, and get all the methods of a Scala collection!

The only problem is that a Traversable.foreach function takes only one argument, and here we are taking two. We can change it into receive a tuple, though. Here's the full code:

import java.nio.file._
import java.nio.file.attribute.BasicFileAttributes
import scala.collection.Traversable

// Make it extend Traversable
class TraversePath(path: Path) extends Traversable[(Path, BasicFileAttributes)] {

  // Make foreach receive a function from Tuple2 to Unit
  def foreach(f: ((Path, BasicFileAttributes)) => Unit) {
    class Visitor extends SimpleFileVisitor[Path] {
      override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = try {
        // Pass a tuple to f
        f(file -> attrs)
        FileVisitResult.CONTINUE
      } catch { 
        case _ => FileVisitResult.TERMINATE
      }
    }
    Files.walkFileTree(path, new Visitor)
  }
}

ProjectHome foreach {
  // use case to seamlessly deconstruct the tuple
  case (file, _) => if (!file.toString.contains(".svn")) println(File)
}

Disclaimer: I have tested none of this code, because I don't have Java 7 installed. There are probably some bugs.

禾厶谷欠 2025-01-04 07:35:36

Daniel 的回答为基础,我做了一些工作,使 Path 可通过方便的隐式访问,正如您在集合中使用的那样。请注意,并非包含所有功能。

class TraversePath(path: Path) {
    def foreach(f: (Path, BasicFileAttributes) => Unit) {
        Files.walkFileTree(path, new SimpleFileVisitor[Path] {
            override def visitFile(file: Path, attrs: BasicFileAttributes) = {
                f(file, attrs)
                FileVisitResult.CONTINUE
            }
        })
    }

    /**
     * foreach that takes FileVisitResult instead of Unit
     */
    def foreach2(f: (Path, BasicFileAttributes) => FileVisitResult) {
        Files.walkFileTree(path, new SimpleFileVisitor[Path] {
            override def visitFile(file: Path, attrs: BasicFileAttributes) = f(file, attrs)
        })
    }

    def foldLeft[T](t: T)(f: (T, Path) => T) = {
        var current = t
        foreach((p, _) => current = f(current, p))
        current
    }

    def forall(f: Path => Boolean) = {
        var ret = true
        foreach2((p, _) =>
            if ( !f(path) ) {
                ret = false
                FileVisitResult.TERMINATE
            }
            else
                FileVisitResult.CONTINUE
        )
        ret
    }

    def exists(f: Path => Boolean) = {
        var ret = false
        foreach2((p, _) =>
            if ( f(path) ) {
                ret = true
                FileVisitResult.TERMINATE
            }
            else
                FileVisitResult.CONTINUE
        )
    }

    /**
     * Directly modifies the underlying path.
     */
    def mapReal(f: Path => Path) = foreach((p, _) => Files.move(p, f(p)))

    /**
     * @param f map function
     * @return a left-folded list with the map function applied to each element
     */
    def map(f: Path => Path) = foldLeft(Nil: List[Path]) {
        case (xs, p) => xs ::: f(p) :: Nil
    }

    def find(f: Path => Boolean) = {
        var k = None: Option[Path]
        foreach2((p, _) =>
            if ( f(p) ) {
                k = Some(p)
                FileVisitResult.TERMINATE
            } else FileVisitResult.CONTINUE
        )
        k
    }
}

implicit def fromNioPath(path: Path) = new TraversePath(path)

java.nio API 非常强大,恕我直言,非常适合与 Scala 一起使用。有了这些隐式(还有更多,如果你想编写一些函数),完成更困难的任务就变得非常简单。

您现在可以通过编写如下内容来使用它:

val path1 = Paths.get(sys.props("user.home"), "workspace")

val path2 = Paths.get(sys.props("user.home"), "workspace2")

val list1 = path1.foldLeft(Nil: List[Path]) {
    (xs, p) => xs ::: path1.relativize(p) :: Nil
}
val list2 = path2.foldLeft(Nil: List[Path]) {
    (xs, p) => xs ::: path2.relativize(p) :: Nil
}
(list1 diff list2) foreach println

问候,
丹尼尔

Taking Daniel's answer as fundament, I have worked a little to make Path accessible with convenient implicits, as you are used in collections. Notice that not all functions are included.

class TraversePath(path: Path) {
    def foreach(f: (Path, BasicFileAttributes) => Unit) {
        Files.walkFileTree(path, new SimpleFileVisitor[Path] {
            override def visitFile(file: Path, attrs: BasicFileAttributes) = {
                f(file, attrs)
                FileVisitResult.CONTINUE
            }
        })
    }

    /**
     * foreach that takes FileVisitResult instead of Unit
     */
    def foreach2(f: (Path, BasicFileAttributes) => FileVisitResult) {
        Files.walkFileTree(path, new SimpleFileVisitor[Path] {
            override def visitFile(file: Path, attrs: BasicFileAttributes) = f(file, attrs)
        })
    }

    def foldLeft[T](t: T)(f: (T, Path) => T) = {
        var current = t
        foreach((p, _) => current = f(current, p))
        current
    }

    def forall(f: Path => Boolean) = {
        var ret = true
        foreach2((p, _) =>
            if ( !f(path) ) {
                ret = false
                FileVisitResult.TERMINATE
            }
            else
                FileVisitResult.CONTINUE
        )
        ret
    }

    def exists(f: Path => Boolean) = {
        var ret = false
        foreach2((p, _) =>
            if ( f(path) ) {
                ret = true
                FileVisitResult.TERMINATE
            }
            else
                FileVisitResult.CONTINUE
        )
    }

    /**
     * Directly modifies the underlying path.
     */
    def mapReal(f: Path => Path) = foreach((p, _) => Files.move(p, f(p)))

    /**
     * @param f map function
     * @return a left-folded list with the map function applied to each element
     */
    def map(f: Path => Path) = foldLeft(Nil: List[Path]) {
        case (xs, p) => xs ::: f(p) :: Nil
    }

    def find(f: Path => Boolean) = {
        var k = None: Option[Path]
        foreach2((p, _) =>
            if ( f(p) ) {
                k = Some(p)
                FileVisitResult.TERMINATE
            } else FileVisitResult.CONTINUE
        )
        k
    }
}

implicit def fromNioPath(path: Path) = new TraversePath(path)

The java.nio API is extremely powerful and is, IMHO, very sufficing for use with Scala. With these implicits (and more, if you want to write some functions), it is very simple to accomplish even harder tasks.

You could use this now by writing something like this:

val path1 = Paths.get(sys.props("user.home"), "workspace")

val path2 = Paths.get(sys.props("user.home"), "workspace2")

val list1 = path1.foldLeft(Nil: List[Path]) {
    (xs, p) => xs ::: path1.relativize(p) :: Nil
}
val list2 = path2.foldLeft(Nil: List[Path]) {
    (xs, p) => xs ::: path2.relativize(p) :: Nil
}
(list1 diff list2) foreach println

Regards,
Danyel

何其悲哀 2025-01-04 07:35:36

这是丹尼尔的可编译脚本:

import java.nio.file._
import java.nio.file.attribute.BasicFileAttributes
import scala.collection.Traversable

// Make it extend Traversable
class TraversePath(path: Path) extends Traversable[(Path, BasicFileAttributes)] {

  // Make foreach receive a function from Tuple2 to Unit
  def foreach[U](f: ((Path, BasicFileAttributes)) => U) {
    class Visitor extends SimpleFileVisitor[Path] {
      override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = try {
        // Pass a tuple to f
        f(file -> attrs)
        FileVisitResult.CONTINUE
      } catch { 
        case _ => FileVisitResult.TERMINATE
      }
    }
    Files.walkFileTree(path, new Visitor)
  }
}
val projectHome = new TraversePath(Paths.get("."))

projectHome foreach { 
  // use case to seamlessly deconstruct the tuple
  case (file:Path, attr:BasicFileAttributes) => if (!file.toString.contains(".svn")) println(file)
}

Here's Daniel's script made compilable:

import java.nio.file._
import java.nio.file.attribute.BasicFileAttributes
import scala.collection.Traversable

// Make it extend Traversable
class TraversePath(path: Path) extends Traversable[(Path, BasicFileAttributes)] {

  // Make foreach receive a function from Tuple2 to Unit
  def foreach[U](f: ((Path, BasicFileAttributes)) => U) {
    class Visitor extends SimpleFileVisitor[Path] {
      override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult = try {
        // Pass a tuple to f
        f(file -> attrs)
        FileVisitResult.CONTINUE
      } catch { 
        case _ => FileVisitResult.TERMINATE
      }
    }
    Files.walkFileTree(path, new Visitor)
  }
}
val projectHome = new TraversePath(Paths.get("."))

projectHome foreach { 
  // use case to seamlessly deconstruct the tuple
  case (file:Path, attr:BasicFileAttributes) => if (!file.toString.contains(".svn")) println(file)
}
淡墨 2025-01-04 07:35:36

您可以使代码更漂亮一些,但最终它看起来仍然像普通的老访客模式。

You could make your code a bit more pretty, but at the end of the day it would still look like the plain old visitor pattern.

吾性傲以野 2025-01-04 07:35:36

FIles.walkFileTree 比较两个目录/同步两个目录的示例
对于文件差异

private static void compareDirectories(String srcPath, String destPath) throws IOException, InterruptedException {
    System.out.println("sync. started....");
    final Path mainDir = Paths.get(srcPath);
    final Path otherDir = Paths.get(destPath);

    // Walk thru mainDir directory
    Files.walkFileTree(mainDir, new FileVisitor<Path>() {
        @Override
        public FileVisitResult preVisitDirectory(Path path,
                BasicFileAttributes atts) throws IOException {
            return visitFile(path, atts);
        }

        @Override
        public FileVisitResult visitFile(Path path, BasicFileAttributes mainAtts)
                throws IOException {
            // I've seen two implementations on windows and MacOSX. One has passed the relative path, one the absolute path.
            // This works in both cases
            Path relativePath = mainDir.relativize(mainDir.resolve(path));
            File tmpFile = new File(otherDir+"/"+relativePath);

                if(tmpFile.exists()) {
                    BasicFileAttributes otherAtts = Files.readAttributes(otherDir.resolve(relativePath), BasicFileAttributes.class);
                    // Do your comparison logic here: we are skipping directories as all directories are traversed automatically
                    if(!new File(path.toString()).isDirectory()) {
                                                    //write your logic for comparing files
                        compareEntries(mainDir, otherDir, relativePath, mainAtts, otherAtts);
                    }
                    else {
                        File src = new File(path.toString());

                                                   //write your logic here for comparing directories
                                                   compareDirectories(src,tmpFile.toPath().toString()+"/"+s);
                    }
                }
                else {
                                            //this function will copy missing files in destPath from srcPath recursive function till depth of directory structure
                    copyFolderOrFiles(new File(path.toString()), tmpFile);
                }
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path path,
                IOException exc) throws IOException {
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path path, IOException exc)
                throws IOException {
            exc.printStackTrace();
            // If the root directory has failed it makes no sense to continue
            return (path.equals(mainDir))? FileVisitResult.TERMINATE:FileVisitResult.CONTINUE;
        }
    });
}

FIles.walkFileTree example to compare two directories / synchronizing two directories
for file difference

private static void compareDirectories(String srcPath, String destPath) throws IOException, InterruptedException {
    System.out.println("sync. started....");
    final Path mainDir = Paths.get(srcPath);
    final Path otherDir = Paths.get(destPath);

    // Walk thru mainDir directory
    Files.walkFileTree(mainDir, new FileVisitor<Path>() {
        @Override
        public FileVisitResult preVisitDirectory(Path path,
                BasicFileAttributes atts) throws IOException {
            return visitFile(path, atts);
        }

        @Override
        public FileVisitResult visitFile(Path path, BasicFileAttributes mainAtts)
                throws IOException {
            // I've seen two implementations on windows and MacOSX. One has passed the relative path, one the absolute path.
            // This works in both cases
            Path relativePath = mainDir.relativize(mainDir.resolve(path));
            File tmpFile = new File(otherDir+"/"+relativePath);

                if(tmpFile.exists()) {
                    BasicFileAttributes otherAtts = Files.readAttributes(otherDir.resolve(relativePath), BasicFileAttributes.class);
                    // Do your comparison logic here: we are skipping directories as all directories are traversed automatically
                    if(!new File(path.toString()).isDirectory()) {
                                                    //write your logic for comparing files
                        compareEntries(mainDir, otherDir, relativePath, mainAtts, otherAtts);
                    }
                    else {
                        File src = new File(path.toString());

                                                   //write your logic here for comparing directories
                                                   compareDirectories(src,tmpFile.toPath().toString()+"/"+s);
                    }
                }
                else {
                                            //this function will copy missing files in destPath from srcPath recursive function till depth of directory structure
                    copyFolderOrFiles(new File(path.toString()), tmpFile);
                }
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult postVisitDirectory(Path path,
                IOException exc) throws IOException {
            return FileVisitResult.CONTINUE;
        }

        @Override
        public FileVisitResult visitFileFailed(Path path, IOException exc)
                throws IOException {
            exc.printStackTrace();
            // If the root directory has failed it makes no sense to continue
            return (path.equals(mainDir))? FileVisitResult.TERMINATE:FileVisitResult.CONTINUE;
        }
    });
}
黑色毁心梦 2025-01-04 07:35:36

扩展其他帖子的想法。我喜欢我们可以匹配案例类的解决方案。以下代码仅返回访问者可能被调用的不同事件的字符串集合。

FileWalker(java.nio.file.Paths.get(" your dir ")).map({
    case PreVisitDirectory(dir, atts) => s"about to visit dir ${dir}"
    case PostVisitDirectory(dir, exc) => s"have visited dir ${dir}"
    case VisitFile(file, attrs) => s"visiting file ${file}"
    case VisitFileFailed(file, exc) => s"failed to visit ${file}"
})

FileWalker的实现是:

import java.io.IOException
import java.nio.file.{FileVisitResult, Files, Path, SimpleFileVisitor}
import java.nio.file.attribute.BasicFileAttributes

trait FileVisitEvent
case class PreVisitDirectory(path: Path, atts: BasicFileAttributes) extends FileVisitEvent
case class PostVisitDirectory(dir: Path, exc: IOException) extends FileVisitEvent
case class VisitFile(file: Path, attrs: BasicFileAttributes) extends FileVisitEvent
case class VisitFileFailed(file: Path, exc: IOException) extends FileVisitEvent

/**
  * Scala style walker for a directory tree
  *
  * Is a treversable over the tree which traverses different event types extending {{FileVisitEvent}}
  *
  * @param from
  */
class FileWalker(from: Path) extends Traversable[FileVisitEvent] {
  // just to simplify error handling
  def wrapper(x: => Unit): FileVisitResult = try {
    x
    FileVisitResult.CONTINUE
  }
  catch {
    case _ : Throwable => FileVisitResult.TERMINATE
  }

  override def foreach[U](f: (FileVisitEvent) => U): Unit = {
    Files.walkFileTree(from, new SimpleFileVisitor[Path] {
      override def preVisitDirectory(dir: Path, atts: BasicFileAttributes): FileVisitResult =
        wrapper( f(PreVisitDirectory(dir, atts)))

      override def postVisitDirectory(dir: Path, exc: IOException): FileVisitResult =
        wrapper(f(PostVisitDirectory(dir, exc)))

      override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult =
        wrapper(f(VisitFile( file, attrs ) ))

      override def visitFileFailed(file: Path, exc: IOException): FileVisitResult =
        wrapper( f(VisitFileFailed( file, exc ) ))
    })
  }
}

object FileWalker {
  def apply( from : Path ) = new FileWalker( from )
}

Extending on the ideas of the other posts. I like the solution where we can match over case classes. The following code just returns a collection of strings for the different events that the visitor would have been called on.

FileWalker(java.nio.file.Paths.get(" your dir ")).map({
    case PreVisitDirectory(dir, atts) => s"about to visit dir ${dir}"
    case PostVisitDirectory(dir, exc) => s"have visited dir ${dir}"
    case VisitFile(file, attrs) => s"visiting file ${file}"
    case VisitFileFailed(file, exc) => s"failed to visit ${file}"
})

The implementation of FileWalker is:

import java.io.IOException
import java.nio.file.{FileVisitResult, Files, Path, SimpleFileVisitor}
import java.nio.file.attribute.BasicFileAttributes

trait FileVisitEvent
case class PreVisitDirectory(path: Path, atts: BasicFileAttributes) extends FileVisitEvent
case class PostVisitDirectory(dir: Path, exc: IOException) extends FileVisitEvent
case class VisitFile(file: Path, attrs: BasicFileAttributes) extends FileVisitEvent
case class VisitFileFailed(file: Path, exc: IOException) extends FileVisitEvent

/**
  * Scala style walker for a directory tree
  *
  * Is a treversable over the tree which traverses different event types extending {{FileVisitEvent}}
  *
  * @param from
  */
class FileWalker(from: Path) extends Traversable[FileVisitEvent] {
  // just to simplify error handling
  def wrapper(x: => Unit): FileVisitResult = try {
    x
    FileVisitResult.CONTINUE
  }
  catch {
    case _ : Throwable => FileVisitResult.TERMINATE
  }

  override def foreach[U](f: (FileVisitEvent) => U): Unit = {
    Files.walkFileTree(from, new SimpleFileVisitor[Path] {
      override def preVisitDirectory(dir: Path, atts: BasicFileAttributes): FileVisitResult =
        wrapper( f(PreVisitDirectory(dir, atts)))

      override def postVisitDirectory(dir: Path, exc: IOException): FileVisitResult =
        wrapper(f(PostVisitDirectory(dir, exc)))

      override def visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult =
        wrapper(f(VisitFile( file, attrs ) ))

      override def visitFileFailed(file: Path, exc: IOException): FileVisitResult =
        wrapper( f(VisitFileFailed( file, exc ) ))
    })
  }
}

object FileWalker {
  def apply( from : Path ) = new FileWalker( from )
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文