文件操作和IO(下)
目录
字节流
流对象的两种关闭方式
InputStream
OutputStream
字符流
Reader
Writer
在上一篇文章中,主要介绍了在Java中如何进行文件系统操作,具体见博客: 文件操作和IO(上)在本篇中主要介绍如何对文件内容进行操作。
对于文件内容的操作,主要使用流对象来进行,具体可以分为两个大类:字节流和字符流,下面将详细展开两种方式的具体细节和使用示例。
字节流
字节流主要是针对二进制文件进行处理,其读写数据的基本单位是字节,主要包括InputStream 和 OutputStream 两个类。
流对象的两种关闭方式
在创建了 InputStream 或者 OutputStream 对象且执行完操作后,为了避免资源泄露,我们需要显式地关闭这些流对象。关闭流对象的操作应该始终在完成 I/O 操作后进行,以确保流所占用的资源(如内存、文件句柄等)能够得到及时释放。在 Java 中,关闭流的操作有常见的两种方式:
· 使用 Finally 块来关闭流
public static void main(String[] args) {InputStream inputStream = null;try {// 此处隐含了一个操作,打开文件inputStream = new FileInputStream("./test.txt");}catch (IOException e) {e.printStackTrace();} finally {try {inputStream.close();} catch (IOException e) {throw new RuntimeException(e);}}}
这种写法虽然可以保证严谨性,但其写法较为麻烦,因此我们通常使用另一种简单的方式来进行关闭,具体如下所示:
· 使用 try-with-resources 语句
public static void main(String[] args) {try(InputStream inputStream = new FileInputStream("./test.txt")) {} catch (IOException e) {e.printStackTrace();}}
在上述代码中,try() 中创建的资源可以是多个,使用 “;” 来进行分隔,try 的 {} 中的代码执行完毕后,最后都会自动执行 close 方法,因为 InputStream 这个抽象类实现了 Closeable 接口。
InputStream
在使用 InputStream 进行读取文件内容时,通常使用该抽象类的子类 FileInputStream 来进行操作。在使用其进行文件读取时,有三种读 read 方法,具体如下所示: 第一个 read 方法表示一次读取一个字节,返回值为该字节的内容;第二个 read 方法则是一次读取多个字节,它需要我们首先定义一个 buffer 数组,并将该数组传入 read 方法,buffer 数组在这里充当“输出型参数”;第三个 read 方法是在第二个方法的基础上扩展的,它允许我们指定一个字节数组的特定区间(即 [off, off + len)),并将数据填充到该区间中,而不是整个数组。
以下为分别使用这三种方法进行文件内容读取的具体示例:
/*** 一次读取一个字节* @param args*/public static void main(String[] args) {try(InputStream inputStream = new FileInputStream("./test.txt")) {while (true) {// 一次读一个字节int b = inputStream.read();if (b == -1) {// 读取完毕break;}// 表示字节,更习惯使用 16 进制来打印显示System.out.printf("0x%x\n", b);}} catch (IOException e) {e.printStackTrace();}}
/*** 使用 buffer 数组* @param args*/public static void main(String[] args) {try (InputStream inputStream = new FileInputStream("./test.txt")) {while (true) {byte[] buffer = new byte[1024];// 将硬盘中读到的对应数据,填充到 buffer 内存的字节数组中,尽可能填满 (一次 IO 尽可能填满)// 此处将 buffer 当成了“输出型参数”// n 返回值表示 read 操作,实际读取到多少个字节。int n = inputStream.read(buffer);if (n == -1) {break;}for (int i = 0; i < n; i++) {System.out.printf("0x%x\n", buffer[i]);}}} catch (IOException e) {e.printStackTrace();}}/*** 使用 buffer 数组限制范围读取* @param args*/public static void main(String[] args) {try (InputStream inputStream = new FileInputStream("./test.txt")) {while (true) {byte[] buffer = new byte[1024];// 将硬盘中读到的对应数据,填充到 buffer 内存的字节数组中,尽可能填满 (一次 IO 尽可能填满)// 此处将 buffer 当成了“输出型参数”// n 返回值表示 read 操作,实际读取到多少个字节。int n = inputStream.read(buffer, 0, 256);if (n == -1) {break;}for (int i = 0; i < n; i++) {System.out.printf("0x%x\n", buffer[i]);}}} catch (IOException e) {e.printStackTrace();}}
OutputStream
在使用 OutputStream 进行填写文件内容时,通常使用该抽象类的子类 FileOutputStream 来进行操作。在使用其进行文件内容填写时,与 InputStream 类似,有三种写 write 方法,三种方法的不同之处也与 read 方法类似,其具体使用示例如下所示:
/*** 一次写一个字节* @param args*/public static void main1(String[] args) {// true 表示追加写try (OutputStream outputStream = new FileOutputStream("./test.txt", true)) {// 此处的写操作,会把之前的内容清空(只要使用 OutputStream 打开文件,内容就没了),再进行写操作outputStream.write(0xe4);outputStream.write(0xbd);outputStream.write(0xa0);outputStream.write(0xe5);outputStream.write(0xa5);outputStream.write(0xbd);} catch (IOException e) {e.printStackTrace();}}/*** 一次写多个字节* @param args*/public static void main(String[] args) {// true 表示追加写try (OutputStream outputStream = new FileOutputStream("./test.txt", true)) {// 此处的写操作,会把之前的内容清空(只要使用 OutputStream 打开文件,内容就没了),再进行写操作byte[] buffer = new byte[] { (byte)0xe4, (byte)0xbd, (byte)0xa0, (byte)0xe5, (byte)0xa5, (byte)0xbd};outputStream.write(buffer);} catch (IOException e) {e.printStackTrace();}}
在下面这行代码中,在 new FileOutputStream 对象时除了传入文件路径外,还传入了 true。如果我们不传入 true,在进行写操作时,会将之前文件中的内容清空,再进行写操作,也就是说只要使用 outputStream 打开文件,文件中的内容就会清空,而传入 true 后,也就意味着将其指定为“追加写”的方式,其在保留源文件中内容的基础上,在其后面写入新的内容。
try (OutputStream outputStream = new FileOutputStream("./test.txt", true)) {}
字符流
InputStream 和OutputStream 读写数据是按照字节来进行操作的,当要读取字符(中文)时,需要我们手动的来区分哪几个字节是一个字符,再确保把这几个字节作为整体来进行写入,为了更方便的处理字符,Java引入了字符流。其包括 Reader 和 Writer两个类。
对于字符流的关闭方式与字节流一样,常用的方法包括使用 Finally 块来关闭流和使用 try-with-resources 语句两种方式,通常我们使用后者。
Reader
在使用 Reader进行读取文件内容时,通常使用该抽象类的子类 FileReader 来进行操作。在使用其进行文件读取时,有四种读 read 方法,具体如下所示:
第一个 read 方法表示一次读取一个字符,返回值为该字符的内容;第二个 read 方法则是一次读取多个字符,它需要我们首先定义一个 buffer 数组,并将该数组传入 read 方法,buffer 数组在这里充当“输出型参数”,返回值表示实际读到的字符个数;第三个 read 方法也是一次读取多个字符,其与第二种方法的不同之处在于,第二种方法使用 buffer 数组,而该方法是使用 CharBuffer,它是一个可以自动扩展的缓冲区,因此它可以容纳更多的数据;第四个 read 方法是在第二个方法的基础上扩展的,它允许我们指定一个字符数组的特定区间(即 [off, off + len)),并将数据填充到该区间中,而不是整个数组。
常用的使用方式如下所示:
/*** reader 一次读取一个字符* @param args*/public static void main(String[] args) {try (Reader reader = new FileReader("./test.txt")) {while (true) {int c = reader.read();if (c == -1) {break;}char ch = (char)c;System.out.println(ch);}} catch (IOException e) {e.printStackTrace();}}/*** 一次读取多个字符* @param args*/public static void main(String[] args) {try (Reader reader = new FileReader("./test.txt")) {char[] buffer = new char[1024];int n = reader.read(buffer);System.out.println(n);for (int i = 0; i < n; i++) {System.out.println(buffer[i]);}} catch (IOException e) {e.printStackTrace();}}/*** 一次读取多个字符(指定 buffer 数组范围)* @param args*/public static void main(String[] args) {try (Reader reader = new FileReader("./test.txt")) {char[] buffer = new char[1024];int n = reader.read(buffer, 0, 256);System.out.println(n);for (int i = 0; i < n; i++) {System.out.println(buffer[i]);}} catch (IOException e) {e.printStackTrace();}}
Writer
在使用 Reader进行写入文件内容时,通常使用该抽象类的子类 FileWriter 来进行操作。在使用其进行文件写入时,有五种写入 write方法,具体如下所示:
第一种 write 表示一次可以写入一个字符串,第二种 write 表示一次写入一个字符;第三种 write 表示一次写入一个 buffer 数组的字符;第四种和第五种,都是根据第二种和第三种方法限定了其一次写入的范围。
其常用的使用方式如下所示:
/*** 一次写入一个字符* @param args*/public static void main(String[] args) {try (Writer writer = new FileWriter("./test.txt", true)) {writer.write('你');} catch (IOException e) {e.printStackTrace();}} /*** 一次写入多个字符* @param args*/public static void main(String[] args) {try (Writer writer = new FileWriter("./test.txt", true)) {char[] buffer = {'你', '好', '呀'};writer.write(buffer);} catch (IOException e) {e.printStackTrace();}}/*** 一次写入一个字符串* @param args*/public static void main(String[] args) {try (Writer writer = new FileWriter("./test.txt", true)) {writer.write("你好世界");} catch (IOException e) {e.printStackTrace();}}