文件写入的6种方法,这种方法性能最好

作者 | 王磊

来源 | Java中文社群(ID:javacn666)

转载请联系授权(微信ID:GG_Stone)

在 Java 中操作文件的方法本质上只有两种:字符流和字节流,而字节流和字符流的实现类又有很多,因此在文件写入时我们就可以选择各种各样的类来实现。我们本文就来盘点一下这些方法,顺便测试一下它们性能,以便为我们选出最优的写入方法。

在正式开始之前,我们先来了解几个基本的概念:流、字节流和字符流的定义与区别。

0.什么是流?

Java 中的“流”是一种抽象的概念,也是一种比喻,就好比水流一样,水流是从一端流向另一端的,而在 Java 中的“水流”就是数据,数据会从一端“流向”另一端。

根据流的方向性,我们可以将流分为输入流和输出流,当程序需要从数据源中读入数据的时候就会开启一个输入流,相反,写出数据到某个数据源目的地的时候也会开启一个输出流,数据源可以是文件、内存或者网络等。

1.什么是字节流?

字节流的基本单位为字节(Byte),一个字节通常为 8 位,它是用来处理二进制(数据)的。字节流有两个基类:InputStream(输入字节流)和 OutputStream(输出字节流)。

常用字节流的继承关系图如下图所示:其中 InputStream 用于读操作,而 OutputStream 用于写操作。

2.什么是字符流?

字符流的基本单位为 Unicode,大小为两个字节(Byte),它通常用来处理文本数据。字符流的两个基类:Reader(输入字符流)和 Writer(输出字符流)。

常用字符流的继承关系图如下图所示:

3.流的分类

流可以根据不同的维度进行分类,比如可以根据流的方向进行分类,也可以根据传输的单位进行分类,还可以根据流的功能进行分类,比如以下几个。

① 按流向分类

  • 输出流:OutputStreamWriter 为基类。

  • 输入流:InputStreamReader 为基类。

② 根据传输数据单位分类

  • 字节流:OutputStreamInputStream 为基类。

  • 字符流:Writer 和 Reader 为基类。

③ 根据功能分类

  • 字节流:可以从或向一个特定的地方(节点)读写数据。

  • 处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。

PS:我们通常是以传输数据的单位来为流进行分类。

4.写文件的6种方法

写入文件的方法主要源于字符流 Writer 和输出字节流 OutputStream 的子类,如下图所示:

以上标注✅号的类就是用来实现文件写入的类,除此之外,在 JDK 1.7 中还提供了 Files 类用来实现对文件的各种操作,接下来我们分别来看。

方法 1:FileWriter

FileWriter 属于「字符流」体系中的一员,也是文件写入的基础类,它包含 5 个构造函数,可以传递一个具体的文件位置,或者 File 对象,第二参数表示是否要追加文件,默认值为 false 表示重写文件内容,而非追加文件内容(关于如何追加文件,我们后面会讲)。

FileWriter 类的实现如下:

/*** 方法 1:使用 FileWriter 写文件* @param filepath 文件目录* @param content  待写入内容* @throws IOException*/
public static void fileWriterMethod(String filepath, String content) throws IOException {try (FileWriter fileWriter = new FileWriter(filepath)) {fileWriter.append(content);}
}

只需要传入具体的文件路径和待写入的内容即可,调用代码如下:

public static void main(String[] args) {fileWriterMethod("/Users/mac/Downloads/io_test/write1.txt", "哈喽,Java中文社群.");
}

然后我们打开写入的文件,实现结果如下:

关于资源释放的问题:在 JDK 7 以上的版本,我们只需要使用 try-with-resource 的方式就可以实现资源的释放,就比如使用 try (FileWriter fileWriter = new FileWriter(filepath)) {...} 就可以实现 FileWriter 资源的自动释放。

方法 2:BufferedWriter

BufferedWriter 也属于字符流体系的一员,与 FileWriter 不同的是 BufferedWriter 自带缓冲区,因此它写入文件的性能更高(下文会对二者进行测试)。

小知识点:缓冲区

缓冲区又称为缓存,它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。

