你能在一个程序中使用 2 java 个 nio 文件系统吗?

Can you use 2 java nio file systems in one program?

我正在编写一个项目,该项目将从模板文件夹结构(我将其作为资源打包在我的 jar 中)生成文件夹结构。我想将所有资源保存在我程序的 jar 文件中,这样我的用户只需知道这个 jar 文件,而不必担心单独的资源文件夹。

我这里有这段代码(使用 java 库的 Scala 代码):

package ru.company.project

import java.nio.file.{FileSystems, Files, Path, Paths}
import java.util

object Main {
  def main(args: Array[String]): Unit = {
    val uri = this.getClass.getClassLoader.getResource("resource1").toURI

    // create file system
    val env = new util.HashMap[String, String]()
    env.put("create", "true")
    val jarFS = FileSystems.newFileSystem(uri, env)

    val file = Paths.get(uri)

    Files.walk(file).forEach(f => {
      println(f)
      if (Files.isRegularFile(f))
        copyToLocal(f)
    })

    jarFS.close()
  }

  def copyToLocal(file: Path): Unit = {
    val content = Files.readAllLines(file)
    Files.write(file, content)
  }
}

它能够很好地从我的 jar 文件中读取资源,但是当它在 copyToLocal 中执行 Files.write(file, content) 时,它会失败并出现以下异常:

Exception in thread "main" java.nio.file.FileAlreadyExistsException: resource1/another_text_file.txt
        at com.sun.nio.zipfs.ZipFileSystem.newOutputStream(ZipFileSystem.java:516)
        at com.sun.nio.zipfs.ZipPath.newOutputStream(ZipPath.java:790)
        at com.sun.nio.zipfs.ZipFileSystemProvider.newOutputStream(ZipFileSystemProvider.java:285)
        at java.nio.file.Files.newOutputStream(Unknown Source)
        at java.nio.file.Files.write(Unknown Source)
        at java.nio.file.Files.write(Unknown Source)
        at ru.company.project.Main$.copyToLocal(Main.scala:28)
        at ru.company.project.Main$.$anonfun$main(Main.scala:20)
        at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(Unknown Source)
        at java.util.stream.ReferencePipeline.accept(Unknown Source)
        at java.util.Iterator.forEachRemaining(Unknown Source)
        at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Unknown Source)
        at java.util.stream.AbstractPipeline.copyInto(Unknown Source)
        at java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)
        at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(Unknown Source)
        at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(Unknown Source)
        at java.util.stream.AbstractPipeline.evaluate(Unknown Source)
        at java.util.stream.ReferencePipeline.forEach(Unknown Source)
        at ru.company.project.Main$.main(Main.scala:17)
        at ru.company.project.Main.main(Main.scala)

这是因为我不知何故需要使用另一个文件系统来写入 jar 文件之外,但似乎当您这样做时 FilesSystems.newFileSystem 它会创建一个全局文件系统。

那么,如何创建另一个文件系统并同时使用2个文件系统?

你的方法copyToLocal(...)是错误的。它从一个文件中读取,然后将所有读取的行再次写回同一个文件。没有 'magic mechanism' 'knows' 哪个文件系统用于读取,哪个文件系统用于写入。

所以主要问题是: Files.write(path, content)这样的操作如何知道,FileSystem使用哪个?

答案: 它使用了用于创建路径的FileSystem

更长的答案: Path 对象总是关联到 FileSystem。它始终表示 特定文件系统 的文件或文件夹,因此它包含文件系统和路径本身。

Files 的所有静态方法,如 Files.delete(path)Files.list(path) 使用与给定路径关联的文件系统。在多个路径上运行的方法,如 Files.copy(source, target, options) 在两个路径都来自同一文件系统(这是常见情况)的情况下进行了优化,但也应该适用于不同的文件系统(如您的情况)。

如果您使用 (1) 工厂方法 Paths.get(first, ...more),路径总是关联到 默认值 FileSystem。如果您使用 (2) Paths.get(uri),则会在所有已安装的文件系统提供程序中搜索该文件系统。如果(3)使用了具体FileSystemgetPath(...)方法,则该路径与此文件系统相关联。

所以你应该使用这三种方式中的任何一种来控制应该与你的路径相关联的文件系统,例如:

try(FileSystem jarFs = ...;
    FileSystem defaultFs = ...) {

    Path source = jarFs.getPath(...);
    Path target = defaultFs.getPath(...);

    Files.copy(source, target);
}

(代码片段在 Java 中,因为我不了解 Scala)

这意味着您的代码:

...
val jarPath = Paths.get(uri)

Files.walk(jarPath).forEach(jarFile => {
  println(jarFile)
  if (Files.isRegularFile(jarFile))
    Files.copy(jarFile, createLocalPathFor(jarFile))
})
...

def createLocalPathFor(path: Path): Path = {
  // TODO: create a local path for the given file
  // TODO: which local folder? which local file name?
  return Paths.get(...)
}