别乱用了!文件写入的6种方法

发表于 2年以前  | 总阅读数:446 次

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

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

0.什么是流?

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

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

1.什么是字节流?

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

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

其中 InputStream 用于读操作,而 OutputStream 用于写操作。

2.什么是字符流?

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

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

3.流的分类

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

① 按流向分类

  • 输出流:OutputStreamWriter 为基类。
  • 输入流:InputStreamReader 为基类。

② 根据传输数据单位分类

  • 字节流:OutputStreamInputStream 为基类。
  • 字符流:WriterReader 为基类。

③ 根据功能分类

  • 字节流:可以从或向一个特定的地方(节点)读写数据。
  • 处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。

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);  
    }  
}  

如果使用的是 BufferedWriterPrintWriter,也是需要在构建 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 类,但它的性能不怎么好。如果对性能有要求就推荐使用带有缓存区的流来完成操作,如 BufferedWriterBufferedOutputStream。如果写入的内容是字符串的话,那么推荐使用 BufferedWriter,如果写入的内容是二进制文件的话就推荐使用 BufferedOutputStream

参考 & 鸣谢

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

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/1WkBuYqYZFJyIbU3VL_CsA

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237231次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8065次阅读
 目录