缓冲区的优势以文件流的写入为例,如果我们不使用缓冲区,那么每次写操作 CPU 都会和低速存储设备也就是磁盘进行交互,那么整个写入文件的速度就会受制于低速的存储设备(磁盘)。但如果使用缓冲区的话,每次写操作会先将数据保存在高速缓冲区内存上,当缓冲区的数据到达某个阈值之后,再将文件一次性写入到磁盘上。因为内存的写入速度远远大于磁盘的写入速度,所以当有了缓冲区之后,文件的写入速度就被大大提升了。

了解了缓存区的优点之后,咱们回到本文的主题,接下来我们用 BufferedWriter 来文件的写入,实现代码如下:

/*** 方法 2:使用 BufferedWriter 写文件* @param filepath 文件目录* @param content  待写入内容* @throws IOException*/
public static void bufferedWriterMethod(String filepath, String content) throws IOException {try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filepath))) {bufferedWriter.write(content);}
}

调用代码和方法 1 类似,这里就不再赘述了。

方法 3:PrintWriter

PrintWriter 也属于字符流体系中的一员,它虽然叫“字符打印流”,但使用它也可以实现文件的写入,实现代码如下:

/*** 方法 3:使用 PrintWriter 写文件* @param filepath 文件目录* @param content  待写入内容* @throws IOException*/
public static void printWriterMethod(String filepath, String content) throws IOException {try (PrintWriter printWriter = new PrintWriter(new FileWriter(filepath))) {printWriter.print(content);}
}

从上述代码可以看出,无论是 PrintWriter 还是 BufferedWriter 都必须基于 FileWriter 类来完成调用。

方法 4:FileOutputStream

上面 3 个示例是关于字符流写入文件的一些操作,而接下来我们将使用字节流来完成文件写入。我们将使用 String 自带的 getBytes() 方法先将字符串转换成二进制文件,然后再进行文件写入,它的实现代码如下:

/*** 方法 4:使用 FileOutputStream 写文件* @param filepath 文件目录* @param content  待写入内容* @throws IOException*/
public static void fileOutputStreamMethod(String filepath, String content) throws IOException {try (FileOutputStream fileOutputStream = new FileOutputStream(filepath)) {byte[] bytes = content.getBytes();fileOutputStream.write(bytes);}
}

方法 5:BufferedOutputStream

BufferedOutputStream 属于字节流体系中的一员,与 FileOutputStream 不同的是,它自带了缓冲区的功能,因此性能更好,它的实现代码如下:

/*** 方法 5:使用 BufferedOutputStream 写文件* @param filepath 文件目录* @param content  待写入内容* @throws IOException*/
public static void bufferedOutputStreamMethod(String filepath, String content) throws IOException {try (BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(filepath))) {bufferedOutputStream.write(content.getBytes());}
}

方法 6:Files

接下来的操作方法和之前的代码都不同,接下来咱们就使用 JDK 7 中提供的一个新的文件操作类 Files 来实现文件的写入。

Files 类是 JDK 7 添加的新的操作文件的类,它提供了提供了大量处理文件的方法,例如文件复制、读取、写入,获取文件属性、快捷遍历文件目录等,这些方法极大的方便了文件的操作,它的实现代码如下:

/*** 方法 6:使用 Files 写文件* @param filepath 文件目录* @param content  待写入内容* @throws IOException*/
public static void filesTest(String filepath, String content) throws IOException {Files.write(Paths.get(filepath), content.getBytes());
}

以上这些方法都可以实现文件的写入,那哪一种方法性能更高呢?接下来我们来测试一下。

5.性能测试

