Kotlin Native:跟踪文件

当您编写命令行实用程序时,您最后要依赖的是将JVM,Ruby或Python安装在将要运行的计算机上。 我还想拥有一个易于启动的二进制文件。 并且不要太在意内存管理。

由于上述原因,近年来,每当我需要编写此类实用程序时,我都会使用Go。

Go具有一个相对简单的语法,一个好的标准库,一个垃圾回收,在输出处我们得到一个二进制。 似乎还需要什么?

不久前,Kotlin还开始以Kotlin Native的形式尝试在类似领域尝试。 该提议听起来很有希望-GC,一种单一的二进制,熟悉且方便的语法。 但是,一切是否都如我们所愿?

我们必须解决的问题:在Kotlin Native上编写一个简单的文件监视程序。 作为参数,实用程序应获取扫描的文件路径和频率。 如果文件已更改,该实用程序必须在具有新名称的相同文件夹中创建该文件的副本。

换句话说,该算法应如下所示:

fileToWatch = getFileToWatch() howOftenToCheck = getHowOftenToCheck() while (!stopped) { if (hasChanged(fileToWatch)) { copyAside(fileToWatch) } sleep(howOftenToCheck) } 

好吧,所以我们想要实现的目标似乎已经解决了。 是时候编写代码了。

星期三


我们需要的第一件事是IDE。 Vim爱好者请不要担心。

我们启动了熟悉的IntelliJ IDEA,发现在Kotlin Native中它根本无法用单词来表达。 您需要使用CLion

上一次在2004年遇到C的人的不幸事件尚未结束。 需要一个工具链。 如果使用OSX,很可能CLion会找到合适的工具链。 但是,如果您决定使用Windows而不使用C语言编程,则必须修改本教程以安装一些Cygwin

安装了IDE,整理出了工具链。 我可以开始编写代码了吗? 差不多了
由于Kotlin Native仍处于实验阶段,因此默认情况下未安装CLion中的插件。 因此,在我们看到珍贵的题词“ New Kotlin / Native Application”之前,必须手动安装它

一些设置


因此,最后我们有了一个空的Kotlin Native项目。 有趣的是,它基于Gradle(而不是Makefile),甚至基于Kotlin Script版本。

看看build.gradle.kts

 plugins { id("org.jetbrains.kotlin.konan") version("0.8.2") } konanArtifacts { program("file_watcher") } 

我们将使用的唯一插件称为Konan。 他将产生我们的二进制文件。

konanArtifacts我们指定可执行文件的名称。 在这个例子中,我们得到file_watcher.kexe

代号


