当您编写命令行实用程序时,您最后要依赖的是将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) {
通常,命令行实用程序还具有可选参数及其默认值。 但是例如,我们将假定始终有两个参数:
path
和
interval
对于那些已经使用过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
是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 }
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
发射时间很难测量。 但是我们可以使用例如活动监视器:852KB来检查我们的进程占用了多少内存。 还不错!
一些结论
因此,我们发现在Kotlin Native的帮助下,我们可以获得的单个可执行文件的内存占用少于同一Go的内存。 胜利的 不完全是
如何测试这一切? 那些使用Go或Kotlin'om进行工作的人都知道,两种语言对于这项重要任务都有很好的解决方案。 到目前为止,Kotlin Native对此处理不佳。
看来在
2017年,JetBrains试图解决这个问题 。 但是考虑到甚至
Kotlin Native官方示例都没有测试,显然还不太成功。
另一个问题是跨平台开发。 那些使用比我大的C语言工作的人可能已经注意到,我的示例可以在OSX上运行,但不能在Windows上运行,因为我依赖于仅
platform.darwin
可用的几个函数。 我希望将来Kotlin Native会有更多的包装器,使它可以从平台中抽象出来,例如,在使用文件系统时。
您可以
在此处找到所有代码示例
。以及
指向我的原始文章的
链接 (如果您喜欢英语阅读)