【疯狂Android之Kotlin】 Kotlin中IO操作与多线程

3,847 阅读9分钟

前沿

  • 既然使用Kotlin作为Android开发的主力编程语言,在使用 Groovy 的文件 IO 操作的时候,感觉非常便利。同样的Kotlin也有好用的文件 IO 操作的 API。同样的在 Kotlin 中对 Java 的正则表达式功能做了一些实用的扩展。还有 Kotlin 中的多线程主要也是对 Java 的多线程 API 作了一些封装。因为这些 Java 已经有了很多的基础 API,Kotlin 并没有自己再去重复实现,而是在 Java 的基础上进行了实用的功能扩展。
  • 下面来看看再Kotlin中是如何进行IO操作与多线程的,简单来一起学习下吧

Kotlin IO

终端IO

  • 在Kotlin里面很简单,只需要使用println或者print这两个全局函数即可,我们不再需要冗长的前缀。当然如果我们很怀旧,就是想用 System.out.println() ,Kotlin 依然支持直接这么使用(与 Java 无缝互操作)
System.out.println("K")
K
println("K")
K
  • 可以看下print函数实现如下
@kotlin.internal.InlineOnly
publicinline fun println(message:Any?){
System.out.println(message)
}

从终端读取数据也很简单,最基本的方法就是全局函数readLine,它直接从终端读取一行作为字符串。如果需要更进一步的处理,可以使用Kotlin提供的各种字符串处理函数来处理和转换字符串,可以查看kotlin的console源码

文件IO操作

  1. Kotlin为java.io.File提供了大量好用的扩展函数,这些扩展函数主要在下面三个源文件中:
  • kotlin/io/files/FileTreeWalk.kt
  • kotlin/io/files/Utils.kt
  • kotlin/io/FileReadWrite.kt
  1. 同时,Kotlin 也针对InputStream、OutputStream和 Reader 等都做了简单的扩展。它们主要在下面的两个源文件中:
  • kotlin/io/IOStreams.kt
  • kotlin/io/ReadWrite.kt

读文件

  1. 如果简单读取一个文件,可以使用readText()方法,它直接返回整个文件内容
/**
 * 获取文件全部内容字符串
 * @param filename
 */
fun getFileContent(filename: String): String {
    val f = File(filename)
    return f.readText(Charset.forName("UTF-8"))
}

直接使用 File 对象来调用 readText 函数即可获得该文件的全部内容,它返回一个字符串。如果指定字符编码,可以通过传入参数Charset来指定,默认是UTF-8编码, 如果我们想要获得文件每行的内容,可以简单通过split("\n")来获得一个每行内容的数组。

  • 获取文件每行的内容 直接调用 Kotlin 封装好的readLines函数,获得文件每行的内容。readLines函数返回一个持有每行内容的 List。
/**
 * 获取文件每一行内容,存入一个 List 中
 * @param filename
 */
fun getFileLines(filename:String):List<String>{
returnFile(filename).readLines(Charset.forName("UTF-8"))
}
  • 直接操作文件的字节数组,可以使用readBytes()。如果想使用传统的Java方式,在Kotlin 中你也可以像 Groovy 一样自如使用
//读取为bytes数组
val bytes:ByteArray= f.readBytes()
println(bytes.joinToString(separator =" "))
//直接像 Java 中的那样处理Reader或InputStream
val reader:Reader= f.reader()
val inputStream:InputStream= f.inputStream()
val bufferedReader:BufferedReader= f.bufferedReader()

写文件

读文件类似,写入文件也很简单。我们可以写入字符串,也可以写入字节流。还可以直接使用Java的 Writer 或者 OutputStream

  • 覆盖写文件
fun writeFile(text:String, destFile:String){
    val f =File(destFile)
if(!f.exists()){
        f.createNewFile()
}
    f.writeText(text,Charset.defaultCharset())
}
  • 未尾追加写文件
fun appendFile(text:String, destFile:String){
    val f =File(destFile)
if(!f.exists()){
        f.createNewFile()
}
    f.appendText(text,Charset.defaultCharset())
}

