【问题标题】:How to list all files from resources folder with scala如何使用scala列出资源文件夹中的所有文件
【发布时间】:2021-06-17 16:47:54
【问题描述】:

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

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 中。

【问题讨论】:

    标签: scala jar nio


    【解决方案1】:

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

    1. java.nio 使用 java Stream API,我无法从 scala 代码中收集它:Collectors.toList() 无法编译,因为它无法确定正确的类型。
    2. 对于 OS 文件系统和基于 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 和 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. 我的生产库中没有额外的测试代码

    【讨论】:

    • 对于Collectors.toList,你可以手动给它类型参数,对吧?
    【解决方案2】:

    好的,经过一些尝试和分析收集器的 API,我能够通过 ListBuffer 收集器创建一个 scala.List。

    class SpecReader (val spec:String) {
    
      private val basePath = s"/spec_$spec"
      lazy val jarFileSystem: FileSystem = FileSystems.newFileSystem(getClass.getResource(basePath).toURI, Map[String, String]().asJava);
    
    
      def readSpecMessageScala(): String = {
        List("CN", "DO", "KF")
          .flatMap(listPathsFromResource)
          .flatMap(path ⇒ Source.fromInputStream(Files.newInputStream(path), "UTF-8").getLines())
          .reduce(_ + " " + _)
      }
    
      val collector: Collector[_ >: Path, ListBuffer[Path], List[Path]] =  Collector.of(
        new Supplier[ListBuffer[Path]]() {
          override def get(): ListBuffer[Path] = ListBuffer[Path]()
        },
        new BiConsumer[ListBuffer[Path], Path]() {
          override def accept(t: ListBuffer[Path], u: Path): Unit = t.addOne(u)
        },
        new BinaryOperator[ListBuffer[Path]]() {
          override def apply(t: ListBuffer[Path], u: ListBuffer[Path]): ListBuffer[Path] = t.addAll(u)
        },
        new Function[ListBuffer[Path], List[Path]](){
          override def apply(v1: ListBuffer[Path]): List[Path] = v1.toList
        },
        Array[Collector.Characteristics](): _*
    )
    
      def listPathsFromResource(folder: String): List[Path] = {
        Files.list(getPathForResource(folder))
          .filter(p ⇒ Files.isRegularFile(p, Array[LinkOption](): _*))
          .sorted.collect(collector)
      }
    
      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)
      }
    }
    
    object Main {
      def main(args: Array[String]): Unit = {
        System.out.println(new SpecReader(args.head).readSpecMessage())
      }
    }
    

    需要特别注意空可变参数和空设置映射。

    测试和jar操作仍然如此。 Git 更新,欢迎 PUll 请求:https://github.com/kurellajunior/list-files-from-resource-directory

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-04-05
      • 1970-01-01
      • 1970-01-01
      • 2012-08-12
      • 2014-07-12
      • 1970-01-01
      相关资源
      最近更新 更多