Java IO 在Android中的应用

112 阅读5分钟

A77AF030-B797-4700-B7B3-6AFC88C8DCE1.jpeg

什么是IO

IO是Input 和 Output的简称,在Java中通常表示存储器之间的输入和输出。在刚接触IO相关的几个类有FileOutputSteam,FileInputStream,OutputStreamWrite,InputStreamRead...在这里可能会有概念上的混淆,从哪里输出到哪里?从哪里输入到哪里?为什么输出是write?为什么输入是read?下面就来缕清这些关系。

在android中,操作都是在内存中进行的,把内存当做出发点来看,假如是读文件,文件处于磁盘中,需要往内存中输入文件数据才可以读取,所以这时就是输入;反之,要往磁盘中写入数据,这时就是输出。

程序中的输入和输出都是以流的形式保存,所有存在流中的全部都是字节文件。关于Java中的流(Stream),分为字节流字符流。字节流的最小单位是字节,字符的最小单位是字符,字节是数据的最小单位,其中,2字节=1字符。

字节流

Java中的字节流处理的最基本单位为单个字节,它通常用来处理二进制数据。Java中最基本的两个字节流类是InputStream和OutputStream,它们分别代表了最基本的输入字节流和输出字节流。InputStream和OutputStream都是抽象类,基本都是用OutputStreamWrite和InputStreamRead在Java中操作流文件。

字符流

字符通常用来处理文本数据,例如字符、字符数组或字符串。每种数据通常有着各自的编码方式,例如操作中文字符文件时就需要用到中文的编码方式。

InputStream和OutputStream的使用
private val file = File("xxxxxxxxxx")
private var out: DataOutputStream? = null

private fun wirteStream() {
    try {
        out = DataOutputStream(
            BufferedOutputStream(
                FileOutputStream(file)
            )
        ).apply {
            write(byteArrayOf(1,2,3))
            flush()
        }
    } catch (e: Exception) {
        e.printStackTrace()
    } finally {
        try {
            out?.close()
        }catch (e:Exception){
            e.printStackTrace()
        }
    }
}

首先获取到某个文件file,FileOutputStream是OutputStream的子类,作用是将file文件转成流的形式操作,BufferedOutputStream也是OutputStream的子类,是将这个流套了一层转变成缓存的流,后面再讲下缓存流有什么用。DataOutputStream也是OutputStream的子类,还是将缓冲流套一层,功能更全面,提供了写入的基本数据格式,一般是writeXx。

顺带提一下这里用到了设计模式中的装饰器模式,将一些功能事先准备好,需要时候再加上指定功能,这样的好处是将各个类解耦开,避免子类继承过多导致类体积过大。

OutputStream和InputStream是成双成对的,所以类也是一一对应的

private val file = File("xxxxxxxxxx")

private var input:DataInputStream? = null

private fun readSteam() {
    try {
        input = DataInputStream(
            BufferedInputStream(
                FileInputStream(file)
            )
        ).apply {
            read()
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }finally {
        try {
            input?.close()
        }catch (e:Exception){
            e.printStackTrace()
        }
    }
}

BufferedOutputStream相对于FileOutputStream,由于有缓存区的存在,效率比较高。FileOutputStream每写入一个字节就做一次文件操作,频繁的对磁头操作效率低,而BufferedOutputStream中在内存中定义一块缓存区,缓冲区是一块特殊的内存,可以将写入的信息暂时放到这块缓存区里,后续再一次性写入磁盘中,提高了写入的效率。

使用缓冲区时,当写入的信息达到缓冲区大小后,会自动将文件写入磁盘;如果是主动调用了flush,会强制把缓冲区的内容写入磁盘;如果每调用flush且没close流,写入的文件没达到缓冲区大小,信息是不会写到磁盘里,还是保存在缓冲区中。

close的目的是把刚才打开操作的OutputStream或者InputStream内存回收,如果一直用完不close,会导致内存泄漏。在带有缓存区的流中,在close之前会自动调用flush。最好是在finally中close防止内存泄漏。

字符流和字节流的转换

字节流的形式只是给计算机看的,我们是看不懂的,所以需要把字节流转为我们可以看懂的字符流。

private val file = File("./app/test.txt")

private var bufferedWriter: BufferedWriter? = null

fun write() {
    try {
        bufferedWriter = BufferedWriter(FileWriter(file))
            .apply {
                write("这是write!")
                flush()
            }
    } catch (e: Exception) {
        e.printStackTrace()
    } finally {
        try {
            bufferedWriter?.close()
        } catch (e: Exception) {
            e.printStackTrace()
        }

    }
}

private var bufferedRead: BufferedReader? = null

fun read() {
    try {
        bufferedRead = BufferedReader(
            InputStreamReader(
                FileInputStream(file)
            )
        ).apply {
            print(readLine())
        }
    } catch (e: Exception) {
        e.printStackTrace()
    } finally {
        try {
            bufferedRead?.close()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}

在写入或者读取字符之前,需要将OutputStream或者InputStream转成字符,这里关于字符有关的类是Write和Read,它们都是抽象类,主要用于字符的读写,用他们的子类OutputStreamWrite和InputStreamRead将字节流传入后转变成可以读写的字符流,和字节流一样,字符流也带有缓冲区的类。同样,在操作完后需要将流关闭防止内存泄漏。

为了简化write和read写法,官方提供了 FileWriter 和 FileRead代替InputStreamReader(FileInputStream(file))和OutStreamWrite(FileOutputStream(file))。

文件的复制
fun copy() {
    val file = File("./app/copy1.txt")
    val copyedFile = File("./app/copy2.txt")
    var input: FileInputStream? = null
    var out: FileOutputStream? = null
    try {
        input = FileInputStream(file)
        out = FileOutputStream(copyedFile)
        val buffer = ByteArray(1024)
        var bytes = input.read(buffer)
        while (bytes >= 0) {
            out.write(buffer, 0, bytes)
            bytes = input.read(buffer)
        }
    } catch (e: Exception) {
        e.printStackTrace()
    } finally {
        input?.close()
        out?.close()
    }
}

首先准备好一个已有文件和存放复制完后的文件,之后在准备个1024字节数组(数组大小可以自己设置),用于将读取到的信息存放在数组里,每次写入copy2之前判断数组是否大于等于0再写入,后面把继续读取信息判断是否已经读完了,这样的边读边写实现了文件的复制操作。在kotlin中其实已经帮我们实现了文件的复制的方法copyTo

val file = File("./app/copy1.txt").copyTo(File("./app/copy2.txt"))
以上

许多细节上的点还需要在源码中探索...