第9章 文件IO操作、正则表达式与多线程
第9章 文件IO操作、正则表达式与多线程我们在《第6章 扩展函数与属性》中已经介绍过Kotlin中的类扩展的特性。使用Kotlin的扩展函数功能,我们可以直接为 String 类实现一个 inc() 函数,这个函数把字符串中的每一个字符值加1 "abc".inc() // bcd 这个扩展函数实现如下 fun String.inc(): String { var result = "" this.map { result += it + 1 } return result } 正是因为有了强大的扩展函数,我们可以在Java类库的基础上扩展出大量“看似Java 类中的原生方法” 。而实际上Kotlin的标准库kotlin-stdlib中大量的API都是通过扩展Java的类来实现的。 本章我们将要介绍的文件IO操作、正则表达式与多线程等相关内容都是Kotlin通过扩展Java已有的类来实现的。首先,我们来介绍文件的读写。 9.1 文件 IO 操作Kotlin IO 操作的 API 在 kotlin.io 包下。Kotlin的原则就是Java已经有好用的就直接使用,没有的或者不好用的,就在原有类的基础上进行功能扩展。例如Kotlin 就给 File 类写了扩展函数。 Kotlin为 java.io.File 类扩展了大量好用的扩展函数,这些扩展函数都在 kotlin/io/FileReadWrite.kt 源代码文件中。我们将在下文中介绍。 同时,Kotlin 也针对InputStream、OutputStream和 Reader 等都做了简单的扩展。它们主要在下面的两个源文件中:
Koltin 的序列化直接采用的 Java 的序列化类的类型别名: internal typealias Serializable = java.io.Serializable 下面我们来简单介绍一下 Kotlin 文件读写操作。Kotlin中常用的文件读写 API 如下表所示
9.1.1 读文件readText : 获取文件全部内容字符串我们如果简单读取一个文件,可以使用readText()方法,它直接返回整个文件内容。代码示例如下 fun getFileContent(filename: String): String { val f = File(filename) return f.readText(Charset.forName("UTF-8")) } 我们直接使用 File 对象来调用 readText 函数即可获得该文件的全部内容,它返回一个字符串。如果指定字符编码,可以通过传入参数Charset来指定,默认是UTF-8编码。 readLines : 获取文件每行的内容如果我们想要获得文件每行的内容,可以简单通过 split("n") 来获得一个每行内容的数组。我们也可以直接调用 Kotlin 封装好的readLines函数,获得文件每行的内容。readLines函数返回一个持有每行内容的字符串 List。 fun getFileLines(filename: String): List<String> { return File(filename).readLines(Charset.forName("UTF-8")) } readBytes:读取字节流数组我们如果希望直接操作文件的字节数组,可以使用readBytes 函数 //读取为bytes数组 val bytes: ByteArray = f.readBytes() println(bytes.joinToString(separator = " ")) //与 Java 互操作,直接调用Java 中的 InputStream 和 InputStream类 val reader: Reader = f.reader() val inputStream: InputStream = f.inputStream() val bufferedReader: BufferedReader = f.bufferedReader() }
|
函数名称 | 功能说明 |
---|---|
matches(input: CharSequence): Boolean | 输入字符串全部匹配 |
containsMatchIn(input: CharSequence): Boolean | 输入字符串至少有一个匹配 |
matchEntire(input: CharSequence): MatchResult? | 输入字符串全部匹配,返回一个匹配结果对象 |
replace(input: CharSequence,replacement: String): String | 把输入字符串中匹配的部分替换成replacement的内容 |
replace(input: CharSequence,transform: (MatchResult) -> CharSequence): String | 把输入字符串中匹配到的值,用函数 transform映射之后的新值替换 |
find(input: CharSequence,startIndex: Int = 0): MatchResult? | 返回输入字符串中第一个匹配的值 |
findAll(input: CharSequence,startIndex: Int = 0): Sequence<MatchResult> | 返回输入字符串中所有匹配的值MatchResult的序列 |
下面我们分别就上面的函数给出简单实例。
matches
输入字符串全部匹配正则表达式返回 true,否则返回 false。
>>> val r1 = Regex("[a-z]+") >>> r1.matches("ABCzxc") false >>> val r2 = Regex("[a-z]+",RegexOption.IGNORE_CASE) >>> r2.matches("ABCzxc") true >>> val r3 = "[A-Z]+".toRegex() >>> r3.matches("GGMM") true
containsMatchIn
输入字符串中至少有一个匹配就返回true,没有一个匹配就返回false。
>>> val re = Regex("[0-9]+") >>> re.containsMatchIn("012Abc") true >>> re.containsMatchIn("Abc") false
matchEntire
输入字符串全部匹配正则表达式返回 一个MatcherMatchResult对象,否则返回 null。
>>> val re = Regex("[0-9]+") >>> re.matchEntire("1234567890") kotlin.text.MatcherMatchResult@34d713a2 >>> re.matchEntire("1234567890!") null
我们可以访问MatcherMatchResult的value熟悉来获得匹配的值。
>>> re.matchEntire("1234567890")?.value 1234567890
由于 matchEntire 函数的返回是MatchResult? 可空对象,所以这里我们使用了安全调用符号 ?.
。
replace(input: CharSequence,replacement: String): String
把输入字符串中匹配的部分替换成replacement的内容。
>>> val re = Regex("[0-9]+") >>> re.replace("12345XYZ","abcd") abcdXYZ
我们可以看到,"12345XYZ"中12345
是匹配正则表达式 [0-9]+
的内容,它被替换成了 abcd
。
replace
函数
replace 函数签名如下
replace(input: CharSequence,transform: (MatchResult) -> CharSequence): String
它的功能是把输入字符串中匹配到的值,用函数 transform映射之后的新值替换。
>>> val re = Regex("[0-9]+") >>> re.replace("9XYZ8",{ (it.value.toInt() * it.value.toInt()).toString() }) 81XYZ64
我们可以看到,9XYZ8
中数字9和8是匹配正则表达式[0-9]+
的内容,它们分别被transform函数映射 (it.value.toInt() * it.value.toInt()).toString()
的新值 81 和 64 替换。
find
函数
返回输入字符串中第一个匹配的MatcherMatchResult对象。
>>> val re = Regex("[0-9]+") >>> re.find("123XYZ987abcd7777") kotlin.text.MatcherMatchResult@4d4436d0 >>> re.find("123XYZ987abcd7777")?.value 123
findAll
返回输入字符串中所有匹配的值的MatchResult的序列。
>>> val re = Regex("[0-9]+") >>> re.findAll("123XYZ987abcd7777") kotlin.sequences.GeneratorSequence@f245bdd
我们可以通过 forEach 循环遍历所以匹配的值
>>> re.findAll("123XYZ987abcd7777").forEach{println(it.value)} 123 987 7777
9.4.3 使用 Java 的正则表达式类
除了上面 Kotlin 提供的函数之外,我们在 Kotlin 中仍然可以使用 Java 的正则表达式的 API。
val re = Regex("[0-9]+") val p = re.toPattern() val m = p.matcher("888ABC999") while (m.find()) { val d = m.group() println(d) }
上面的代码运行输出:
888 999
9.5 多线程编程
Kotlin中没有synchronized、volatile关键字。Kotlin的Any类似于Java的Object,但是没有wait(),notify()和notifyAll() 方法。
那么并发如何在Kotlin中工作呢?放心,Kotlin 既然是站在 Java 的肩膀上,当然少不了对多线程编程的支持——Kotlin通过封装 Java 中的线程类,简化了我们的编码。同时我们也可以使用一些特定的注解, 直接使用 Java 中的同步关键字等。下面我们简单介绍一下使用Kotlin 进行多线程编程的相关内容。
9.5.1 创建线程
我们在 Java中通常有两种方法在Java中创建线程:
- 扩展Thread类
- 或者实例化它并通过构造函数传递一个Runnable
因为我们可以很容易地在Kotlin中使用Java类,这两个方式都可以使用。
使用对象表达式创建
object : Thread() { override fun run() { Thread.sleep(3000) println("A 使用 Thread 对象表达式: ${Thread.currentThread()}") } }.start()
此代码使用Kotlin的对象表达式创建一个匿名类并覆盖run()方法。
使用 Lambda 表达式
下面是如何将一个Runnable传递给一个新创建的Thread实例:
Thread({ Thread.sleep(2000) println("B 使用 Lambda 表达式: ${Thread.currentThread()}") }).start()
我们在这里看不到Runnable,在Kotlin中可以很方便的直接使用上面的Lambda表达式来表达。
还有更简单的方法吗? 且看下文解说。
使用 Kotlin 封装的 thread 函数
例如,我们写了下面一段线程的代码
val t = Thread({ Thread.sleep(2000) println("C 使用 Lambda 表达式:${Thread.currentThread()}") }) t.isDaemon = false t.name = "CThread" t.priority = 3 t.start()
后面的四行可以说是样板化的代码。在 Kotlin 中把这样的操作封装简化了。
thread(start = true,isDaemon = false,name = "DThread",priority = 3) { Thread.sleep(1000) println("D 使用 Kotlin 封装的函数 thread(): ${Thread.currentThread()}") }
这样的代码显得更加精简整洁了。事实上,thread()函数就是对我们编程实践中经常用到的样板化的代码进行了抽象封装,它的实现如下:
public fun thread(start: Boolean = true,isDaemon: Boolean = false,contextClassLoader: ClassLoader? = null,name: String? = null,priority: Int = -1,block: () -> Unit): Thread { val thread = object : Thread() { public override fun run() { block() } } if (isDaemon) thread.isDaemon = true if (priority > 0) thread.priority = priority if (name != null) thread.name = name if (contextClassLoader != null) thread.contextClassLoader = contextClassLoader if (start) thread.start() return thread }
这只是一个非常方便的包装函数,简单实用。从上面的例子我们可以看出,Kotlin 通过扩展 Java 的线程 API,简化了样板代码。
9.5.2 同步方法和块
synchronized不是Kotlin中的关键字,它替换为@Synchronized 注解。 Kotlin中的同步方法的声明将如下所示:
@Synchronized fun appendFile(text: String,Charset.defaultCharset()) }
@Synchronized 注解与 Java中的 synchronized 具有相同的效果:它会将JVM方法标记为同步。 对于同步块,我们使用synchronized() 函数,它使用锁作为参数:
fun appendFileSync(text: String,destFile: String) { val f = File(destFile) if (!f.exists()) { f.createNewFile() } synchronized(this){ f.appendText(text,Charset.defaultCharset()) } }
跟 Java 基本一样。
9.5.3 可变字段
同样的,Kotlin没有 volatile 关键字,但是有@Volatile注解。
@Volatile private var running = false fun start() { running = true thread(start = true) { while (running) { println("Still running: ${Thread.currentThread()}") } } } fun stop() { running = false println("Stopped: ${Thread.currentThread()}") }
@Volatile会将JVM备份字段标记为volatile。
当然,在 Kotlin 中我们有更好用的协程并发库。在代码工程实践中,我们可以根据实际情况自由选择。
本章小结
Kotlin 是一门工程实践性很强的语言,从本章介绍的文件IO、正则表达式以及多线程等内容中,我们可以领会到 Kotlin 的基本原则:充分使用已有的 Java 生态库,在此基础之上进行更加简单实用的扩展,大大提升程序员们的生产力。从中我们也体会到了Kotlin 编程中的极简理念——不断地抽象、封装、扩展,使之更加简单实用。
本章示例代码:https://github.com/EasyKotlin...
另外,笔者综合了本章的内容,使用 SpringBoot + Kotlin 写了一个简单的图片爬虫 Web 应用,感兴趣的读者可参考源码:https://github.com/EasyKotlin...
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!