如何使用scala列出资源文件夹中的所有文件

假设您的资源文件夹中的结构如下:

resources
├─spec_A
| ├─AA
| | ├─file-aev
| | ├─file-oxa
| | ├─…
| | └─file-stl
| ├─BB
| | ├─file-hio
| | ├─file-nht
| | ├─…
| | └─file-22an
| └─…
├─spec_B
| ├─AA
| | ├─file-aev
| | ├─file-oxa
| | ├─…
| | └─file-stl
| ├─BB
| | ├─file-hio
| | ├─file-nht
| | ├─…
| | └─file-22an
| └─…
└─…

任务是逐个读取给定规范 spec_X 的所有文件。出于显而易见的原因,我们不希望使用 Source.fromResource("spec_A/AA/…") 打开代码中数百个文件的确切名称作为字符串文字。

此外,该解决方案当然应该在开发环境中运行,即无需打包到 jar 中。

stack overflow How to list all files from resources folder with scala
原文答案
author avatar

接受的答案

感谢@TrebledJ 的回答,这可以最小化为以下内容:

class ConfigFiles (val basePath String) {
  lazy val jarFileSystem: FileSystem = FileSystems.newFileSystem(getClass.getResource(basePath).toURI, Map[String, String]().asJava);

  def listPathsFromResource(folder: String): List[Path] = {
    Files.list(getPathForResource(folder))
      .filter(p ⇒ Files.isRegularFile(p, Array[LinkOption](): _*))
      .sorted.toList.asScala.toList // from Stream to java List to Scala Buffer to scala List
  }

  private def getPathForResource(filename: String) = {
    val url = classOf[ConfigFiles].getResource(basePath + "/" + filename)
    if ("file" == url.getProtocol) Paths.get(url.toURI)
    else jarFileSystem.getPath(basePath, filename)
  }
}

需要特别注意空置地图。

检查 URL 协议似乎是不可避免的。 Git 已更新,欢迎 PUll 请求: https://github.com/kurellajunior/list-files-from-resource-directory


答案:

作者头像

我发现在资源文件夹中列出文件的唯一选择是使用 nio 的文件系统概念,因为它可以将 jar 文件作为文件系统加载。但这有两个主要缺点:

  1. java.nio 使用 java Stream API,我无法从 scala 代码中收集到: Collectors.toList() 无法编译,因为它无法确定正确的类型。
  2. 文件系统需要操作系统文件系统和基于 jar 文件的文件系统的不同基本路径。所以我需要手动区分testing和jar-based running这两种情况。

如果需要,首先延迟加载 jar 文件系统

  private static FileSystem jarFileSystem;

  static synchronized private FileSystem getJarFileAsFilesystem(String drg_file_root) throws URISyntaxException, IOException {
    if (jarFileSystem == null) {
      jarFileSystem = FileSystems.newFileSystem(ConfigFiles.class.getResource(drg_file_root).toURI(), Collections.emptyMap());
    }
    return jarFileSystem;
  }

接下来通过检查 URL 的协议并返回路径来确定我们是否在 jar 中。 (jar 文件中的协议将是 jar:

  static Path getPathForResource(String resourceFolder, String filename) throws IOException, URISyntaxException {
    URL url = ConfigFiles.class.getResource(resourceFolder + "/" + filename);
    return "file".equals(url.getProtocol())
           ? Paths.get(url.toURI())
           : getJarFileAsFilesystem(resourceFolder).getPath(resourceFolder, filename);
  }

最后列出并收集到一个java列表中

  static List<Path> listPathsFromResource(String resourceFolder, String subFolder) throws IOException, URISyntaxException {
    return Files.list(getPathForResource(resourceFolder, subFolder))
      .filter(Files::isRegularFile)
      .sorted()
      .collect(toList());
  }

只有这样我们才能回去做 Scala 和 fetch 是

class SpecReader {
  def readSpecMessage(spec: String): String = {
    List("CN", "DO", "KF")
      .flatMap(ConfigFiles.listPathsFromResource(s"/spec_$spec", _).asScala.toSeq)
      .flatMap(path ⇒ Source.fromInputStream(Files.newInputStream(path), "UTF-8").getLines())
      .reduce(_ + " " + _)
  }
}

object Main {
  def main(args: Array[String]): Unit = {
    System.out.println(new SpecReader().readSpecMessage(args.head))
  }
}

我在这里放了一个正在运行的迷你项目来证明它: https://github.com/kurellajunior/list-files-from-resource-directory

但这当然远非最佳。我想消除上面提到的两个缺点,这样,

  1. 仅 scala 文件
    2.我的生产库中没有额外的测试代码
作者头像

这是一个从资源文件夹中读取所有文件的函数。我的用例是小文件。受 Jan 的回答启发,但不需要用户定义的收集器或搞乱 Java。

// Helper for reading an individual file.
def readFile(path: Path): String =
  Source.fromInputStream(Files.newInputStream(path), "UTF-8").getLines.mkString("n")

private var jarFS: FileSystem = null; // Static variable for storing a FileSystem. Will be loaded on the first call to getPath.
/**
 * Gets a Path object corresponding to an URL.
 * @param url The URL could follow the `file:` (usually used in dev) or `jar:` (usually used in prod) rotocols.
 * @return A Path object.
 */
def getPath(url: URL): Path = {
  if (url.getProtocol == "file")
    Paths.get(url.toURI)
  else {
    // This hacky branch is to handle reading resource files from a jar (where url is jar:...).
    val strings = url.toString.split("!")
    if (jarFS == null) {
      jarFS = FileSystems.newFileSystem(URI.create(strings(0)), Map[String, String]().asJava)
    }
    jarFS.getPath(strings(1))
  }
}

/**
 * Given a folder (e.g. "A"), reads all files under the resource folder (e.g. "src/main/resources/A/**") as a Seq[String]. */
 * @param folder Relative path to a resource folder under src/main/resources.
 * @return A sequence of strings. Each element corresponds to the contents of a single file.
 */
def readFilesFromResource(folder: String): Seq[String] = {
  val url = Main.getClass.getResource("/" + folder)
  val path = getPath(url)
  val ls = Files.list(path)
  ls.collect(Collectors.toList()).asScala.map(readFile) // Magic!
}

(不适合有问题的例子)

相关进口:

import java.nio.file._
import scala.collection.JavaConverters._ // Needed for .asScala
import java.net.{URI, URL}
import java.util.stream._
import scala.io.Source