前面介绍了文件的信息获取、管理操作,以及目录下的文件遍历,那么文件内部数据又是怎样读写的呢?这正是本文所要阐述的内容。File工具固然强大,但它并不能直接读写文件,而要借助于其它工具方能开展读写操作。对于写操作来说,需要通过文件写入器FileWriter搭配File工具才行。创建写入器对象的过程很简单,只要在调用FileWriter的构造方法时传递文件对象即可,接着就能调用写入器的下列方法向文件写入数据了。
write:往文件写入字符串。注意该方法存在多个同名的重载方法。
append:也是往文件写入字符串。按字面意思,append方法像是往文件末尾追加字符串,然而并非如此,append方法与write方法的写入位置是同样的。二者的区别在于,append方法会把空指针当作“null”写入文件,而write方法不支持写入空指针。
close:关闭文件写入器。
把文件的一系列写入操作串起来,形成以下流程的写文件代码,注意文件写入器的几个方法均需捕捉输入输出异常IOException:

	private static String mFileName = "D:/test/aac.txt";
// 存在隐患的写文件代码。发生异常时不会关闭文件
private static void writeFileSimple() {
String str = "白日依山尽,黄河入海流。\n";
File file = new File(mFileName); // 创建一个指定路径的文件对象
try {
FileWriter writer = new FileWriter(file); // 创建一个文件写入器
writer.write(str); // 往文件写入字符串
writer.close(); // 关闭文件
} catch (IOException e) {
e.printStackTrace();
}
}

上面的例子代码看似结构完整,实则存在不小的隐患。因为close方法只有在正常分支才会被调用,异常分支并没有调用该方法,如此一来,一旦异常发生,已经打开的文件将不会正常关闭,结果可能导致文件损坏。解决办法是在try/catch后面补充finally语句,在finally语句块中添加close方法的调用,于是改进后的写文件代码示例如下:

	// 改进后的写文件代码。在finally代码块中关闭文件