我们先来构建一个比较大的字符串,然后分别用以上 6 种方法来测试文件写入的速度,最后再把结果打印出来,测试代码如下:

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;public class WriteExample {public static void main(String[] args) throws IOException {// 构建写入内容StringBuilder stringBuilder = new StringBuilder();for (int i = 0; i < 1000000; i++) {stringBuilder.append("ABCDEFGHIGKLMNOPQRSEUVWXYZ");}// 写入内容final String content = stringBuilder.toString();// 存放文件的目录final String filepath1 = "/Users/mac/Downloads/io_test/write1.txt";final String filepath2 = "/Users/mac/Downloads/io_test/write2.txt";final String filepath3 = "/Users/mac/Downloads/io_test/write3.txt";final String filepath4 = "/Users/mac/Downloads/io_test/write4.txt";final String filepath5 = "/Users/mac/Downloads/io_test/write5.txt";final String filepath6 = "/Users/mac/Downloads/io_test/write6.txt";// 方法一:使用 FileWriter 写文件long stime1 = System.currentTimeMillis();fileWriterTest(filepath1, content);long etime1 = System.currentTimeMillis();System.out.println("FileWriter 写入用时:" + (etime1 - stime1));// 方法二:使用 BufferedWriter 写文件long stime2 = System.currentTimeMillis();bufferedWriterTest(filepath2, content);long etime2 = System.currentTimeMillis();System.out.println("BufferedWriter 写入用时:" + (etime2 - stime2));// 方法三:使用 PrintWriter 写文件long stime3 = System.currentTimeMillis();printWriterTest(filepath3, content);long etime3 = System.currentTimeMillis();System.out.println("PrintWriterTest 写入用时:" + (etime3 - stime3));// 方法四:使用 FileOutputStream  写文件long stime4 = System.currentTimeMillis();fileOutputStreamTest(filepath4, content);long etime4 = System.currentTimeMillis();System.out.println("FileOutputStream 写入用时:" + (etime4 - stime4));// 方法五:使用 BufferedOutputStream 写文件long stime5 = System.currentTimeMillis();bufferedOutputStreamTest(filepath5, content);long etime5 = System.currentTimeMillis();System.out.println("BufferedOutputStream 写入用时:" + (etime5 - stime5));// 方法六:使用 Files 写文件long stime6 = System.currentTimeMillis();filesTest(filepath6, content);long etime6 = System.currentTimeMillis();System.out.println("Files 写入用时:" + (etime6 - stime6));}/*** 方法六:使用 Files 写文件* @param filepath 文件目录* @param content  待写入内容* @throws IOException*/private static void filesTest(String filepath, String content) throws IOException {Files.write(Paths.get(filepath), content.getBytes());}/*** 方法五:使用 BufferedOutputStream 写文件* @param filepath 文件目录* @param content  待写入内容* @throws IOException*/private static void bufferedOutputStreamTest(String filepath, String content) throws IOException {try (BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(filepath))) {bufferedOutputStream.write(content.getBytes());}}/*** 方法四:使用 FileOutputStream  写文件* @param filepath 文件目录* @param content  待写入内容* @throws IOException*/private static void fileOutputStreamTest(String filepath, String content) throws IOException {try (FileOutputStream fileOutputStream = new FileOutputStream(filepath)) {byte[] bytes = content.getBytes();fileOutputStream.write(bytes);}}/*** 方法三:使用 PrintWriter 写文件* @param filepath 文件目录* @param content  待写入内容* @throws IOException*/private static void printWriterTest(String filepath, String content) throws IOException {try (PrintWriter printWriter = new PrintWriter(new FileWriter(filepath))) {printWriter.print(content);}}/*** 方法二:使用 BufferedWriter 写文件* @param filepath 文件目录* @param content  待写入内容* @throws IOException*/private static void bufferedWriterTest(String filepath, String content) throws IOException {try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filepath))) {bufferedWriter.write(content);}}/*** 方法一:使用 FileWriter 写文件* @param filepath 文件目录* @param content  待写入内容* @throws IOException*/private static void fileWriterTest(String filepath, String content) throws IOException {try (FileWriter fileWriter = new FileWriter(filepath)) {fileWriter.append(content);}}
}

在查看结果之前,我们先去对应的文件夹看看写入的文件是否正常,如下图所示:

从上述结果可以看出,每种方法都正常写入了 26 MB 的数据,它们最终执行的结果如下图所示:

从以上结果可以看出,字符流的操作速度最快,这是因为我们本次测试的代码操作的是字符串,所以在使用字节流时,需要先将字符串转换为字节流,因此在执行效率上不占优势。

从上述结果可以看出,性能最好的是带有缓冲区的字符串写入流 BufferedWriter,性能最慢的是 Files

PS:以上的测试结果只是针对字符串的操作场景有效,如果操作的是二进制的文件,那么就应该使用带缓冲区的字节流 BufferedOutputStream。

6.扩展知识:内容追加

以上代码会对文件进行重写,如果只想在原有的基础上追加内容,就需要在创建写入流的时候多设置一个 append 的参数为 true,比如如果我们使用 FileWriter 来实现文件的追加的话,实现代码是这样的:

public static void fileWriterMethod(String filepath, String content) throws IOException {// 第二个 append 的参数传递一个 true = 追加文件的意思try (FileWriter fileWriter = new FileWriter(filepath, true)) {fileWriter.append(content);}
}

如果使用的是 BufferedWriter 或 PrintWriter,也是需要在构建 new FileWriter 类时多设置一个 append 的参数为 true,实现代码如下:

try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filepath, true))) {bufferedWriter.write(content);
}