遍历文件树

和Groovy一样,Kotlin也提供了方便的功能来遍历文件树。遍历文件树需要调用扩展方法walk(),它会返回一个FileTreeWalk对象,它有一些方法用于设置遍历方向和深度。

详情参见FileTreeWalk API 文档说明

  • 遍历了指定文件夹下的所有文件
fun traverseFileTree(filename: String) {
    val f = File(filename)
    val fileTreeWalk = f.walk()
    fileTreeWalk.iterator().forEach { println(it.absolutePath) }
}
  • 遍历当前文件下面所有子目录文件,存入一个 Iterator 中
fun getFileIterator(filename: String): Iterator<File> {
    val f = File(filename)
    val fileTreeWalk = f.walk()
    return fileTreeWalk.iterator()
}
  • 遍历当前文件下面所有子目录文件,还可以根据条件过滤,并把结果存入一个 Sequence 中
fun getFileSequenceBy(filename: String, p: (File) -> Boolean): Sequence<File> {
    val f = File(filename)
    return f.walk().filter(p)
}

网络IO操作

Kotlin为java.net.URL增加了两个扩展方法,readBytes和readText。我们可以方便的使用这两个方法配合正则表达式实现网络爬虫的功能。 下面我们简单写几个函数实例

  • 根据 url 获取该 url 的响应 HTML函数
fun getUrlContent(url: String): String {
    return URL(url).readText(Charset.defaultCharset())
}
  • 根据 url 获取该 url 响应比特数组函数
fun getUrlBytes(url: String): ByteArray {
    return URL(url).readBytes()
}
  • 把 url 响应字节数组写入文件
fun writeUrlBytesTo(filename: String, url: String) {
    val bytes = URL(url).readBytes()
    File(filename).writeBytes(bytes)
}

kotlin.io标准库

Kotlin 的 io 库主要是扩展 Java 的 io 库。下面我们简单举几个例子

  • appendBytes 追加字节数组到该文件,方法签名
fun File.appendBytes(array: ByteArray)
  • appenText 追加文本到该文件
fun File.appendText(
    text: String, 
    charset: Charset = Charsets.UTF_8)
  • bufferedReader 获取该文件的BufferedReader
fun File.bufferedReader(
    charset: Charset = Charsets.UTF_8, 
    bufferSize: Int = DEFAULT_BUFFER_SIZE
): BufferedReader
```#### `bufferedWriter`
获取该文件的BufferedWriter
方法签名:
```kotlin
fun File.bufferedWriter(
    charset: Charset = Charsets.UTF_8, 
    bufferSize: Int = DEFAULT_BUFFER_SIZE
): BufferedWriter
  • copyRecursively 复制该文件或者递归复制该目录及其所有子文件到指定路径,如果指定路径下的文件不存在,会自动创建
fun File.copyRecursively(
    target: File, 
    overwrite: Boolean = false, // 是否覆盖。true:覆盖之前先删除原来的文件
    onError: (File, IOException) -> OnErrorAction = { _, exception -> throw exception }
): Boolean

执行Shell命令行

  1. 下面来看看,使用Groovy的文件IO操作
package com.easy.kotlin
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@RunWith(JUnit4)
class ShellExecuteDemoTest {
    @Test
    def void testShellExecute() {
        def p = "ls -R".execute()
        def output = p.inputStream.text
        println(output)
        def fname = "我图.url"
        def f = new File(fname)
        def lines = f.readLines()
        lines.forEach({
            println(it)
        })
        println(f.text)
    }
}
  • 我们看到使用 Groovy 执行终端命令
def p = "ls -R".execute()
def output = p.inputStream.text
  1. 在 Kotlin 中,目前还没有对 String 类和 Process 扩展这样的函数。其实扩展这样的函数非常简单。我们完全可以自己扩展
  • 首先,扩展 String 的 execute() 函数
fun String.execute(): Process {
    val runtime = Runtime.getRuntime()
    return runtime.exec(this)
}
  • 接着,我们来给Process类扩展text函数
