kotlin中的文件和IO流 您所在的位置:网站首页 io流csdn kotlin中的文件和IO流

kotlin中的文件和IO流

2023-03-27 09:18| 来源: 网络整理| 查看: 265

kotlin中的文件和IO流 File

java.io.File类,File 能新建、删除、重命名文件和目录,但 File 不能访问文件内容本身。如果需要访问文件内容本身,则需要使用输入/输出流。File对象可以作为参数传递给流的构造函数。

文件路径以及文件名 val file1 = File(getExternalFilesDir("")?.absolutePath + "/zx1001.txt") println("name=" + file1.name) println("absoluteFile=" + file1.absoluteFile) println("absolutePath=" + file1.absolutePath) println("path=" + file1.path) println("parent=" + file1.parent) val file2 = File(getExternalFilesDir("")?.absolutePath + "/zx1002.txt") val b = file1.renameTo(file2) println("重命名是否成功" + b + ",name=" + file1.name) 复制代码

输出

name=zx1001.txt absoluteFile=/storage/emulated/0/Android/data/zx.com.demo/files/zx1001.txt absolutePath=/storage/emulated/0/Android/data/zx.com.demo/files/zx1001.txt path=/storage/emulated/0/Android/data/zx.com.demo/files/zx1001.txt parent=/storage/emulated/0/Android/data/zx.com.demo/files 重命名是否成功false,name=zx1001.txt 复制代码 文件检测 val file = File(getExternalFilesDir("")?.absolutePath + "/app-debug.apk") val dir = File(getExternalFilesDir("")?.absolutePath) println("exists=" + file.exists()) println("canExecute=" + file.canExecute()) println("canRead=" + file.canRead()) println("canWrite=" + file.canWrite()) println("isDirectory=" + file.isDirectory) println("lastModified=" + Date(file.lastModified())) println("length=" + file.length()) println("---------") println("dir.isDirectory=" + dir.isDirectory) println("dir.lastModified=" + Date(dir.lastModified())) println("dir.length=" + dir.length()) 复制代码

输出

exists=true canExecute=false canRead=true canWrite=true isDirectory=false lastModified=Fri Jun 11 14:05:38 GMT+08:00 2021 length=7785185 --------- dir.isDirectory=true dir.lastModified=Fri Jun 11 14:13:14 GMT+08:00 2021 dir.length=4096 复制代码

注意,.length()只能返回文件的大小(单位为字节),文件夹的实际大小无法返回。

文件夹操作 val dir = File(getExternalFilesDir("")?.absolutePath + "/myDir/dir1") dir.mkdirs()//创建一个文件目录。若上层文件目录不存在,一并创建 val appDir = File(getExternalFilesDir("")?.absolutePath) dir.mkdirs() val files = appDir.listFiles() //返回目录下的文件列表 for (i in files) { println(i) } val filePaths = appDir.list()//返回目录下文件名和文件夹名称数组 for (i in filePaths) { println(i) } 复制代码

输出

/storage/emulated/0/Android/data/zx.com.demo/files/myDir /storage/emulated/0/Android/data/zx.com.demo/files/zx1001.txt /storage/emulated/0/Android/data/zx.com.demo/files/app-debug.apk myDir zx1001.txt app-debug.apk 复制代码 kotlin对File的扩展

以上这些示例中用的都是Java提供的API,kotlin提供了一些扩展方法和扩展属性,可以更加方便的操作文件。