相比来说 Files 类要想实现文件的追加写法更加特殊一些,它需要在调用 write 方法时多传一个 StandardOpenOption.APPEND 的参数,它的实现代码如下:

Files.write(Paths.get(filepath), content.getBytes(), StandardOpenOption.APPEND);

7.总结

本文我们展示了 6 种写入文件的方法,这 6 种方法总共分为 3 类:字符流写入、字节流写入和 Files 类写入。其中操作最便利的是 Files 类,但它的性能不怎么好。如果对性能有要求就推荐使用带有缓存区的流来完成操作,如 BufferedWriter 或 BufferedOutputStream。如果写入的内容是字符串的话,那么推荐使用 BufferedWriter,如果写入的内容是二进制文件的话就推荐使用 BufferedOutputStream

参考 & 鸣谢

https://www.cnblogs.com/absfree/p/5415092.html


往期推荐

线程池的7种创建方式,强烈推荐你用它...


求求你,别再用wait和notify了!


硬核Redis总结,看这篇就够了!


关注我,每天陪你进步一点点!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/545107.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

单位矩阵属性(I ^ k = I)| 使用Python的线性代数

Prerequisites: 先决条件&#xff1a; numpy.matmul( ) matrix multiplication numpy.matmul()矩阵乘法 Identity matrix 身份矩阵 In linear algebra, the identity matrix, of size n is the n n square matrix with ones on the main diagonal and zeros elsewhere. It is…

linux 更改文件权限(子文件夹)

加入-R 参数&#xff0c;就可以将读写权限传递给子文件夹例如chmod -R 777 /home/mypackage那么mypackage 文件夹和它下面的所有子文件夹的属性都变成了777.777是读、写、执行权限...

JavaScript中带有示例的Math.cos()方法

JavaScript | Math.cos()方法 (JavaScript | Math.cos() Method) Math.cos() is a function in math library of JavaScript that is used to find the value of cosine of a number and return the value in radians. Math.cos()是JavaScript数学库中的一个函数&#xff0c;用…

JDK 16 即将发布,新特性速览!

你还能追上 Java 的更新速度吗&#xff1f;当开发者深陷 Java 8 版本之际&#xff0c;这边下一版本 Java 16 有了最新的消息&#xff0c;与 Java 15 一样&#xff0c;作为短期版本&#xff0c;Oracle 仅提供 6 个月的支持。根据发布计划&#xff0c;JDK 16 将在 12 月 10 日和 …

SQL手动注入

随着B/S模式应用开发的发展&#xff0c;使用这种模式编写应用程序的程序员也越来越多。但是由于这个行业的入门门槛不高&#xff0c;程序员的水平及经验也参差不齐&#xff0c;相当大一部分程序员在编写代码的时候&#xff0c;没有对用户输入数据的合法性进行判断&#xff0c;使…

最牛逼的 Java 项目实战,没有之一!

想要成长为高级开发&#xff0c;掌握更多层面的技术&#xff0c;兼顾深度和广度是毋庸置疑的。你肯定认为&#xff0c;我要认真努力的学习技术&#xff0c;丰富自己的技术栈&#xff0c;然后就可以成为一个优秀的高级开发了。但当你真正去学习之后就会发现&#xff0c;技术栈异…

ArcPad 10 的安装部署

ArcPad是安装在手持设备或者移动终端的一个外业ArcGIS产品&#xff0c;也就是说ArcPad是Esri的一款软件产品&#xff0c;而不是硬件设备哦。尽管不比ArcGIS Desktop功能复杂缤纷&#xff0c;可是对于野外作业、数据採集等工作来说&#xff0c;算是功能十分丰富了。 说到安装&am…

python 获取当前目录_如何在Python中获取当前的工作目录?

python 获取当前目录To get the current working directory in Python, there is a library function getcwd() in the os module. 为了在Python中获得当前的工作目录&#xff0c; os模块中有一个库函数getcwd() 。 getcwd() function does not accept any parameter and retu…

