Kotlin中的IO

博客地址sguotao.top/Kotlin-2018…

IO流是Java中很重要的一部分内容,常用的数据传输,文件的上传和下载都和它分不开。那么与Java中的IO相比,Kotlin中的IO又是怎样的呢?

Java中的IO

Java中的IO根据处理数据的方式,可以分为字节流和字符流,同时根据传输方向的不同,又可以分为输入流和输出流。先来看一张Java IO的框架图。

在这张图中,整理了在Java 8中根据上述分类的IO流,其中字节输入流有28种,字节输出流有18种,字符输入流有9种,字符输出流有8种,看到这么多的流,实际开发中经常使用到的只是其中的一部分。比如字节输入流中的FileInputStream、BufferedInputStream,字节输出流中的FileOutputStream、BufferedOutputStream,字符输入流中的BufferedReader、InputStreamReader、FileReader,字符输出流中的BufferedWriter、OutputStreamWriter、FileWriter等。在图中已用黑色框图进行了突出标注。

在Java中对流的处理,需要注意异常的处理,同时注意流的关闭操作,否则可能会引起内存溢出。比如使用BufferedReader读取项目目录下的build.gradle文件。

public static void main(String[] args) {
        BufferedReader bufferedReader = null;
        try {
            bufferedReader = new BufferedReader(new FileReader(new File("build.gradle")));
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
复制代码

Kotlin中的IO

先给出上面使用BufferedReader读取build.gradle文件的Kotlin的几种写法,然后再来总结一下Kotlin中的IO。

fun main(args: Array<String>) {
    val file = File("build.gradle")
    val bufferedReader = BufferedReader(FileReader(file))
    var line: String

    try {
        while (true) {
            line = bufferedReader.readLine() ?: break
            println(line)
        }
    } catch (e: Exception) {
        e.printStackTrace()
    } finally {
        try {
            bufferedReader.close()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}
复制代码

这种写法似乎与Java的写法没有什么区别,怎么体现Kotlin的优势呢?好在Kotlin封装了很多高阶函数,下面给出一个使用高阶函数的版本。

fun main(args: Array<String>) {
    val file = File("build.gradle")
    BufferedReader(FileReader(file)).use {
        var line: String
        while (true) {
            line = it.readLine() ?: break
            println(line)
        }
    }
}
复制代码

代码精简了不少,省略了一些对异常的捕获,这是因为这样的模板代码kotlin已经封装好了,所以再也不用担心忘记对流进行close了。对一些小文件的读取,还有更简便的写法:

fun main(args: Array<String>) {
    File("build.gradle").readLines().forEach(::println)
}
复制代码

Kotlin中关于IO的扩展函数

那么Kotlin中都提供了哪些扩展函数呢?这些扩展函数实现的效果又是怎样的?Kotlin所有与IO相关的都放在kotlin.io这个包中,可以看到Kotlin并没有对Java中已经存在的IO流进行重复实现,而是对经常用到的一些字节流,字符流进行了扩展。这里我们可以将扩展对象的不同,将这些扩展函数分成以下几类。

关于扩展函数的使用,可以将扩展函数看作是被扩展对象的成员方法,比如bufferedReader(charset: Charset)函数的被扩展对象是InputStream,那么我么就可以在InputStream及其子类上调用该方法,比如:

    val inputStream: InputStream = FileInputStream("build.gradle")
    inputStream.bufferedReader().forEachLine { line ->
        println(line)
    }
复制代码

Kotlin对字节流的扩展

Kotlin对字节流的扩展主要位于ByteStreamKt.class中,下面分别介绍一下:

序号扩展函数名被扩展对象描述
1buffered((bufferSize: Int)InputStream对字节输入流进行包装,得到一个带缓冲区的字节输入流BufferedInputStream,缓冲区默认大小为8*1024字节。
2bufferedReader(charset: Charset)InputStream对字节输入流进行包装得到一个带缓冲区的字符输入流BufferedReader,默认的字符编码是UTF-8。
3copyTo(out: OutputStream, bufferSize: Int )InputStream将字节输入流复制到给定的输出流,返回复制的字节数,缓冲区大小默认为8*1024字节,需要注意的是两个流都需要手动的close。
4readBytes(estimatedSize: Int)InputStream将字节输入流读入到一个大小不超过8*1024的字节数组中。
5reader(charset: Charset)InputStream对字节输入流进行包装得到一个字符输入流InputStreamReader,默认的字符编码是UTF-8。
6buffered(bufferSize: Int)OutputStream对字节输入流进行包装得到一个带缓冲区的字节输出流BufferedOutputStream,缓冲区的默认大小为8*1024字节。
7bufferedWriter(charset: Charset)OutputStream对字节输出流进行包装得到一个带缓冲区的字符输出流BufferedWriter,字符的默认编码是UTF-8。
8writer(charset: Charset)OutputStream对字节输出流进行包装得到一个字符输出流OutputStreamWriter,默认的字符编码是UTF-8。
9inputStream()ByteArray为给定的字节数组创建一个字节输入输入流ByteArrayInputStream,来读取该字节数组。
10inputStream(offset: Int, length: Int)ByteArray为给定的字节数组创建一个字节输入流ByteArrayInputStream,来读取该数组,其中offset是读取位置,这个位置是相对起始位置的偏移量,length是读取长度。
11byteInputStream(charset: Charset)String为给定的字符串创建一个字节输入流ByteArrayInputStream,默认按UTF-8编码。

Kotlin对字符流的扩展

Kotlin对字符流的扩展主要位于TextStreamKt.class中,我们对这些扩展函数逐个介绍:

序号扩展函数名被扩展对象描述
1buffered(bufferSize: Int)Reader对字符输入流进行包装得到一个带缓冲区的字符输入流BufferedReader,缓冲区默认大小为8*1024字节。
2copyTo(out: Writer, bufferSize: Int)Reader将字符输入流复制给一个给定的字符输出流,返回复制的字符数,缓冲区默认大小为8*1024字节。需要注意的是两个流需要手动的close。
3forEachLine(action: (String) -> Unit)Reader遍历字符输入流Reader读取的每一行,同时对每一行调用传入的函数,处理完成后会关闭流。这个传入函数带一个String类型的参数,没有返回值。
4readLines()Reader将字符输入流读取的每一行数组,存入List,读取完成后返回该List。需要注意的是不能用该函数读取比较大的文件,否则会引起内存溢出。
5readText()Reader将字符输入流读到的内容以字符串的形式返回。需要手动关闭流。
6useLines(block: (Sequence) -> T)Reader将字符输入流Reader读取的内容存储在一个字符序列中,在字符序列上执行传入的lambda表达式,处理完后后会关闭流 ,将lambda表达式的返回值作为函数的返回值。
7buffered(bufferSize: Int)Writer对字符输出流进行包装,得到一个带缓冲区的字符输出流BufferedWriter,缓冲区默认大小为8*1024字节。
8readBytes()URL将URL返回的内容读取到字节数组,字节数组默认大小为8*1024字节,需要注意不能读取大文件,否则可能会引起内存溢出。
9readText(charset: Charset)URL将URL返回的内容以字符串的形式返回,默认的字符编码是UTF-8,需要注意不能读取大文件,否则可能会引起内存溢出。
10reader()String为给定的字符串创建一个字符输入流StringReader。

Kotlin对File的扩展

Kotlin对File的扩展主要位于FileKt.class中,下面介绍一下这些扩展函数:

序号扩展函数被扩展对象描述
1appendBytes(array: ByteArray)File对文件追加指定字节数组大小的内容。
2appendText(text: String, charset: CharsetFile对文件追加指定内容,默认的字符编码为UTF-8。
3bufferedReader(charset: Charset, bufferSize: Int )File对文件进行包装,获取一个带缓冲区的字符输入流,输入流的默认编码是UTF-8,缓冲区默认大小为8*1024字节。
4bufferedWriter(charset: Charset, bufferSize: Int)File对文件进行包装,获取一个带缓冲区的字符输出流,输出流的默认编码是UTF-8,缓冲区默认大小为8*1024字节。
5copyRecursively(target: File,overwrite: Boolean, onError: (File, IOException))File递归地复制文件,该函数接收三个参数,copy文件的目的地址target,是否进行覆盖overwrite,默认值是false不覆盖,异常处理的onError,默认抛出异常。函数的返回值true 复制完成,复制过程中被中断都会返回false。如果指定的目的地址没有文件,则创建文件;如果File指向的是单个文件,则直接复制文件到target目录下;如果File指向的是目录,则递归的复制目录下所有的子目录及文件到target目录下;如果target指定的File已经存在,根据overwrite来控制是否进行覆写操作;文件的一些属性信息,如创建日期,读写权限,复制时是不进行保存的。接受一个表达式来处理异常,默认是抛出异常。需要注意的是,如果复制失败,可能会出现部分复制的情况。
6copyTo(target: File, overwrite: Boolean, bufferSize: Int )File复制文件到指定路径,如果指定路径不存在文件则创建;如果存在根据overwrite参数控制是否进行覆写;如果target指向的是一个目录,并且overwrite设置为true,则只有该目录为空时,文件才会被复制。如果File指向的是一个目录,那么调用该方法,只会创建target指定的目录,不会复制目录的内容。最后文件的一些属性信息,如创建日期,读写权限,复制时是不进行保存的。
7deleteRecursively()File递归删除文件,如果文件指向的是目录,会递归删除目录下的内容。需要注意的是,如果递归删除失败,可能会出现部分删除的情况。
8endsWith(other: File)File判断文件的路径是否以给定的文件other的路径结尾。
9endsWith(other: String)File判断文件的路径是否与给定字符串指向的路径在同一根目录下,并且文件路径以字符串结尾。
10forEachBlock(action: (buffer: ByteArray, bytesRead: Int) -> Unit)File该函数接收一个表达式,表达式有两个参数,字节数组buffer和Int型bytesRead,表达式没有返回值。函数按照字节数组(默认大小为4096字节)的长度来读取文件,每当读取一字节数组的内容,就调用一次传入的表达式。比如文件大小为7409字节,那么就会调用两次表达式,第一次是在读取4096字节时,第二次是在读取余下的3313字节。
11forEachBlock(blockSize: Int, action: (buffer: ByteArray, bytesRead: Int) -> Unit)File该函数实现的功能与第10个函数功能相同,区别是可以指定字节数组buffer的大小。
12forEachLine(charset: Charset = Charsets.UTF_8, action: (line: String) -> Unit)File该函数接收一个字符编码参数charset和一个表达式,表达式接收一个String类型参数,没有返回值。该函数实现按照指定字符编码(默认是UTF-8)按行来读取文件,每读取一行,就调用一次传入的表达式。该函数可以用来读取大文件。
13inputStream()File对文件进行包装,得到一个字节输入流InputStream。
14normalize()File移除文件路径中包含的. 并且解析..比如文件路径为File("/foo/./bar/gav/../baaz")那么调用normalize()之后的结果为File("/foo/bar/baaz")。
15outputStream()File对文件进行包装,得到一个字节输出流OutputStream。
16printWriter(charset: Charset)File对文件进行包装,得到一个字符输出流PrintWriter。默认字符编码是UTF-8 。
17readBytes()File将文件内容读取到字节数组中,并且返回该字节数组。该函数不建议读取大文件,否则会引起内存溢出。函数内部限制字节数组的大小不超过2GB。
18reader(charset: Charset)File对文件进行包装,得到一个字符输入流InputStreamReader。默认字符编码是UTF-8。
19readLines(charset: Charset)File按照指定字符编码,将文件按照行,读入到一个LIst中,并且返回该List。默认字符编码是UTF-8。该方法不建议读取大文件,可能会引起内存溢出。
20readText(charset: Charset)File按照指定字符编码,读取整个文件,并且以String的类型返回读取的内容。**该方法不建议读取大文件,可能会引起内存溢出。**函数内部限制文件大小为2GB。
21relativeTo(base: File)File计算与指定文件base的相对路径。这里的参数base被当做一个文件目录,如果文件与base的路径相同,返回一个空路径。如果文件与base文件具有不同的根路径,会抛出IllegalArgumentException。
22File.relativeToOrNull(base: File)File计算与指定文件base的相对路径,如果文件路径与base相同,返回一个空路径,如果文件路径与base具有不同的根路径,返回null。
23relativeToOrSelf(base: File)File计算与指定文件base的相对路径,如果文件路径与base相同,返回一个空路径,如果文件路径与base具有不同的根路径,返回文件自身。
24resolve(relative: File)File将指定文件relatived的路径添加到当前文件的目录中,如果relative文件有根路径,返回relative文件本身,否则返回添加后的文件路径。比如当前文件路径File("/foo/bar")调用.resolve(File("gav"))后的结果为File("/foo/bar/gav")。
25resolve(relative: String)File将指定路径relative添加到当前文件的目录中。
26resolveSibling(relative: File)File将指定文件relative的路径添加到当前文件的上一级目录中,如果relative文件有根路径,返回relative文件自身,否则返回添加之后的文件路径。比如当前文件路径File("/foo/bar")调用.resolve("gav")后的结果为File("/foo/bar/gav")。
27resolveSibling(relative: String)File将指定路径relative添加到当前文件目录的上一级目录中。
28startsWith(other: File)File判断当前文件是否与给定文件other是否有相同的根目录,并且当前文件的路径是否以指定的文件路径开头。
29startsWith(other: String)File判断当前文件是否与跟定的路径具有相同的根目录,并且当前文件的路径是否以给定的路径开头。
30toRelativeString(base: File)File计算当前文件与指定文件base的相对路径,如果当前文件路径与base路径相同,返回一个空字符串,如果当前文件与base具有不同的根路径,抛出IllegalArgumentException。
31useLines(charset: Charset = Charsets.UTF_8, block: (Sequence) -> T)File该函数接收两个参数:一个字符编码charset和一个表达式。默认字符编码是UTF-8,表达式接收一个字符序列,并且返回一个泛型,表达式的返回值作为该函数的返回值。这个函数与forEachline()函数很相似,区别是该函数返回一个字符序列,并且返回在返回字符序列时会关闭流。
32walk(direction: FileWalkDirection = FileWalkDirection.TOP_DOWN)File按照指定的顺序(top-down、bottom-up),使用深度优先遍历当前目录及目录下的内容,默认的顺序是自顶向下即top-down,得到一个文件访问序列。
33walkBottomUp()File按照自底向上的顺序遍历当前目录及目录下的内容,该函数使用深度优先遍历,得到一个文件访问序列,即先访问文件,后访问文件目录的序列。
34walkTopDown()File按照自顶向下的顺序遍历当前目录及目录下的内容,该函数使用深度优先遍历,得到一个文件访问序列,即先访问文件目录,后访问文件的序列。
35writeBytes(array: ByteArray)File将指定字节数组的内容写入文件,如果文件已经存在,则被覆写。
36writer(charset: Charset = Charsets.UTF_8)File对文件包装,得到一个字符输出流OutputStreamWriter,默认字符编码是UTF-8。
37writeText(text: String, charset: Charset = Charsets.UTF_8)File将指定字符串内容,按照默认UTF-8的编码方式,写入文件,如果文件已经存在,则会被覆写。

Kotlin其它的IO扩展

Java中IO 流就继承了Closeable接口,在kotlin.io中的CloseableKt.class中,有一个use的扩展函数:

public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
    var exception: Throwable? = null
    try {
        return block(this)
    } catch (e: Throwable) {
        exception = e
        throw e
    } finally {
        when {
            apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)
            this == null -> {}
            exception == null -> close()
            else ->
                try {
                    close()
                } catch (closeException: Throwable) {
                    // cause.addSuppressed(closeException) // ignored here
                }
        }
    }
}
复制代码

use函数封装了try...catch...finally模板代码,这就是在kotlin中,在IO流上使用use时,不用对流进行关闭的原因,因为kotlin已经对其进行了封装。

在kotlin.io中,ConsoleKt.class中封装了如System.out.print等终端IO的操作,在Kotlin中可以直接使用print、println在命令行打印输出。

学习资料

  1. Kotlin Bootcamp for Programmers
  2. Kotlin Koans

转载于:https://juejin.im/post/5be0086751882516fc40515f

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Ktor 是 Kotlin 官方提供的一个轻量级、灵活的 Web 框架,它可以用于开发 Web 应用程序、API、微服务等。在 Kotlin for Desktop ,我们可以使用 Ktor 构建和部署桌面应用程序的后端,以提供数据和服务。 下面是一个简单的 Kotlin for Desktop Ktor 的使用示例: ```kotlin import io.ktor.application.* import io.ktor.response.* import io.ktor.routing.* import io.ktor.server.engine.* import io.ktor.server.netty.* fun main() { embeddedServer(Netty, port = 8080) { routing { get("/") { call.respondText("Hello, world!") } } }.start(wait = true) } ``` 这个示例创建了一个 Ktor 应用程序,监听 8080 端口,并且在访问根路径时返回 "Hello, world!" 字符串。可以在浏览器或者其他 HTTP 客户端访问 http://localhost:8080/ 查看结果。 需要注意的是,在 Kotlin for Desktop 使用 Ktor 可能需要添加额外的依赖项,例如: ```kotlin dependencies { implementation("io.ktor:ktor-server-netty:$ktor_version") implementation("io.ktor:ktor-server-core:$ktor_version") implementation("io.ktor:ktor-server-host-common:$ktor_version") implementation("io.ktor:ktor-serialization:$ktor_version") } ``` 其 $ktor_version 是 Ktor 的版本号,可以根据需要进行修改。另外,还需要在项目的 build.gradle 或 settings.gradle 添加 Maven 仓库: ```kotlin repositories { mavenCentral() } ``` 通过使用 Ktor,我们可以很方便地在 Kotlin for Desktop 构建和部署后端服务,实现应用程序的完整功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值