名称作用extension文件格式的后缀,不包括.,例如:mp3nameWithoutExtension名称,不包括后缀appendBytes(ByteArray)追加字节数组到文件末尾writeBytes(ByteArray)写入字节数组,如果之前有内容,会被覆盖readBytes(): ByteArray以字节数组的形式获取此文件的全部内容,不建议在大文件上使用此方法,上限2GappendText(String,Charset)追加字符串到文件末尾,默认是UTF-8编码writeText(String,Charset)写入字符串,如果之前有内容,会被覆盖readText(Charset): String以String 形式获取此文件的全部内容,默认编码UTF-8,不建议在大文件上使用此方法,上限2Greader(Charset): InputStreamReader返回一个FileReader以读取此文件的内容bufferedReader(Charset, bufferSize: Int): BufferedReader返回一个BufferedReader用于读取此文件的内容writer(Charset):OutputStreamWriter返回一个FileWriter用于写入此文件bufferedWriter(Charset, bufferSize: Int): BufferedWriter返回一个BufferedWriter用于写入此文件forEachBlock(action: (buffer: ByteArray, bytesRead: Int) -> Unit)高阶函数,按字节块读取文件并为每个读取的块调用action,字节块默认为4096。适用于大文件forEachLine(Charset, action: (line: String) -> Unit)高阶函数,按行读取文件并为每行调用action,适用于大文件readLines( Charset): List按行读取文件,不要在大文件上使用此方法copyTo(target: File, overwrite: Boolean = false, bufferSize: Int = DEFAULT_BUFFER_SIZE): File复制文件或者文件夹,并且会创建target所需的各个父级文件夹(如果缺少)。overwrite为true时,target可被覆盖,不为true并且当target存在时,返回false,复制失败。overwrite为true并且target是一个文件夹时,只有当文件夹为空时才会被替换。源文件如果是文件夹,则只会创建目标文件夹,不会复制文件夹中的文件。该操作不会保留复制的文件属性,例如创建/修改日期、权限等copyRecursively(target: File, overwrite: Boolean = false, onError: (File, IOException))递归复制文件或者文件夹,并且会创建target所需的各个父级文件夹(如果缺少)。如果源文件是文件夹,将复制文件夹中的所有内容。该操作不会保留复制的文件属性,例如创建/修改日期、权限等。默认自带的错误处理器会将错误抛出,可以传入一个Lambda用来处理异常。复制文件夹失败时,可能已经复制了一部分deleteRecursively()递归删除文件或者文件夹,删除文件夹失败时,可能已经删除了一部分.walk()自上而下,深度优先,遍历整个文件夹

看完这些扩展方法,瞬间觉得Java不香了,感觉都不需要再学IO流了,但是有时候我们并不是只处理文件的IO流,所以Java的IO流还是要学。

IO流

按照“流”的数据流向,可以将其化分为:输入流和输出流。输入和输出是相对于程序而言,数据流入程序中,那就是输入流,数据流出程序,那就是输出流。按照“流”中处理数据的单位,可以将其区分为:字节流(二进制流)和字符流。在java中,字节是占1个Byte,即8位;而字符是占2个Byte,即16位。一般来说, 对于文本编辑器或文本输出程序创建的文件例如.txt、.xml、.java,应该使用字符流来读取, 对于二进制文件例如png、doc, 应该使用字节流来读取。因为字节流操作的是二进制数据,所以字节流不涉及编码。字符流操作的是字符,而字符是有编码的,所以字符流需要指定编码,下方的例子中都是用的默认的UTF-8编码。

字节流的抽象基类:InputStream,OutputStream。字符流的抽象基类:Reader,Writer。

由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀,如InputStream的子类FileInputStream,Reader的子类FileReader。

表格总结了各种流:

IO流.png

文件流

文件流可以直接操作文件的读写,字符流操作的类为FileWriter和FileReader,字节流操作的类为FileInputStream和FileOutputStream。

FileWriter、FileReader

val fileWriter = FileWriter(file, true) fileWriter.write(SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date()) + ",自己换行\n") fileWriter.appendLine(SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date()) + ",张三") fileWriter.close() var reader = FileReader(file) val buf = CharArray(1024) var len: Int while (reader.read(buf).also { len = it } != -1) { println(String(buf, 0, len)) } reader.close() println("-------------") //使用扩展方法 reader = FileReader(file) val lines = reader.readLines() for (item in lines) { println(item) } println("-------------") //使用扩展方法 reader = FileReader(file) reader.forEachLine { println(it) } println("-------------") //使用扩展方法 reader = FileReader(file) reader.useLines { it.iterator().forEach { it1 -> println(it1) } } 复制代码

FileWriter的第二个参数,表示是否追加写入,true为追加,false为覆盖。write中可以追加\n实现换行,也可以使用kotlin扩展函数appendLine实现写入一行。读数据时,同样可以使用kotlin扩展函数readLines()、forEachLine、useLines来简化读取,这三种读取方式不需要手动关闭流。最终输出:

