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

4
假设您的资源文件夹中具有以下结构:
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包。

3个回答

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

你实际上尝试过对非打包和打包的程序代码进行测试吗?如果我还记得正确的话,上次 Files.list(…) 方法失败是因为 toURU 中的 URI 实际上指向了协议 jar:… - Jan
@Jan 你是对的,我的原始代码对于jar:协议文件无效。我尝试使用你答案中的jarFileSystem方法,但getClass.getResource("/").toURI一直返回file:/opt/spark/conf/(我正在使用spark),这使得FileSystem抛出异常。我设法使用了这种方法:https://dev59.com/1FzUa4cB1Zd3GeqP7umu#32557217。有点丑陋,但似乎可以工作。 - TrebledJ
好的,你可以在 Files.list() 之后直接使用 javaCollectors 来避免使用自己编写的收集器。很不错。但是对于 jar 或文件的检查仍然保持不变。甚至你使用的 jar-filesystem 也是一样的。如果过滤仍然有效,我将合并这些想法以使其更加简洁。谢谢。 - Jan

2
我发现列出资源文件夹内文件的唯一选项是使用nio的Filesystem概念,因为它可以将jar文件作为文件系统加载。但这带来了两个主要问题:
  1. java.nio使用java Stream API,我无法从scala代码中收集: Collectors.toList() 无法编译,因为它无法确定正确的类型。
  2. 文件系统需要不同的基本路径用于操作系统文件系统和基于jar文件的文件系统。因此,我需要手动区分这两种情况并进行测试和基于jar的运行。

如果需要,请首先惰性加载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 并获取它

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. 在我的生产库中没有额外的测试代码

对于Collectors.toList,你可以手动给它指定类型参数,对吗? - user

0
感谢@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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接