现在该显示代码了。 顺便说一下,这里是:

 fun main(args: Array<String>) { if (args.size != 2) { return println("Usage: file_watcher.kexe <path> <interval>") } val file = File(args[0]) val interval = args[1].toIntOrNull() ?: 0 require(file.exists()) { "No such file: $file" } require(interval > 0) { "Interval must be positive" } while (true) { // We should do something here } } 

通常,命令行实用程序还具有可选参数及其默认值。 但是例如,我们将假定始终有两个参数: pathinterval

对于那些已经使用过Kotlin的人,将path包装在自己的File类中而不使用java.io.File似乎很奇怪。 对此的解释在一两分钟之内。

如果您不熟悉Kotlin中的require()函数,则这是验证参数的一种更便捷的方法。 Kotlin-一切都是为了方便。 可以这样写:

 if (interval <= 0) { println("Interval must be positive") return } 

通常,这是通常的Kotlin代码,没什么有趣的。 但是从现在开始它会很有趣。

让我们尝试编写常规的Kotlin代码,但是每次我们需要使用Java中的某些内容时,我们都会说“哎呀!”。 准备好了吗

让我们回到while ,让它在每个interval都印一些符号,例如一个句点。

 var modified = file.modified() while (true) { if (file.modified() > modified) { println("\nFile copied: ${file.copyAside()}") modified = file.modified() } print(".") // ... Thread.sleep(interval * 1000) } 

Thread是Java中的一个类。 我们不能在Kotlin Native中使用Java类。 仅Kotlin的课程。 没有java。

顺便说一下,因为main是因为我们没有使用java.io.File

好吧,那可以用什么呢? 来自C的函数!

 var modified = file.modified() while (true) { if (file.modified() > modified) { println("\nFile copied: ${file.copyAside()}") modified = file.modified() } print(".") sleep(interval) } 

欢迎来到世界C


现在我们知道了等待的内容,让我们从File exists()函数的外观:

 data class File(private val filename: String) { fun exists(): Boolean { return access(filename, F_OK) != -1 } // More functions... } 

File是一个简单的data class ,它使我们从框中实现toString()的实现,稍后我们将使用它。

“幕后”,我们调用access()函数C,如果这样的文件不存在,它将返回-1

在列表的更下方,我们有modified()函数:

 fun modified(): Long = memScoped { val result = alloc<stat>() stat(filename, result.ptr) result.st_mtimespec.tv_sec } 

可以使用类型推断来简化该函数,但是在这里我决定不执行此操作,因此很明显该函数不会返回,例如Boolean

此功能有两个有趣的细节。 首先,我们使用alloc() 。 由于我们使用C,因此有时我们需要分配结构,这是使用malloc()在C中手动完成的。

这些结构也必须手动释放。 来自Kotlin Native的memScoped()函数可以为您提供帮助。

我们仍然需要考虑最重要的功能: opyAside()

 fun copyAside(): String { val state = copyfile_state_alloc() val copied = generateFilename() if (copyfile(filename, copied, state, COPYFILE_DATA) < 0) { println("Unable to copy file $filename -> $copied") } copyfile_state_free(state) return copied } 

在这里,我们使用C函数copyfile_state_alloc()来选择copyfile()所需的结构。

但是我们必须自己释放此结构-使用
copyfile_state_free(state)

剩下要显示的是名称的生成。 只有一点Kotlin:

 private var count = 0 private val extension = filename.substringAfterLast(".") private fun generateFilename() = filename.replace(extension, "${++count}.$extension") 

这是一个非常幼稚的代码,它忽略了许多情况,但仅作为示例。

开始


现在如何运行这一切?

一种选择当然是使用CLion。 他会为我们做一切。

但是,让我们使用命令行来更好地理解该过程。 是的,某些CI不会从CLion运行我们的代码。

 ./gradlew build && ./build/konan/bin/macos_x64/file_watcher.kexe ./README.md 1 

首先,我们使用Gradle编译项目。 如果一切顺利,将出现以下消息:

 BUILD SUCCESSFUL in 16s 

十六秒?! 是的,与JVM的某些Go甚至Kotlin相比,结果令人失望。 这仍然是一个很小的项目。

现在,您应该在屏幕上看到圆点。 并且,如果您更改文件的内容,将出现一条消息。 如下图所示:

 ................................ File copied: ./README.1.md ................... File copied: ./README.2.md 

发射时间很难测量。 但是我们可以使用例如活动监视器:8​​52KB来检查我们的进程占用了多少内存。 还不错!

一些结论


因此,我们发现在Kotlin Native的帮助下,我们可以获得的单个可执行文件的内存占用少于同一Go的内存。 胜利的 不完全是

如何测试这一切? 那些使用Go或Kotlin'om进行工作的人都知道,两种语言对于这项重要任务都有很好的解决方案。 到目前为止,Kotlin Native对此处理不佳。

看来在2017年,JetBrains试图解决这个问题 。 但是考虑到甚至Kotlin Native官方示例都没有测试,显然还不太成功。

另一个问题是跨平台开发。 那些使用比我大的C语言工作的人可能已经注意到,我的示例可以在OSX上运行,但不能在Windows上运行,因为我依赖于仅platform.darwin可用的几个函数。 我希望将来Kotlin Native会有更多的包装器,使它可以从平台中抽象出来,例如,在使用文件系统时。

您可以在此处找到所有代码示例

以及指向我的原始文章链接 (如果您喜欢英语阅读)

Source: https://habr.com/ru/post/zh-CN435220/


All Articles