2021-06-11 22:43:18,自己换行 2021-06-11 22:43:18,张三 ------------- 2021-06-11 22:43:18,自己换行 2021-06-11 22:43:18,张三 ------------- 2021-06-11 22:43:18,自己换行 2021-06-11 22:43:18,张三 ------------- 2021-06-11 22:43:18,自己换行 2021-06-11 22:43:18,张三 复制代码

FileInputStream、FileOutputStream

val fileOutputStream = FileOutputStream(file, true) fileOutputStream.write((SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date()) + ",自己换行\n").toByteArray()) fileOutputStream.write((SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date()) + ",李四\n").toByteArray()) var fileInputStream = FileInputStream(file) val buffer = ByteArray(1024) var len: Int while (fileInputStream.read(buffer).also { len = it } != -1) { print(String(buffer, 0, len)) } fileInputStream.close() println("-------------") //使用扩展方法 fileInputStream = FileInputStream(file) print(String(fileInputStream.readBytes())) fileInputStream.close() 复制代码

使用String的扩展方法toByteArray(),将String转换为字节数组,然后写入到文件。读取时,按块读取,设置块的大小为1024,每读取一块就打印一块。也可以使用InputStreamd类的扩展方法readBytes()一次性读出所有的内容,但是这种方法不适合大文件。

缓冲流

为了提高数据读写的速度,JavaAPI提供了带缓冲功能的流,在使用这些流类,会创建一个内部缓冲区数组。缓冲流要“套接”在相应的其他流之上(创建时需要传入其他流),对读写的数据提供了缓冲的功能,提高了读写的效率。写出的数据会先在内存中缓存,使用flush()将会使内存中的数据立刻写出。字符流的缓冲流类为BufferedReader和BufferedWriter,字节流的缓冲流类为BufferedInputStream和BufferedOutputStream。

BufferedReader、BufferedWriter

BufferedReader需要套接在其他的Reader上使用,所以创建时需要传入一个Reader,例如传入一个FileReader,BufferedWriter也是如此。也可以通过Reader和Writer类的扩展函数buffered()返回一个创建好的BufferedReader或BufferedWriter。BufferedReader继承自Reader,所以Reader类的readLines()、forEachLine、useLines扩展函数,BufferedReader同样也可以使用。BufferedReader、BufferedWriter使用示例如下:

var fileWriter = FileWriter(file, true) val bufferedWriter = BufferedWriter(fileWriter) bufferedWriter.write("新增一行\n") bufferedWriter.close() //使用扩展函数buffered获取BufferedWriter, // 然后用BufferedWriter的扩展函数appendLine添加一行 fileWriter = FileWriter(file, true) fileWriter.buffered().apply { appendLine("扩展函数新增一行") close() } var fileReader = FileReader(file) val bufferedReader = BufferedReader(fileReader) var buffer: CharArray = CharArray(1024) var len: Int while (bufferedReader.read(buffer).also { len = it } != -1) { print(String(buffer, 0, len)) } bufferedReader.close() println("-------------------") //使用扩展函数buffered获取BufferedReader, // 然后用BufferedReader的扩展函数forEachLine逐行获取 fileReader = FileReader(file) fileReader.buffered().forEachLine { println(it) } println("-------------------") fileReader = FileReader(file) val lines = fileReader.buffered().readLines() for (item in lines) { println(item) } println("-------------------") fileReader = FileReader(file) fileReader.buffered().useLines { it.iterator().forEach { it1 -> println(it1) } } 复制代码

输出

新增一行 扩展函数新增一行 ------------------- 新增一行 扩展函数新增一行 ------------------- 新增一行 扩展函数新增一行 ------------------- 新增一行 扩展函数新增一行 复制代码

可以看出3种扩展函数更简单易用。

下边来对比一下使用了BufferedWriter和不使用BufferedWriter的效率差别,见如下示例:

