什么是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"))
以上
许多细节上的点还需要在源码中探索...