定时任务的实现原理,看完就能手撸一个!

一、摘要在很多业务的系统中&#xff0c;我们常常需要定时的执行一些任务&#xff0c;例如定时发短信、定时变更数据、定时发起促销活动等等。在上篇文章中&#xff0c;我们简单的介绍了定时任务的使用方式&#xff0c;不同的架构对应的解决方案也有所不同&#xff0c;总结起来…

linux localhost的修改

在论坛上看到有一些需要更改/proc/sys/kernel/hostname才行 linux修改主机名的方法 用hostname命令可以临时修改机器名&#xff0c;但机器重新启动之后就会恢复原来的值。 #hostname //查看机器名 #hostname -i //查看本机器名对应的ip地址 另外一种方法就是之久修改配置文件…

ARC和MRC 兼容的单例模式

一、ARC下的单例实现说明&#xff1a;在用户实例化的方法控制单次执行&#xff0c;同时开放单例的初始化方法。 -(instancetype)init{self[super init];if(self){static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{});}return self;}static id instance; (in…

scala 函数中嵌套函数_Scala中的嵌套函数 用法和示例

scala 函数中嵌套函数Scala中的嵌套函数 (Nested functions in Scala) A nested function is defined as a function which is defined inside the definition of another function. Programming languages like c, java, etc do not support nested functions but Scala does.…

Spring Boot集成Redis,这个坑把我害惨了!

最近项目中使用SpringBoot集成Redis&#xff0c;踩到了一个坑&#xff1a;从Redis中获取数据为null&#xff0c;但实际上Redis中是存在对应的数据的。是什么原因导致此坑的呢&#xff1f;本文就带大家从SpringBoot集成Redis、所踩的坑以及自动配置源码分析来学习一下SpringBoot…

www(apache)服务器的基本设置

1、安装yum install httpdyum install mysql-server*yum install mysql-develyum install phpyum install php-develyum install php-mysql2、httpd 和 mysql 的启动停止a、httpd/etc/init.d/httpd start/etc/init.d/httpd stop注&#xff1a;Apache自行提供一个script&#xf…

数据分析告诉你为什么Apple Watch会大卖?

摘要: 不管是无敌创意还是无聊鸡肋&#xff0c;苹果手表还是来了。眼下它上市在即&#xff0c;将率先登陆9个国家或地区——包括中国。根据凌晨发布会上公布的内容&#xff0c;Apple Watch采用全新的压感触屏和蓝宝石镜面&#xff0c;能够记录健康数据、同步手机信息 ...不管是…

putc函数_C语言中的putc()函数与示例

putc函数C语言中的putc()函数 (putc() function in C) The putc() function is defined in the <stdio.h> header file. putc()函数在<stdio.h>头文件中定义。 Prototype: 原型&#xff1a; int putc(const char ch, FILE *filename);Parameters: const char ch,…

编程中的21个坑,你占几个?

前言最近看了某客时间的《Java业务开发常见错误100例》&#xff0c;再结合平时踩的一些代码坑&#xff0c;写写总结&#xff0c;希望对大家有帮助&#xff0c;感谢阅读~1. 六类典型空指针问题包装类型的空指针问题级联调用的空指针问题Equals方法左边的空指针问题ConcurrentHas…

单词:131209

N&#xff1a; Acting 表演、演艺界 Cooperative 合作企业 Images 影像、图像 Information 信息 Projects 项目、工程 Role 角色、作用 Technology 技术 Victims 受害人 Characters 人物、性格 Desire 渴望、欲望 Diversity 多样性 Escape 逃走&#xff0c;逃脱 P…

Yii 2 美化 url

在使用 yii 1.x 中&#xff0c;我们都知道美化 url 是在配置文件中进行配置&#xff0c;那其实在 yii 2.x 中也还是一样的&#xff0c;只是配置的值不同了而已&#xff0c;接下来我们就看看如何在 yii 2.x 中美化 url 打开 config\web.php, 在 components 这个大数组里面添加以…

java timezone_Java TimeZone getAvailableIDs()方法与示例

java timezoneTimeZone类的getAvailableIDs()方法 (TimeZone Class getAvailableIDs() method) Syntax: 句法&#xff1a; public static String [] getAvailableIDs()public static String [] getAvailableIDs(int r_off);getAvailableIDs() method is available in java.uti…