fun Process.text(): String {
    var output = ""
    //    输出 Shell 执行的结果
    val inputStream = this.inputStream
    val isr = InputStreamReader(inputStream)
    val reader = BufferedReader(isr)
    var line: String? = ""
    while (line != null) {
        line = reader.readLine()
        output += line + "\n"
    }
    return output
}
  • 完成了上面两个简单的扩展函数之后,我们就可以在下面的测试代码中,可以像Groovy一样执行终端命令了
val p = "ls -al".execute()
val exitCode = p.waitFor()
val text = p.text()
println(exitCode)
println(text)

正则表达式

构造Regex表达式

在 Kotlin 中除了仍然可以使用 Java中的 Pattern,Matcher 等类之外,Kotlin 还提供了一个正则表达式类Regex.kt ,我们通过 Regex 的构造函数来创建一个正则表达式

  • 使用Regex构造函数 匹配选项 RegexOption 是直接使用的 Java 类 Pattern中的正则匹配选项
val r1 = Regex("[a-z]+")
val r2 = Regex("[a-z]+", RegexOption.IGNORE_CASE)
  • 使用String的toRegex扩展函数
val r3 = "[A-Z]+".toRegex()

Regex函数

Regex 里面提供了丰富的简单而实用的函数,如下表所示

函数名称功能说明
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的序列

下面分别举些例子

  • 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
  • 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

Koltin 多线程

  1. Kotlin中没有synchronized关键字。
  2. Kotlin中没有volatile关键字。
  3. Kotlin的Any类似于Java的Object,但是没有wait(),notify()和notifyAll() 方法。
  4. 那么并发如何在Kotlin中工作呢?对于这样,Kotlin既然是站在 Java 的肩膀上,当然少不了对多线程编程的支持——Kotlin通过封装 Java 中的线程类,简化了我们的编码。同时我们也可以使用一些特定的注解,直接使用 Java 中的同步关键字等。下面我们简单介绍一下使用Kotlin 进行多线程编程的相关内容。

创建线程

  1. 我们通常创建线程有两种方法:
  • 扩展Thread类
  • 实例化它并通过构造函数传递一个Runnable

使用对象表达式创建

使用Kotlin的对象表达式创建一个匿名类并覆盖run()方法

object : Thread() {
    override fun run() {
        Thread.sleep(3000)
        println("A 使用 Thread 对象表达式: ${Thread.currentThread()}")
    }
}.start()

使用Lambda表达式

将一个Runnable传递给一个新创建的Thread实例:

Thread({
    Thread.sleep(2000)
    println("B 使用 Lambda 表达式: ${Thread.currentThread()}")
}).start()

使用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,简化了样板代码。

同步方法和块

  • synchronized不是Kotlin中的关键字,它替换为@Synchronized 注解。 Kotlin中的同步方法的声明将如下所示
@Synchronized fun appendFile(text:String, destFile:String){
    val f =File(destFile)
if(!f.exists()){
        f.createNewFile()
}
    f.appendText(text,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())
}
}

可变字段

  • Kotlin没有 volatile 关键字,但是有@Volatile注解,@Volatile会将JVM备份字段标记为volatile
@Volatileprivatevar running =false
fun start(){
    running =true
    thread(start =true){
while(running){
            println("Still running: ${Thread.currentThread()}")
}
}
}
fun stop(){
    running =false
    println("Stopped: ${Thread.currentThread()}")
}

结语

  • 这也从侧面反应了Kotlin是一门实践性很强的语言,通过学习文件IO、正则表达式以及多线程等内容中,我们可以领会到 Kotlin 的基本原则:
    • 充分使用已有的 Java 生态库,在此基础之上进行更加简单实用的扩展,大大提升程序员们的生产力。
    • 从中我们也体会到了Kotlin 编程中的极简理念——不断地抽象、封装、扩展,使之更加简单实用。
  • 通过整理帮助我更加系统的学习Kotlin,未来的路还很长,持续成长中。。。