val time1 = measureTime { val longFile = File(getExternalFilesDir("")?.absolutePath + "/View.java") val fileWriter = FileWriter(File(getExternalFilesDir("")?.absolutePath + "/View1.txt"), true) val bufferedWriter = BufferedWriter(fileWriter) longFile.forEachLine { bufferedWriter.write(it) } bufferedWriter.flush() bufferedWriter.close() } println("使用BufferedWriter耗时$time1") val time2 = measureTime { val longFile = File(getExternalFilesDir("")?.absolutePath + "/View.java") val fileWriter = FileWriter(File(getExternalFilesDir("")?.absolutePath + "/View2.txt"), true) longFile.forEachLine { fileWriter.write(it) } fileWriter.close() } println("不使用的情况耗时$time2") 复制代码

输出

使用BufferedWriter耗时102ms 不使用的情况耗时351ms 复制代码

示例中我将Android的View的源码的2倍作为数据源,然后分别用FileWriter和BufferedWriter将数据源复制到另外两个TXT中。这里用View的源码的2倍作为数据源是因为View的源码只有1M多,体现不出差异,所以复制了一遍。

输出可以看出,使用BufferedWriter的耗时是不使用时的1/3不到,如果文件再大一些,差异可能更明显。为什么使用缓冲后能提升效率呢,简单理解就是:使用缓冲处理流包装就是一堆一堆的干活,不用CPU多次处理数据转换,只是设置一下数据转换成功后的文件。不使用缓冲处理流包装就是CPU傻傻的一个字节一个字节循环来干活存储写入文件中,相比可见效率明显变慢。所以建议总是使用缓冲流。

BufferedInputStream、BufferedOutputStream

类似于BufferedReader、BufferedWriter,可以使用InputStream/OutputStream的buffered()扩展函数获取对应的BufferedInputStream、BufferedOutputStream,贴上示例代码:

var fileOutputStream = FileOutputStream(file, true) val bufferedOutputStream = BufferedOutputStream(fileOutputStream) bufferedOutputStream.write("这是一行数据\n".toByteArray()) bufferedOutputStream.close() fileOutputStream = FileOutputStream(file, true) fileOutputStream.buffered().apply { write("扩展函数新增的数据\n".toByteArray()) close() } var fileInputStream = FileInputStream(file) val bufferedInputStream = BufferedInputStream(fileInputStream) val buffer = ByteArray(1024) var len: Int while (bufferedInputStream.read(buffer).also { len = it } != -1) { print(String(buffer, 0, len)) } bufferedInputStream.close() println("-------------") //使用扩展方法buffered()获取BufferedInputStream, //再调用BufferedInputStream的readBytes()扩展方法,一次获取所有的数据 fileInputStream = FileInputStream(file) fileInputStream.buffered().apply { print(String(readBytes())) close() } 复制代码 对象流

DataInputStream类和 DataOutputStream 类可以实现基本数据类型与字符串的输入和输出。 而 ObjectlnputStream 类和 ObjectOutputStream 类除了可以实现基本数据类型与字符串的输入和输出之外, 还可以实现对象的输入和输出。 由于 ObjectlnputStream 类和ObjectOutputStream 类包含 DatalnputStream 类和DataOutputStream 类的所有功能, 所以, 完全可以用 ObjectlnputStream 类和 ObjectOutputStream 类代替 DatalnputStream 类和DataOutputStream 类 。

序列化

并不是每一个对象都可以写到输出流,可以写入到输出流中的对象称为可序列化的。对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。序列化的好处在于可将任何实现了Serializable接口的对象转化为字节数据,使其在保存和传输时可被还原。如果需要让某个对象支持序列化机制,则必须让其类是可序列化的为了让某个类是可序列化的,该类必须实现Serializable或者Externalizable。

凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:serialVersionUID,serialVersionUID用来表明类的不同版本间的兼容性。如果类没有显示定义这个静态变量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的源代码作了修改,serialVersionUID可能发生变化,这就会导致使用旧版类创建的流将无法恢复成新版的类对象。故建议,显示声明serialVersionUID。如果希望类的不同版本对序列化兼容,需确保类的不同版本具有相同的serialVersionUID;如果不希望类的不同版本对序列化兼容,因此需确保类的不同版本具有不同的serialVersionUID。

如果一个对象是 Serializable 的实例, 但它包含了非序列化的属性, 那么可以序列化这个对象吗? 答案是否定的。 为了使该对象是可序列化的, 需要给这些属性加上关键字transient, 告诉 Java 虚拟机将对象写入对象流时忽略这些属性。

