如何使用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中。

列出资源文件夹中文件的唯一选项是nio的文件系统概念,因为这可以将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的协议并返回Path,来判断我们是否在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. 我的生产库中没有额外的测试代码

这里有一个从资源文件夹中读取所有文件的函数。我的用例是小文件。灵感来自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

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

最新更新