Kotlin IO 新特性

作者: 周小安live | 来源:发表于2018-06-03 16:28 被阅读0次

    一、终端IO

    println("text")
    val line = readLine()
    

    kotlin 扩展了一下方法,使得在每个地方都可以直接,简洁地调用到这些方法。

    @kotlin.internal.InlineOnly
    public inline fun println(message: Any?) {
        System.out.println(message)
    }
    
    public fun readLine(): String? = stdin.readLine()
    

    stdin 是一个BufferedReader,其修饰了System.in 然后使用BufferedReader的readLine方法

    二、文件IO

    1、可以通过file直接拿到流

    file.inputStream()
    file.outputStream()
    
    file.bufferedReader()
    file.bufferedWriter()
    

    可以大概看一下其中的bufferedReader() 的实现。

    public inline fun File.bufferedReader(charset: Charset = Charsets.UTF_8, bufferSize: Int = DEFAULT_BUFFER_SIZE): BufferedReader = reader(charset).buffered(bufferSize)
    
    public inline fun File.reader(charset: Charset = Charsets.UTF_8): InputStreamReader = inputStream().reader(charset)
    
    // 创建inputStream
    public inline fun File.inputStream(): FileInputStream {
        return FileInputStream(this)
    }
    
    // 创建inputStreamReader
    public inline fun InputStream.reader(charset: Charset = Charsets.UTF_8): InputStreamReader = InputStreamReader(this, charset)
        
    // 创建BufferedReader
    public inline fun Reader.buffered(bufferSize: Int = DEFAULT_BUFFER_SIZE): BufferedReader
            = if (this is BufferedReader) this else BufferedReader(this, bufferSize)
    

    java的时候都是要一个流一个流地装饰,现在kotlin扩展了一些方法,使得我们可以简单快捷地拿到装饰好的流。
    类似的流也有一些扩展:

    val bufferedInputStream = inputstream.buffered()
    val bufferedReader = inputstream.bufferedReader()
    val byteArrayInputStream = string.byteInputStream()
    val inputStream = bytes.inputStream()
    

    这些常见的转换可以使我们很方便的使用。

    2、通过file直接写入文件

    file.readBytes()
    file.writeBytes(kotlin.ByteArray(1024))
    
    file.readText()
    file.writeText("")
    

    可以通过file对象直接将数据写入到文件中。
    以readText为例子:

    public fun File.readText(charset: Charset = Charsets.UTF_8): String = readBytes().toString(charset)
    // 创建一个FileInputSream 并将数据读取出来
    public fun File.readBytes(): ByteArray = FileInputStream(this).use { input ->
        var offset = 0
        var remaining = this.length().let {
            if (it > Int.MAX_VALUE) throw OutOfMemoryError("File $this is too big ($it bytes) to fit in memory.") else it
        }.toInt()
        val result = ByteArray(remaining)
        while (remaining > 0) {
            val read = input.read(result, offset, remaining)
            if (read < 0) break
            remaining -= read
            offset += read
        }
        if (remaining == 0) result else result.copyOf(offset)
    }
    
    // 上面使用到了一个use方法,这个方法可以帮处理关闭流的逻辑,  如果出现异常的话也会往上一层抛出
    @InlineOnly
    public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
        var closed = false
        try {
            return block(this)
        } catch (e: Exception) {
            closed = true
            try {
                this?.close()
            } catch (closeException: Exception) {
            }
            throw e
        } finally {
            if (!closed) {
                this?.close()
            }
        }
    }
    

    另外还有append方法

    file.appendText("sss", UTF_8)
    file.appendBytes(kotlin.ByteArray(1024))
    

    3、遍历获取行

    file.forEachLine { it }
    file.useLines { lines -> {} }
    
    public fun File.forEachLine(charset: Charset = Charsets.UTF_8, action: (line: String) -> Unit): Unit {
        // Note: close is called at forEachLine
        BufferedReader(InputStreamReader(FileInputStream(this), charset)).forEachLine(action)
    }
    // 遍历BufferedReader, 读取到的每一行
    public fun Reader.forEachLine(action: (String) -> Unit): Unit = useLines { it.forEach(action) }
    
    // 创建BufferedReader, 读取所有行
    public inline fun <T> Reader.useLines(block: (Sequence<String>) -> T): T =
            buffered().use { block(it.lineSequence()) }
    
    public inline fun Reader.buffered(bufferSize: Int = DEFAULT_BUFFER_SIZE): BufferedReader
            = if (this is BufferedReader) this else BufferedReader(this, bufferSize)
    // 返回一个只能消费一次的Sequence
    public fun <T> Sequence<T>.constrainOnce(): Sequence<T> {
        return if (this is ConstrainedOnceSequence<T>) this else ConstrainedOnceSequence(this)
    }
    
    @kotlin.jvm.JvmVersion
    private class ConstrainedOnceSequence<T>(sequence: Sequence<T>) : Sequence<T> {
        private val sequenceRef = java.util.concurrent.atomic.AtomicReference(sequence)
    
        override fun iterator(): Iterator<T> {
            val sequence = sequenceRef.getAndSet(null) ?: throw IllegalStateException("This sequence can be consumed only once.")
            return sequence.iterator()
        }
    }
    

    三、文件遍历

    file.walk()
        .maxDepth(3)
        .onEnter {
            it.name != "break"
        }.onLeave {  }
        .onFail({ _: File, _: IOException -> {}})
        .filter { it.isFile }
        .forEach {
            println(it.name)
        }
        file.walkTopDown()
        file.walkBottomUp()
    

    kotlin 扩展了一个FileTreeWalk.kt 的方法,其中主要扩展了先根遍历,和后根遍历的方法,使得我们使用起来很方便。
    其中还提供了onEnter,onLeave,onError方法,这些方法都是针对文件夹而言。
    onEnter,第一次遍历到该节点,可以返回true代表可以继续遍历其子文件,返回false则直接跳过该文件夹
    onLeave,当遍历完该文件中的所有子项的时候调用
    onError,目前只有没有权限访问的错误

    看看代码:

    public class FileTreeWalk private constructor(
            private val start: File,
            private val direction: FileWalkDirection = FileWalkDirection.TOP_DOWN,
            private val onEnter: ((File) -> Boolean)?,
            private val onLeave: ((File) -> Unit)?,
            private val onFail: ((f: File, e: IOException) -> Unit)?,
            private val maxDepth: Int = Int.MAX_VALUE
    ) : Sequence<File> {
    

    这个对象实现了Sequence,所以其拥有filter,foreach方法

    public fun File.walkTopDown(): FileTreeWalk = walk(FileWalkDirection.TOP_DOWN)
        
    // 创建FileTreeWalk对象
    public fun File.walk(direction: FileWalkDirection = FileWalkDirection.TOP_DOWN): FileTreeWalk =
            FileTreeWalk(this, direction)
        
    // 修改并返回新对象, 其中onEnter,onLeave,onFail是一样的。
    public fun maxDepth(depth: Int): FileTreeWalk {
            if (depth <= 0)
                throw IllegalArgumentException("depth must be positive, but was $depth.")
            return FileTreeWalk(start, direction, onEnter, onLeave, onFail, depth)
        }
    
    // 该类中实现了iterator接口,然后这里实现了先根遍历和后根遍历的两种算法
    private abstract class WalkState(val root: File) {
            /** Call of this function proceeds to a next file for visiting and returns it */
            abstract public fun step(): File?
        }
    private inner class TopDownDirectoryState(rootDir: File)
    private inner class BottomUpDirectoryState(rootDir: File) 
    

    四、文件拷贝,删除

    1、拷贝

    inputStream扩展了拷贝方法,从一个流写到另一个流

    public fun InputStream.copyTo(out: OutputStream, bufferSize: Int = DEFAULT_BUFFER_SIZE): Long 
    

    文件扩展了copy方法,其中调用的是inputStream的copy方法

    public fun File.copyTo(target: File, overwrite: Boolean = false, bufferSize: Int = DEFAULT_BUFFER_SIZE): File 
    

    文件扩展了copyRecursicely方法,支持拷贝文件夹,其中用到了文件遍历,以及File.copyTo方法

    public fun File.copyRecursively(target: File,
                                    overwrite: Boolean = false,
                                    onError: (File, IOException) -> OnErrorAction =
                                             { _, exception -> throw exception }
    ): Boolean 
    

    2、文件夹以及子文件的删除操作

    public fun File.deleteRecursively(): Boolean = walkBottomUp().fold(true, { res, it -> (it.delete() || !it.exists()) && res })
    

    其中fold 方法会遍历所有文件,然后调用operation方法。 删除操作的时候初始值为true,如果中间有一个删除失败的情况的话则最终返回值为false

    public inline fun <T, R> Sequence<T>.fold(initial: R, operation: (acc: R, T) -> R): R {
        var accumulator = initial
        for (element in this) accumulator = operation(accumulator, element)
        return accumulator
    }
    

    五、网络IO

    如果我们需要将一张图片下载到本地的文件中的话可以通过下面简短的代码来实现

    val imageUrl = "http://...."
    File("path").writeBytes(URL(imageUrl).readBytes())
    

    其中可以分为两个部分,一个是网络读取,另外一个是文件写入。

    1、网络读取

    URL(imageUrl).readBytes()
    
    // 其实也就是调用了URL的openConnection,然后根据得到的connection拿到流,再读取数据。
    public fun URL.readBytes(): ByteArray = openStream().use { it.readBytes() }
    
    public final InputStream openStream() throws java.io.IOException {
            return openConnection().getInputStream();
        }
    

    这为我们节省了许多代码量。

    2、文件写入

    File("path").writeBytes()
    

    这部分逻辑在上面已经看到了,其就是将这个file包装成为一个bufferWrite,然后写入字节流

    六、总结

    上面大致列举了一下kotlin io包中的一些扩展函数,以及这些扩展函数给我们开发过程中带来了很大的方便,更多好用的扩展可以查看一下源码或者是相关api。

    相关文章

      网友评论

        本文标题:Kotlin IO 新特性

        本文链接:https://www.haomeiwen.com/subject/geijsftx.html