您当前的位置:首页 > 计算机 > 编程开发 > Java

RandomAccessFile的效率问题,以及多线程断点续传下载要不要使用rws模式

时间:02-08来源:作者:点击数:

文件复制操作,我们先使用普通的FileInputStream和FileOutputStream来完成,代码如下:

val start = System.currentTimeMillis()
FileInputStream("D:\\hello.mp4").use { input ->
    FileOutputStream("D:\\hello_copy.mp4").use { output ->
        val buf = ByteArray(8192)
        var length: Int
        while (input.read(buf).also { length = it } != -1) {
            output.write(buf, 0, length)
        }
    }
}
println("复制完成,用时:${System.currentTimeMillis() - start}")

运行效果如下:

复制完成,用时:345

在公司的电脑上,每次运行时间不一样,大概是在300 ~ 400左右浮动。

下面改为RandomAccessFile实现:

val start = System.currentTimeMillis()
RandomAccessFile("D:\\hello.mp4", "r").use { input ->
    RandomAccessFile("D:\\hello_copy.mp4", "rw").use { output ->
        val buf = ByteArray(8192)
        var length: Int
        while (input.read(buf).also { length = it } != -1) {
            output.write(buf, 0, length)
        }
    }
}
println("复制完成,用时:${System.currentTimeMillis() - start}")

运行效果如下:

复制完成,用时:288

经过多次运行,发现经常到跑到300以下,所以RandomAccessFile的效率还是非常高的,似乎比FileInputStreamFileOutputStream的效率还要高一点点。

但是在写文件的时候需要注意,如果使用的读写模式为:“rws”,如下:

RandomAccessFile("D:\\hello.mp4", "r").use { input ->
    RandomAccessFile("D:\\hello_copy.mp4", "rws").use { output ->
        。。。
    }
}

运行效果如下:

复制完成,用时:3148

可以看到,读写模式加了s之后效率非常的低,比原来慢10倍都不止。对于s参数,官方文档说明如下:

public RandomAccessFile(File file, String mode) throws FileNotFoundException

创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。将创建一个新的 FileDescriptor 对象来表示此文件的连接。

mode 参数指定用以打开文件的访问模式。允许的值及其含意为:

含义
“r” 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
“rw” 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
“rws” 打开以便读取和写入,对于 “rw”,还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
“rwd” 打开以便读取和写入,对于 “rw”,还要求对文件内容的每个更新都同步写入到底层存储设备。

“rws” 和 “rwd” 模式的工作方式极其类似 FileChannel 类的 force(boolean) 方法,分别传递 true 和 false 参数,除非它们始终应用于每个 I/O 操作,并因此通常更为高效。如果该文件位于本地存储设备上,那么当返回此类的一个方法的调用时,可以保证由该调用对此文件所做的所有更改均被写入该设备。这对确保在系统崩溃时不会丢失重要信息特别有用。如果该文件不在本地设备上,则无法提供这样的保证。

“rwd” 模式可用于减少执行的 I/O 操作数量。使用 “rwd” 仅要求更新要写入存储的文件的内容;使用 “rws” 要求更新要写入的文件内容及其元数据,这通常要求至少一个以上的低级别 I/O 操作。

如果存在安全管理器,则使用 file 参数的路径名作为其参数调用它的 checkRead 方法,以查看是否允许对该文件进行读取访问。如果该模式允许写入,那么还使用该路径参数调用该安全管理器的 checkWrite 方法,以查看是否允许对该文件进行写入访问。

当我们需要非常即时的写入内容的时候,就需要使用"rws"模式,因为"rw"模式会使用buffer,只有buffer满的时候,或者调用close()函数的时候才真正写到文件,而使用"rws"则不使用buffer,可以即时写到文件,因为不使用buffer,所以效率就低下。这是网上的说法,根据我的实验,我发现即使写一个字节,也是可以即时写出去的,说明没有使用Buffer,示例代码如下:

val start = System.currentTimeMillis()
val file = File("D:\\test\\test.txt")
val output = RandomAccessFile(file, "rw")
val input = RandomAccessFile(file, "r")
output.write(65)
println("读取一个字节:${input.read()}")
Thread.sleep(1000 * 30)
output.close()
input.close()
println("写入完成,用时:${System.currentTimeMillis() - start}")

运行结果如下:

读取一个字节:65

可以看到,写入一个字节后,立马读就能读取到写入文件的字节内容,而且我们只写了一个字节,说明RandomAccessFile没有使用Buffer,即使是1个字节也立马写入到文件了,而且我打开文件看也是能看到字母“A”的(A的ASCCI码为65),而此RandomAccessFile对象也还没有关闭呢。这也有可能是在Windows平台没有Buffer,万一Linux平台有呢?于是跑到我的Android手机上再试一次,把代码改一下,放到Activity中运行,如下:

thread {
    val start = System.currentTimeMillis()
    val file = File(filesDir,"test.txt")
    val output = RandomAccessFile(file, "rw")
    val input = RandomAccessFile(file, "r")
    output.write(65)
    println("读取一个字节:${input.read()}")
    output.close()
	input.close()
    Thread.sleep(1000 * 30)
    println("写入完成,用时:${System.currentTimeMillis() - start}")
}

通过运行结果发现,也是可以立即看到读取的字节内容的,说明RandomAccessFile是没有使用缓冲的。所以,要想搞懂rws模式的功能,估计得懂系统原理才能搞懂了。根据JDK文档对rws的描述,大概理解是如果想要写入更及时,则使用rws模式吧,我也不知道这样理解对不对。

暂且不管理解的对不对,这里我们来算一下rws的写入速度,之前写入的文件大小为182MB,使用了3148毫秒,则:182MB / 3.148秒 = 57.8MB/秒,对于文件下载来说,这个写入速度是够用的,使用rws似乎没什么问题。但是,随着科技的发展,网速越来越快,现在电脑上下载速度达到100M/S也不是什么难事,如果手机带宽也很快的话,则使用rws就会出现网速比你写磁盘的速度快的问题了,所以rws不应该使用。对于多线程的断点续传的下载,也是没必要使用rws模式的,在使用多线程下载的时候,先把流中获取的数据写文件,然后再把写入文件的大小写到另一个文件中记录起来,假设数据写入文件后,程序断开了,比如直接杀进程把程序杀掉,假设这时程序来不及把刚刚写入文件的大小保存到记录中,这也是没有问题的,因为在下次再一次下载时,没保存记录的那段数据需要重新下载而已,比如定的缓冲为8K,则是有8K需要重新下然后覆盖之前下好的8K,这没什么的,现在手机的下载最少也有100多K每秒,有8K需要重下没什么大不了的。倒时如果你加了rws,如果人家网速有100M每秒,但是你写文件时只能达到60M/S,那就浪费网速了,所以在多线程下载中没必要使用rws模式,使用rw模式即可。大线程下载保存的记录简单理解就是可以少,不可以多,比如我们下载了40K,保存的记录为30K,这是可以的,这样就有10K需要重复下载而已,但是,如果下载了30K,你保存的记录为40K,这就有问题了,因为你下次下载的时候会从40K的位置开始下,但是你之前只下了30K,这样文件就不完整了,所以在保存记录时,一定是把保存记录的代码放在写文件数据的代码的后面。

在复制文件的时候也不要使用"rws"了,因为实在是太慢了。那什么时候需要使用rws模式,讲真我真不知道,假设如果你要即时保存一些数据,而且这些数据也很小,而且这些数据很重要,则你可以使用rws模式,这个模式可能能让你的写入更及时,虽然效率低,但是由于数据量小的话,影响也不大。

对于"rwd",据说功能和"rws"差不多,只不过是只对“文件的内容”同步更新到磁盘,不对metadata同步更新。那metadata是什么,比如文件修改日期,如下:

“rws”:即时刷新文件的内容和文件的修改日期。

“rwd”:即时刷新文件的内容,但修改日期可能不会更改,直到文件关闭。

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门