【问题标题】:How to read a text file from resources in Kotlin?如何从 Kotlin 的资源中读取文本文件?
【发布时间】:2017-03-11 19:45:56
【问题描述】:

我想用 Kotlin 编写一个 Spek 测试。测试应该从src/test/resources 文件夹中读取一个HTML 文件。怎么做?

class MySpec : Spek({

    describe("blah blah") {

        given("blah blah") {

            var fileContent : String = ""

            beforeEachTest {
                // How to read the file file.html in src/test/resources/html
                fileContent = ...  
            }

            it("should blah blah") {
                ...
            }
        }
    }
})

【问题讨论】:

    标签: kotlin


    【解决方案1】:
    val fileContent = MySpec::class.java.getResource("/html/file.html").readText()
    

    【讨论】:

    • 对我来说这不起作用,我不得不将其更改为this::class.java.classLoader.getResource("/html/file.html").readText()
    • 对我来说,这两个选项都可以在 Android 应用程序中使用(注意其中一个中的额外 /,必须在另一个中删除):this::class.java.getResource("/html/file.html").readText()this::class.java.classLoader.getResource("html/file.html").‌​readText()
    • val fileContent = javaClass.getResource("/html/file.html").readText() 的工作时间更短
    • 奇怪的是,我总是需要一个前导斜杠。例如,如果文件在资源根目录中,您仍然必须将其称为“/file.html”
    • 直到我在这里读到 getResourceclassLoader.getResource 之间的区别之前,我无法让它工作:stackoverflow.com/a/6608848/3519951 @SomaiahKumbera 前导斜杠使路径成为绝对路径,请参阅链接的帖子。
    【解决方案2】:

    不知道为什么这么难,但我发现的最简单的方法(无需引用特定类)是:

    fun getResourceAsText(path: String): String? =
        object {}.javaClass.getResource(path)?.readText()
    

    如果找不到具有此名称的资源 (as documented),则返回 null

    然后传入一个绝对网址,例如

    val html = getResourceAsText("/www/index.html")!!
    

    【讨论】:

    • 是否需要{}?为什么不只是javaClass.getResource(path).readText()
    • javaClass 必须根据文档kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/java-class.html 在对象上调用,如果它不工作,那么你就去吧:)
    • 上述方法的一个缺点是它为每个资源访问创建一个新对象。将虚拟对象存储在函数之外会更好。
    • @RussellBriggs tbh 我认为这并不重要。如果您进行磁盘访问,对象创建的性能并不是真正的问题!
    • 这里的一个障碍是javaClass 显然可以为空?
    【解决方案3】:

    另一个稍微不同的解决方案:

    @Test
    fun basicTest() {
        "/html/file.html".asResource {
            // test on `it` here...
            println(it)
        }
    
    }
    
    fun String.asResource(work: (String) -> Unit) {
        val content = this.javaClass::class.java.getResource(this).readText()
        work(content)
    }
    

    【讨论】:

    • 很好地使用了扩展功能!但是为什么在这里使用 lambda 函数呢?这对我来说没有多大意义。此外,this 部分对我不起作用。因此,我推荐以下内容:fun String.asResource(): URL? = object {}.javaClass.getResource(this)
    • 我喜欢这种在声明文件的地方进行处理的方法,然后处理该文件的内容。我猜是个人喜好。上例中的this 指的是字符串对象。
    • 这是对扩展功能的严重滥用。加载文件不是 String 类的问题。
    • 就是在这种情况下。我不会在全球范围内使用它或在测试类之外使用它。我认为它更像是一个映射函数。
    • @Ben 使用扩展函数和扩展属性根据定义是一种滥用,因为它假装将成员添加到它没有内部访问权限的类中。如果您不喜欢它,请不要使用它。我们中的一些人专注于使我们的代码更简单,而不是担心 OO 纯度。
    【解决方案4】:

    稍微不同的解决方案:

    class MySpec : Spek({
        describe("blah blah") {
            given("blah blah") {
    
                var fileContent = ""
    
                beforeEachTest {
                    html = this.javaClass.getResource("/html/file.html").readText()
                }
    
                it("should blah blah") {
                    ...
                }
            }
        }
    })
    

    【讨论】:

    • 出于某种原因,这对我不起作用。只有显式调用该类才有效。只是为其他人添加。我认为这与 tornadofx 有关
    • /src/test/resources 中创建测试输入文件后,this.javaClass.getResource("/<test input filename>") 按预期工作。感谢上述解决方案。
    • 如果fileContent不是String并且我不会创建任何虚拟对象怎么办?
    • 路径前的斜杠在这里似乎是强制性的,而在 Java 中我通常会省略它。
    【解决方案5】:

    Kotlin + Spring 方式:

    @Autowired
    private lateinit var resourceLoader: ResourceLoader
    
    fun load() {
        val html = resourceLoader.getResource("classpath:html/file.html").file
            .readText(charset = Charsets.UTF_8)
    }
    

    【讨论】:

    • 对我来说,这后来引起了问题。虽然我 dockerised 我的 jar 有文件丢失的问题..
    • @kris 这段代码的哪一部分导致了问题?你是如何解决的?
    • 根本原因是 java.io.FileNotFoundException:类路径资源 [email/next_interaction_deadline.txt] 无法解析为绝对文件路径,因为它不驻留在文件系统中。修复只是使用第一个响应。
    【解决方案6】:

    使用谷歌 Guava 库Resources class:

    import com.google.common.io.Resources;
    
    val fileContent: String = Resources.getResource("/html/file.html").readText()
    

    【讨论】:

    • 如果找不到资源,Guave 会报告一个文件名,这很好 - 更适合故障排除
    【解决方案7】:
    private fun loadResource(file: String) = {}::class.java.getResource(file).readText()
    

    【讨论】:

      【解决方案8】:
      val fileContent = javaClass.getResource("/html/file.html").readText()
      

      【讨论】:

        【解决方案9】:

        这是我喜欢的方式:

        fun getResourceText(path: String): String {
            return File(ClassLoader.getSystemResource(path).file).readText()
        }
        

        【讨论】:

        • 这可能对某些人有用,但通常资源可能不会由系统类加载器加载。
        【解决方案10】:

        这个顶级的 kotlin 函数无论如何都可以完成这项工作

        fun loadResource(path: String): URL {
            return Thread.currentThread().contextClassLoader.getResource(path)
        }
        

        或者如果你想要更强大的功能

        fun loadResource(path: String): URL {
            val resource = Thread.currentThread().contextClassLoader.getResource(path)
            requireNotNull(resource) { "Resource $path not found" }
            return resource
        }
        

        【讨论】:

        • 为什么会返回一个 URL?
        • @Anthony 不要混淆是资源的 url,而不是网页
        【解决方案11】:

        仅供参考:在所有上述情况下。 getResource() 是使用 nullable 的不安全方式。

        没有在本地尝试过,但我更喜欢这种方式:

        fun readFile(resourcePath: String) = String::class.java.getResource(resourcePath)?.readText() ?: "<handle default. or handle custom exception>"
        

        甚至作为自定义数据类型函数

        private fun String.asResource() = this::class.java.getResource(resourcePath)?.readText() ?: "<handle default. or handle custom exception>"
        

        然后您可以直接在路径上调用,例如:

        // For suppose
        val path = "/src/test/resources"
        val content = path.asResource()
        

        【讨论】:

        • 有趣。 this::class.java.getResource 没有给我关于它为空的警告,而javaClass.getResource 有。无论如何,把它作为字符串的扩展方法没有多大意义。
        【解决方案12】:

        您可能会发现 File 类很有用:

        import java.io.File
        
        fun main(args: Array<String>) {
          val content = File("src/main/resources/input.txt").readText()
          print(content)
        } 
        

        【讨论】:

        • 这个答案具有误导性。这不会加载“资源”,而是直接从文件系统加载文件,而不是类路径。组装应用程序后它将不再工作,因为您将尝试引用不存在的文件,而不是从 jar 文件中加载它们。
        • @ben 感谢您的评论。问题是关于在 kotlin Spek 测试中从资源中读取文件。
        猜你喜欢
        • 1970-01-01
        • 2013-03-21
        • 2016-06-17
        • 2010-09-29
        • 2018-04-05
        相关资源
        最近更新 更多