在 Java 的 IO 体系里,java.io.Writer 类占据着基础性地位,它作为 Java IO API 中所有 Writer 子类的抽象基类,为字符输出流定义了一系列通用的方法与属性。Writer 与 OutputStream 存在相似之处,不过二者有着本质区别:
Writer 专注于字符层面的处理,专门用于写入文本内容;
OutputStream 则面向字节,主要用于写入诸如图片、音频、视频等各类原始字节数据。
也就是说,当涉及文本写入操作时,Writer 是更为合适的选择;而针对原始字节数据的写入需求, OutputStream 则是更好的工具 。
在实际编程场景中,你一般不会直接使用 Writer 类,而是选用它的子类。这是因为这些子类针对不同的应用场景进行了优化,提供了更具针对性的功能。像 OutputStreamWriter、CharArrayWriter、FileWriter 等,都是 Writer 的子类。下面为你详细列出常见的 Writer子类:
FileWriter 用于将字符数据写入文件。它是对文件进行字符输出操作的便捷类,默认使用系统的默认字符编码。可以直接指定文件名来创建对象,能方便地将文本内容写入到指定文件中。例如,当你有一段文本信息需要保存到文件时,就可以使用 FileWriter。
OutputStreamWriter 是字符流通向字节流的桥梁,可使用指定的字符编码将字符转换为字节。当你需要将字符数据以特定的编码格式写入到字节输出流时,就会用到它。比如,你要将字符数据以 UTF-8 编码写入到一个文件输出流中,就可以借助 OutputStreamWriter 实现。
BufferedWriter 为其他字符输出流添加缓冲功能,提高写入效率。它会先将数据写入到缓冲区,当缓冲区满或者调用 flush() 方法时,才将数据真正写入到目标输出流中。在处理大量字符写入操作时,使用 BufferedWriter 可以显著减少 I/O 操作次数,提升性能。
PipedWriter 用于线程间的字符数据通信,通常与 PipedReader 配合使用。一个线程通过PipedWriter 写入数据,另一个线程通过 PipedReader 读取数据,从而实现线程间的字符数据传递。
CharArrayWriter 用于将字符数据写入到字符数组中。它内部维护了一个字符数组,写入的数据会存储在这个数组里。当你需要将字符数据临时存储在内存中的字符数组中时,CharArrayWriter 是一个不错的选择。
FilterWriter 是所有过滤字符输出流的抽象基类,用于对其他字符输出流进行过滤和增强处理。你可以通过继承 FilterWriter 来实现自定义的过滤逻辑,对要写入的数据进行预处理或修改。
StringWriter 用于将字符数据写入到字符串缓冲区中。它将所有写入的数据收集到一个字符串中,最终可以通过 toString() 方法获取完整的字符串内容。适用于需要动态生成字符串内容的场景。
PrintWriter 提供了方便的格式化输出功能,可用于将各种数据类型以格式化的方式写入到输出流中。它可以像 System.out.println() 一样方便地进行输出操作,支持多种数据类型的输出,并且可以自动刷新缓冲区,在控制台输出或日志记录等场景中应用广泛。
Writer 通常与特定的数据目标相连接,这些数据目标涵盖了文件、字符数组、网络套接字等多种类型 。借助这种连接,Writer 能够将字符数据高效地传输至对应的目标位置,以满足不同场景下数据输出的需求 。
在 Java 中,Writer 类的 write(int) 方法会提取传入 int 值的低 16 位,并将其作为单个字符写入到 Writer所关联的数据目标处。以下为您展示一个使用 Java 的 Writer 写入单个字符的示例:
package com.hxstrive.java_io; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; public class FileWriterExample { public static void main(String[] args) throws IOException { try(Writer writer = new FileWriter("output.txt")) { writer.write('A'); writer.write("Hello, World!"); } } }
在 Java 中,Writer 还提供了 write(char[]) 这一实用方法。此方法的功能是将一个字符数组写入到 Writer 所连接的数据目标处。当 write(char[]) 方法执行完毕后,会返回实际成功写入到目标位置的字符数量。例如:
package com.hxstrive.java_io; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; public class FileWriterExample2 { public static void main(String[] args) throws IOException { try(Writer writer = new FileWriter("output.txt")) { char[] chars = {'a', 'b', 'c'}; writer.write(chars); // 从数组下标1开始写入,写入2个字符 writer.write(chars, 1, 2); } } }
在 Java 编程里,若要使用 Writer 进行数据写入,相较于一次只写入一个字符,写入字符数组的方式效率要高得多。这种效率提升十分显著,最高能达到 10 倍甚至更高。所以,为提高性能,建议优先使用 write(char[]) 方法。
不过,实际的速度提升情况会因运行 Java 代码的计算机的底层操作系统和硬件配置而异。具体而言,速度提升幅度受内存读写速度、硬盘读写速度、缓冲区大小等因素影响。若 Writer 是将数据写入本地文件,硬盘读写速度和缓冲区大小将成为关键;若数据需通过网络传输到其他设备,网卡速度和网络缓冲区大小则会起到决定性作用。
在 Java 中,借助 BufferedWriter 对 Writer 进行封装,能够实现透明化的缓冲功能。当你向 BufferedWriter 写入数据时,所有字节都会先被存储到 BufferedWriter 内部的字节数组缓冲区中。待缓冲区填满后,系统会一次性将缓冲区中的数据刷新到底层的 Writer 里。如此一来,减少了与底层 Writer 的频繁交互,提升了写入性能。例如:
package com.hxstrive.java_io; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; public class FileWriterExample3 { public static void main(String[] args) throws IOException { try(Writer writer = new BufferedWriter(new FileWriter("output.txt"), 1024*8)) { writer.write("hello world"); } } }
Writer 类的 flush() 方法发挥着重要作用,它能够将已写入 Writer 的全部数据强制刷新至底层的数据目标。以 FileWriter 为例,当你使用 FileWriter 写入字节数据时,这些数据可能并未立刻完整地写入磁盘。即便你的 Java 代码已经执行了向 FileWriter 写入数据的操作,数据也可能只是暂时缓存在操作系统的内存里。
调用 flush() 方法,能保证所有缓冲的数据会被立即刷新(即写入)到最终的目标位置,这个目标位置可能是磁盘、网络,或者是 Writer 所关联的其他数据目的地。例如:
try(Writer writer = new BufferedWriter(new FileWriter("output.txt"), 1024*8)) { writer.write("hello world"); writer.flush(); }
当你完成向 Writer 写入字符数据的操作后,应及时将其关闭。这是因为及时关闭 Writer 可以释放系统资源,避免资源浪费和潜在的资源泄漏问题。你可以通过调用 Writer 的 close() 方法来实现关闭操作。
以下是一个关闭 Java Writer 的示例:
Writer writer = null; try { writer = new FileWriter("output.txt"); writer.write("Hello, Java!"); } finally { if(null != writer) { writer.close(); } }
你还可以使用 Java 7 中引入的 try-with-resources 结构。例如:
try(Writer writer = new BufferedWriter(new FileWriter("output.txt"), 1024*8)) { writer.write("hello world"); }
注意,若 Writer 是在 try 代码块的括号内声明的,那么一旦程序执行完 try 代码块,就会自动调用 Writer 的 close() 方法。这一机制相当实用,即便在 try 代码块内部运行过程中出现异常,close() 方法也会在异常沿着调用栈向上传播之前被调用,从而确保资源能够及时释放。