private static void writeFileWithFinally() {
String str = "白日依山尽,黄河入海流。\n";
File file = new File(mFileName); // 创建一个指定路径的文件对象
FileWriter writer = null;
try {
writer = new FileWriter(file); // 创建一个文件写入器
writer.write(str); // 往文件写入字符串
} catch (IOException e) {
e.printStackTrace();
} finally { // 无论是否遇到异常,都要释放文件资源
if (writer != null) {
try {
writer.close(); // 关闭文件
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

改进后的代码确实消除了文件异常关闭的风险,可是整个代码一下子多出好些行,着实变得拖沓不小。为此从Java7开始,try语句支持“try-with-resources”的表达式,意思是携带某些资源去尝试干活,并在尝试结束后自动释放这些资源。具体做法是在try后边添加圆括号,并在圆括号内部填写资源对象的创建语句,只要这个资源类实现了AutoCloseable接口,程序便会在try/catch结束后自动调用该资源的close方法。这样就无需补充finally代码块,也无需显式调用close方法了,采取资源自动管理的优化代码如下所示:

	// 采取自动释放资源的写文件代码
private static void writeFileWithTry() {
String str = "白日依山尽,黄河入海流。\n";
File file = new File(mFileName); // 创建一个指定路径的文件对象
// Java7的新增功能,在try(...)里声明的资源,会在try/catch结束后自动释放。
// 相当于编译器自动补充了finally代码块中的资源释放操作。
// 资源类必须实现java.lang.AutoCloseable接口,这样close方法才会由系统调用。
// 一般说来,文件I/O、套接字、数据库连接等均已实现该接口。
try (FileWriter writer = new FileWriter(file)) {
writer.write(str); // 往文件写入字符串
} catch (IOException e) {
e.printStackTrace();
}
}

由此可见,使用“try-with-resources”方式的代码顿时减少到了寥寥几行,可谓程序员的一大福音。

和写操作对应的是读操作,读文件用到了文件读取器FileReader,它依然与File工具搭档合作。创建读取器对象也要在调用FileReader的构造方法时传递文件对象,读取器提供的调用方法列举如下:
skip:跳过若干字符。注意FileReader的skip方法跳过的是字符数,不是字节数。
read:从文件读取数据到字节数组。注意该方法存在多个同名的重载方法。
close:关闭文件读取器。
通过文件读取器从文件中读取数据的常规代码示例如下:

	// 存在隐患的读文件代码。发生异常时不会关闭文件
private static void readFileSimple() {
File file = new File(mFileName); // 创建一个指定路径的文件对象
try {
FileReader reader = new FileReader(file); // 创建一个文件读取器
//reader.skip(2); // 字符流的skip方法跳过的是字符数,不是字节数
char[] temp = new char[(int) file.length()];
reader.read(temp); // 从文件读取数据到字节数组
String content = new String(temp); // 把字符数组转为字符串
System.out.println("content="+content);
reader.close(); // 关闭文件
} catch (IOException e) {
e.printStackTrace();
}
}

以上的读文件代码仍然没有考虑到异常发生时候的资源释放问题,因而需要增加finally语句加以改进,在finally代码块中调用close方法关闭文件,改进后的代码如下所示:

	// 改进后的读文件代码
private static void readFileWithFinally() {
File file = new File(mFileName); // 创建一个指定路径的文件对象
FileReader reader = null;
try {
reader = new FileReader(file); // 创建一个文件读取器
char[] temp = new char[(int) file.length()];
reader.read(temp); // 从文件读取数据到字节数组
String content = new String(temp); // 把字符数组转为字符串
System.out.println("content="+content);
} catch (IOException e) {
e.printStackTrace();
} finally { // 无论是否遇到异常,都要释放文件资源
if (reader != null) {
try {
reader.close(); // 关闭文件
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

同FileWriter一样,FileReader也实现了AutoCloseable接口,意味着它同样适用于try-with-resources的规则。那么将文件读取器的创建语句放到try之后的圆括号中,之前的finally语句块可以整个删除,因为程序会在try/catch结束后自动释放读取器资源。此时采取自动释放资源的读文件代码变成了下面这样:

	// 采取自动释放资源的读文件代码
private static void readFileWithTry() {
File file = new File(mFileName); // 创建一个指定路径的文件对象
// Java7的新增功能,在try(...)里声明的资源,会在try/catch结束后自动释放。
// 相当于编译器自动补充了finally代码块中的资源释放操作。
// 资源类必须实现java.lang.AutoCloseable接口,这样close方法才会由系统调用。
// 一般说来,文件I/O、套接字、数据库连接等均已实现该接口。
try (FileReader reader = new FileReader(file)) {
char[] temp = new char[(int) file.length()];
reader.read(temp); // 从文件读取数据到字节数组
String content = new String(temp); // 把字符数组转为字符串
System.out.println("content="+content);
} catch (IOException e) {
e.printStackTrace();
}
}

别看上面的代码还有好几行,要是全部去掉注释真没多少行。省时省力的便捷写法理应大力推广,若无特殊情况,往后的相关代码将一律采用以上try-with-resources的写法。

更多Java技术文章参见《Java开发笔记(序)章节目录

Java开发笔记(八十五)通过字符流读写文件的更多相关文章

  1. Java开发笔记(九十五)NIO配套的文件工具Files

    NIO不但引进了高效的文件通道,而且新增了更加好用的文件工具家族,包括路径组工具Paths.路径工具Path.文件组工具Files.先看路径组工具Paths,该工具提供了静态方法get,输入某个文件的 ...

  2. Java开发笔记(十五)短路逻辑运算的优势

    前面提到逻辑运算只能操作布尔变量,这其实是不严谨的,因为经过Java编程实现,会发现“&”.“|”.“^”这几个逻辑符号竟然可以对数字进行运算.譬如下面的代码就直接对数字分别开展了“与”.“或 ...

  3. 第31天学习打卡(File类。字符流读写文件)

    File类 概念 文件,文件夹,一个file对象代表磁盘上的某个文件或者文件夹 构造方法  File(String pathname) File(String parent,String child) ...

  4. Java开发笔记(八十六)通过缓冲区读写文件

    前面介绍了利用文件写入器和文件读取器来读写文件,因为FileWriter与FileReader读写的数据以字符为单位,所以这种读写文件的方式被称作“字符流I/O”,其中字母I代表输入Input,字母O ...

  5. Java开发笔记(一百五十)C3P0连接池的用法

    JDBC既制定统一标准兼容了多种数据库,又利用预报告堵上了SQL注入漏洞,照理说已经很完善了,可是人算不如天算,它在性能方面不尽如人意.问题出在数据库连接的管理上,按照正常流程,每次操作完数据库,都要 ...

  6. Java开发笔记(三十一)字符类型的表达

    前面介绍的Java编程,要么是与数字有关的计算,要么是与逻辑有关的推理,充其量只能实现计算器和状态机.若想让Java运用于更广阔的业务领域,就得使其支撑更加血肉丰满的业务场景,而丰满的前提是能够表达大 ...

  7. Java开发笔记(三十三)字符包装类型

    正如整型int有对应的包装整型Integer那样,字符型char也有对应的包装字符型Character.初始化字符包装变量也有三种方式,分别是:直接用等号赋值.调用包装类型的valueOf方法.使用关 ...

  8. Java开发笔记(一百五十一)Druid连接池的用法

    C3P0连接池自诞生以来在Java Web领域反响甚好,业已成为hibenate框架推荐的连接池.谁知人红是非多,C3P0在大型应用场合中暴露了越来越多的局限性,包括但不限于下列几点:1.C3P0管理 ...

  9. Java开发笔记(九十)对象序列化及其读写

    有些时候,开发者想把程序运行过程中的数据临时保存到文件,可是前面介绍的字符流和字节流,要么用来读写文本字符串,要么用来读写字节数组,并不能直接保存某个对象信息,因为对象里面包括成员属性和成员方法,单就 ...

随机推荐

  1. [dotnet core]落地微服务特色的DevOps管道,持续集成/部署到kubernetes。

    目录 前言 目标 工具 - 最小的学习成本 方案 - 愿景 1. 持续集成 - CI 2. 持续部署 - CD 部署环境 1. 部署gitlab-runner 2. 注册gitlab-runner 搭 ...

  2. eShopOnContainers 知多少[9]:Ocelot gateways

    引言 客户端与微服务的通信问题永远是一个绕不开的问题,对于小型微服务应用,客户端与微服务可以使用直连的方式进行通信,但对于对于大型的微服务应用我们将不得不面对以下问题: 如何降低客户端到后台的请求数量 ...

  3. Linux - 修改Cent OS系统的的hostname、配置DNS映射

    目录 1 修改方式 2 扩展: 配置DNS映射 本篇文章中, 示例设计到的操作系统是CentOS 6.5. 1 修改方式 ① 命令hostname onepiece -- 运行后设置立即生效, 但要在 ...

  4. Netty源码—六、tiny、small内存分配

    tiny内存分配 tiny内存分配流程: 如果申请的是tiny类型,会先从tiny缓存中尝试分配,如果缓存分配成功则返回 否则从tinySubpagePools中尝试分配 如果上面没有分配成功则使用a ...

  5. myeclipse附加源码进行查看的方法

    在编程过程中,有可能需要用到看源码的情况,那么怎么进行添加源码呢,这里做下记录 首先,先下载javaEE源码(可在网上自由下载) 1.在HttpServlet上右键-->Open Declara ...

  6. 《Jave并发编程的艺术》学习笔记(1-2章)

    Jave并发的艺术 并发编程的挑战 上下文切换 CPU通过时间片分配算法来循环执行任务,当前时间片执行完之后会切换到下一个任务.但是,切换会保存上一个任务的状态,一遍下次切换回这个任务时,可以再次加载 ...

  7. 关于页面传参,decodeURI和decodeURIComponent

    之前写过一个关于页面传参的,但是是前端相对于自己的页面做的跳转,也就是页面1,跳转到页面2,里面带的参数.这里可以参考我上一篇文章,包括里面参数中如果有数组和json格式的情况.但是需要注意的是,我前 ...

  8. Windows Server 2016-批量新建域用户(二)

    前几个章节我们讲到Windows Server 2016-图形化新建域用户(一),本章节我们简单讲解下如何通过命令批量创建域用户,以便高效完成日常工作中实际批量创建用户需求,内容涉及dsadd use ...

  9. 关于OSError: [WinError 10038] 在一个非套接字上尝试了一个操作。

    在使用socket的时候,写了一个while循环,就报错了.结果如下: OSError: [WinError 10038] 在一个非套接字上尝试了一个操作. 代码 import socket impo ...

  10. PuppeteerSharp: 更友好的 Headless Chrome C# API

    前端就有了对 headless 浏览器的需求,最多的应用场景有两个 UI 自动化测试:摆脱手工浏览点击页面确认功能模式 爬虫:解决页面内容异步加载等问题 也就有了很多杰出的实现,前端经常使用的莫过于 ...