ObjectlnputStream

ObjectlnputStream 类和 ObjectOutputStream 类可以用于读 / 写可序列化的对象 。示例如下:

val fileOutputStream = FileOutputStream(file, true) val objectOutputStream = ObjectOutputStream(fileOutputStream) objectOutputStream.writeObject(Book("Android从入门到放弃", 100, "人民邮电出版社")) objectOutputStream.writeObject(Book("颈椎病的治疗", 200, "北京大学出版社")) objectOutputStream.close() fileOutputStream.close() val fileInputStream = FileInputStream(file) val objectInputStream = ObjectInputStream(fileInputStream) try { while (true) { objectInputStream.readObject().also { val book = it as Book println(book.toString()) } } } catch (e: EOFException) { } finally { objectInputStream.close() fileInputStream.close() } 复制代码

Book类实现了Serializable

data class Book(val name: String, var page: Int, var publisher: String) : Serializable 复制代码

输出

Book(name=Android从入门到放弃, page=100, publisher=人民邮电出版社) Book(name=颈椎病的治疗, page=200, publisher=北京大学出版社) 复制代码

上述代码第一次写入没有问题,再次写入后会有问题,错误信息为:java.io.StreamCorruptedException: invalid type code: AC。解决方式参考

www.iteye.com/blog/halzha…

RandomAccessFile

上边讲的都只能追加或者覆盖,无法实现任意位置的读写,所以Java 提供了 RandomAccessFile 类, 允许从文件的任何位置进行数据的读写 。RandomAccessFile对象包含一个记录指针,用以标示当前读写处的位置。RandomAccessFile类对象可以自由移动记录指针:

long getFilePointer():获取文件记录指针的当前位置 void seek(long pos):将文件记录指针定位到pos位置

使用示例如下:

//RandomAccessFile的read和write都会改变指针位置。 val randomAccessFile = RandomAccessFile(file, "rw") val data1 = "这是数据1\n" randomAccessFile.write(data1.toByteArray()) randomAccessFile.write("这是数据2\n".toByteArray()) randomAccessFile.close() val randomAccessFile1 = RandomAccessFile(file, "rw") randomAccessFile1.seek(data1.toByteArray().size.toLong()) println("当前指针位置1," + randomAccessFile1.filePointer) //取出data1之后的数据 var stringAfterData1 = "" var buffer = ByteArray(1024) var len: Int while (randomAccessFile1.read(buffer).also { len = it } != -1) { stringAfterData1 += String(buffer, 0, len) } println("当前指针位置2," + randomAccessFile1.filePointer) randomAccessFile1.seek(data1.toByteArray().size.toLong()) randomAccessFile1.write("这一行数据插入在数据1和2之间\n".toByteArray()) //将插入之前data1之后的数据追加上 randomAccessFile1.write(stringAfterData1.toByteArray()) println("当前指针位置3," + randomAccessFile1.filePointer) println("当前数据长度," + randomAccessFile1.length()) //重新移动指针到开头,取出所有数据 randomAccessFile1.seek(0) while (randomAccessFile1.read(buffer).also { len = it } != -1) { print(String(buffer, 0, len)) } println("当前指针位置4," + randomAccessFile1.filePointer) randomAccessFile1.close() 复制代码

输出

当前指针位置1,14 当前指针位置2,28 当前指针位置3,70 当前数据长度,70 这是数据1 这一行数据插入在数据1和2之间 这是数据2 当前指针位置4,70 复制代码

注意:

RandomAccessFile的read()和write()都会使指针往后移动,移动的值就是读取或者写入的字节长度。 指针移动时,seek方法传入的是字节长度,不是字符长度。 write()其实是在指针位置覆盖,所以要做插入操作时,必须先将插入点之后的数据暂存起来,在插入点插入数据后,再将暂存的数据追加到后边,这样才能实现插入。例如:要在第一行和第二行之前插入数据,也就是新插入的数据作为第二行,那么插入之前需要将第二行及之后的数据暂存起来,然后将数据插入(覆盖)到第二行,然后再将之前暂存的第二行(插入前的第二行)及之后的数据取出,追加都文件末尾。


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有