文章目录

  • IO 流
    • 什么是 IO
    • 字节流
      • 字节流概念
      • 字节流读写文件
      • 文件的拷贝
      • 字节流的缓冲区
      • 装饰设计模式
      • 字节缓冲流
    • 字符流
      • 字符流定义及基本用法
      • 字符流操作文件
      • 转换流
    • File 类
      • File 类的常用方法
      • 遍历目录下的文件
      • 删除文件及目录
    • 字符编码
      • 常用字符集
  • 微信公众号

IO 流

什么是 IO

大多数应用程序都需要实现与设备之间的数据传输,例如键盘可以输入数据,显示器可以显示程序的运行结果等。在 Java 中,将这种通过不同输入输出设备(键盘,内存,显示器,网络等)之间的数据传输抽象表述为“流”,程序允许通过流的方式与输入输出设备进行数据传输。Java 中的“流”都位于 java.io 包中,称为 IO(输入输出)流。

IO 流有很多种,按照操作数据的不同,可以分为字节流和字符流,按照数据传输方向的不同又可分为输入流和输出流,程序从输入流中读取数据,向输出流中写入数据。在 IO 包中,字节流的输入输出流分别用 java.io.InputStream 和 java.io.OutputStream 表示,字符流的输入输出流分别用 java.io.Reader 和 java.io.Writer 表示,具体分类如图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6cXJDz5g-1642058941599)(http://47.107.171.232/easily-j/images/20190111/9766628d-b101-45b0-b592-9af06cde7531.png)]

字节流

字节流概念

在计算机中,无论是文本、图片、音频还是视频,所有的文件都是以二进制(字节)形式存在,IO 流中针对字节的输入输出提供了一系列的流,统称为字节流。字节流是程序中最常用的流,根据数据的传输方向可将其分为字节输入流和字节输出流。在 JDK 中,提供了两个抽象类 InputStream 和 OutputStream,它们是字节流的顶级父类,所有的字节输入流都继承自 InputStream,所有的字节输出流都继承自 OutputStream。为了方便理解,可以把 InputStream 和 OutputStream 比作两根“水管”,如图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eZ4OSMv2-1642058941601)(http://47.107.171.232/easily-j/images/20190111/bcfbd193-8f57-444c-a6f4-d8e0d7ac28ea.png)]

图中,InputStream 被看成一个输入管道,OutputStream 被看成一个输出管道,数据通过 InputStream 从源设备输入到程序,通过 OutputStream 从程序输出到目标设备,从而实现数据的传输。由此可见,IO 流中的输入输出都是相对于程序而言的。

在 JDK 中,InputStream 和 OutputStream 提供了一系列与读写数据相关的方法,接下来先来了解一下 InputStream 的常用方法,如表所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FwInIid8-1642058941603)(http://47.107.171.232/easily-j/images/20190111/961a30d6-deb2-4a38-bea3-2c417c2b222f.png)]

表中列举了 InputStream 的四个常用方法。前三个 read()方法都是用来读数据的,其中,第一个 read()方法是从输入流中逐个读入字节,而第二个和第三个 read()方法则将若干字节以字节数组的形式一次性读入,从而提高读数据的效率。在进行 IO 流操作时,当前 IO 流会占用一定的内存,由于系统资源宝贵,因此,在 IO 操作结束后,应该调用 close()方法关闭流,从而释放当前 IO 流所占的系统资源。

与 InputStream 对应的是 OutputStream。OutputStream 是用于写数据的,因此 OutputStream 提供了一些与写数据有关的方法,如表所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qyl5faff-1642058941604)(http://47.107.171.232/easily-j/images/20190111/d2835b5e-42af-4e74-8869-ae9775214172.png)]

表中,列举了 OutputStream 类的五个常用方法。前三个是重载的 write()方法,都是用于向输出流写入字节,其中,第一个方法逐个写入字节,后两个方法是将若干个字节以字节数组的形式一次性写入,从而提高写数据的效率。flush()方法用来将当前输出流缓冲区(通常是字节数组)中的数据强制写入目标设备,此过程称为刷新。close()方法是用来关闭流并释放与当前 IO 流相关的系统资源。

InputStream 和 OutputStream 这两个类虽然提供了一系列和读写数据有关的方法,但是这两个类是抽象类,不能被实例化,因此,针对不同的功能,InputStream 和 OutputStream 提供了不同的子类,这些子类形成了一个体系结构,如图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mnDjt0W2-1642058941606)(http://47.107.171.232/easily-j/images/20190111/dd8d76df-53c9-480b-a7f2-b75ee2db7c7c.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UJtxL5o4-1642058941607)(http://47.107.171.232/easily-j/images/20190111/baea8226-fb97-432b-8b3f-d970844abd75.png)]

从图中可以看出,InputStream 和 OutputStream 的子类有很多是大致对应的,比如 ByteArrayInputStream 和 ByteArrayOutputStream,FileInputStream 和 FileOutputStream 等。图中所列出的 IO 流都是程序中很常见的,接下来将逐步为大家讲解这些流的具体用法。

字节流读写文件

由于计算机中的数据基本都保存在硬盘的文件中,因此操作文件中的数据是一种很常见的操作。在操作文件时,最常见的就是从文件中读取数据并将数据写入文件,即文件的读写。针对文件的读写,JDK 专门提供了两个类,分别是 FileInputStream 和 FileOutputStream。

FileInputStream 是 InputStream 的子类,它是操作文件的字节输入流,专门用于读取文件中的数据。由于从文件读取数据是重复的操作,因此需要通过循环语句来实现数据的持续读取。接下来通过一个案例来实现字节流对文件数据的读取,首先在 D 盘目录下创建一个文本文件 IO.txt,在文件中输入内容“小海绵”,具体代码如例所示。

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;public class IOTest {public static void main(String[] args) {try {// 创建一个文件字节输入流FileInputStream in = new FileInputStream("D:/IO.txt");int b = 0; // 定义一个int 类型的变量b,记住每次读取的一个字节while (true) {b = in.read(); // 变量b 记住读取的一个字节if (b == -1) { // 如果读取的字节为-1,跳出while 循环break;}System.out.println(b); // 否则将b 写出}in.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
}

运行结果:

208
161
186
163
195
224

例中,创建的字节流 FileInputStream 通过 read()方法将当前目录文件“D://IO.txt”中的数据读取并打印。通常情况下读取文件应该输出字符,之所以输出数字是因为硬盘上的文件是以字节的形式存在的,在“IO.txt”文件中,字符’小’,‘海’,'绵’各占 2 个字节,因此,最终结果显示的就是文件中的六个字节所对应的十进制数。

与 FileInputStream 对应的是 FileOutputStream。FileOutputStream 是 OutputStream 的子类,它是操作文件的字节输出流,专门用于把数据写入文件。接下来通过一个案例来演示如何将数据写入文件,如例所示。

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;public class IOTest {public static void main(String[] args) {try {// 创建一个文件字节输出流FileOutputStream out = new FileOutputStream("D:/example.txt");String str = "小海绵";byte[] b = str.getBytes();for (int i = 0; i < b.length; i++) {out.write(b[i]);}out.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
}

程序运行后,会在 D 盘目录下生成一个新的文本文件 example.txt,打开此文件,文件内容为小海绵。

通过运行结果可以看出,通过 FileOutputStream 写数据时,自动创建了文件 example.txt,并将数据写入文件。需要注意的是,如果是通过 FileOutputStream 向一个已经存在的文件中写入数据,那么该文件中的数据首先会被清空,再写入新的数据。若希望在已存在的文件内容之后追加新内容,则可使用 FileOutputStream 的构造函数 FileOutputStream(StringfileName,booleanappend)来创建文件输出流对象,并把 append 参数的值设置为 true。接下来通过一个案例来演示如何将数据追加到文件末尾,如例所示。

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;public class IOTest {public static void main(String[] args) {try {OutputStream out = new FileOutputStream("D:/example.txt", true);String str = "炒鸡帅";byte[] b = str.getBytes();for (int i = 0; i < b.length; i++) {out.write(b[i]);}out.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
}

查看 D://example.txt 文件,文件中的内容为 小海绵炒鸡帅。

文件的拷贝

在应用程序中,IO 流通常都是成对出现的,即输入流和输出流一起使用。例如文件的拷贝就需要通过输入流来读取文件中的数据,通过输出流将数据写入文件。接下来通过一个案例来演示如何进行文件内容的拷贝,首先在 D 盘创建文件夹 one,和 two,然后在 one 文件夹中存放一个“example.txt”文件,拷贝文件的代码如例所示。

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;public class IOTest {public static void main(String[] args) {try {// 创建一个字节输入流,用于读取当前目录下source 文件夹中的mp3 文件InputStream in = new FileInputStream("D:/one/example.txt");// 创建一个文件字节输出流,用于将读取的数据写入target 目录下的文件中OutputStream out = new FileOutputStream("D:/two/example.txt");int len; // 定义一个int 类型的变量len,记住每次读取的一个字节long begintime = System.currentTimeMillis(); // 获取拷贝文件前的系统时间while ((len = in.read()) != -1) { // 读取一个字节并判断是否读到文件末尾out.write(len); // 将读到的字节写入文件}long endtime = System.currentTimeMillis(); // 获取文件拷贝结束时的系统时间System.out.println("拷贝文件所消耗的时间是: " + (endtime - begintime) + "毫秒");in.close();out.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
}

运行结果:

拷贝文件所消耗的时间是: 2毫秒

在拷贝过程中,通过 while 循环将字节逐个进行拷贝。每循环一次,就通过 FileInputStream 的 read()方法读取一个字节,并通过 FileOutputStream 的 write()方法将该字节写入指定文件,循环往复,直到 len 的值为-1,表示读取到了文件的末尾,结束循环,完成文件的拷贝。程序运行结束后,会在命令行窗口打印拷贝文件所消耗的时间。

字节流的缓冲区

虽然上一个案例实现了文件的拷贝,但是一个字节一个字节的读写,需要频繁的操作文件,效率非常低,这就好比从北京运送烤鸭到上海,如果有一万只烤鸭,每次运送一只,就必须运输一万次,这样的效率显然非常低。为了减少运输次数,可以先把一批烤鸭装在车厢中,这样就可以成批的运送烤鸭,这时的车厢就相当于一个临时缓冲区。当通过流的方式拷贝文件时,为了提高效率也可以定义一个字节数组作为缓冲区。在拷贝文件时,可以一次性读取多个字节的数据,并保存在字节数组中,然后将字节数组中的数据一次性写入文件。接下来通过一个案例来学习如何使用缓冲区拷贝文件,如例所示。

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;public class IOTest {public static void main(String[] args) {try {// 创建一个字节输入流,用于读取当前目录下source 文件夹中的mp3 文件InputStream in = new FileInputStream("D:/one/example.txt");// 创建一个文件字节输出流,用于将读取的数据写入当前目录的target 文件中OutputStream out = new FileOutputStream("D:/one/example.txt");// 以下是用缓冲区读写文件byte[] buff = new byte[1024]; // 定义一个字节数组,作为缓冲区// 定义一个int 类型的变量len 记住读取读入缓冲区的字节数int len;long begintime = System.currentTimeMillis();while ((len = in.read(buff)) != -1) { // 判断是否读到文件末尾out.write(buff, 0, len); // 从第一个字节开始,向文件写入len 个字节}long endtime = System.currentTimeMillis();System.out.println("拷贝文件所消耗的时间是: " + (endtime - begintime) + "毫秒");in.close();out.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
}

运行结果:

拷贝文件所消耗的时间是: 0毫秒

在拷贝过程中,使用 while 循环语句逐渐实现字节文件的拷贝,每循环一次,就从文件读取若干字节填充字节数组,并通过变量 len 记住读入数组的字节数,然后从数组的第一个字节开始,将 len 个字节依次写入文件。循环往复,当 len 值为-1 时,说明已经读到了文件的末尾,循环会结束,整个拷贝过程也就结束了,最终程序将整个拷贝过程所消耗的时间打印了出来。

通过两种拷贝方式的对比,可以看出拷贝文件所消耗的时间明显减少了,从而说明缓冲区读写文件可以有效的提高程序的效率。这是因为程序中的缓冲区就是一块内存,用于存放暂时输入输出的数据,使用缓冲区减少了对文件的操作次数,所以可以提高读写数据的效率。

装饰设计模式

俗话说“人靠衣装马靠鞍”,漂亮得体的装扮不仅能提升形象,还能提高竞争力。在程序设计中,同样可以通过“装饰”一个类,增强它的功能。装饰设计模式就是通过包装一个类,动态地为它增加功能的一种设计模式。

装饰设计模式在现实生活中随处可见,比如买了一辆车,想为新车装一个倒车雷达,这就相当于为这辆汽车增加新的功能。接下来通过一个案例来实现上述过程,如例所示。

class Car {private String carName; // 定义一个属性,代表车名public Car(String carName) {this.carName = carName;}public void show() { // 实现Car 的show()方法System.out.println("我是" + carName + ",具有基本功能");}
}// 定义一个类RadarCar
class RadarCar {public Car myCar;public RadarCar(Car myCar) { // 通过构造方法接收被包装的对象this.myCar = myCar;}public void show() {myCar.show();System.out.println("具有倒车雷达功能"); // 实现功能的增强}
}public class IOTest {public static void main(String[] args) {Car benz = new Car("Benz"); // 创建一个NewCar 对象System.out.println("--------------包装前--------------");benz.show();RadarCar decoratedCar_benz = new RadarCar(benz); // 创建一个RadarCar 对象System.out.println("--------------包装后--------------");decoratedCar_benz.show();}
}

运行结果:

--------------包装前--------------
我是Benz,具有基本功能
--------------包装后--------------
我是Benz,具有基本功能
具有倒车雷达功能

例实现了 RadarCar 类对 Car 类的包装。包装类 RadarCar 的构造方法中接收一个 Car 类型的实例对象。通过运行结果可以看出,当 RadarCar 对象调用 show()方法时,被 RadarCar 包装后的对象 benz 不仅具有车的基本功能,而且具有了倒车雷达的功能。

字节缓冲流

在 IO 包中提供两个带缓冲的字节流,分别是 BufferedInputStream 和 BufferdOutputStream,这两个流都使用了装饰设计模式。它们的构造方法中分别接收 InputStream 和 OutputStream 类型的参数作为被包装对象,在读写数据时提供缓冲功能。应用程序、缓冲流和底层字节流之间的关系如图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-buSIMf4w-1642058941609)(http://47.107.171.232/easily-j/images/20190111/dfccccb9-741b-419b-8e47-5ca52e4540f6.png)]

从图中可以看出应用程序是通过缓冲流来完成数据读写的,而缓冲流又是通过底层被包装的字节流与设备进行关联的。接下来通过一个案例来学习 BufferedInputStream 和 BufferedOutputStream 这两个流的用法,如例所示。

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;public class IOTest {public static void main(String[] args) {try {// 创建一个带缓冲区的输入流BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:/example.txt"));// 创建一个带缓冲区的输出流BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:/example.txt"));int len;while ((len = bis.read()) != -1) {bos.write(len);}bis.close();bos.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
}

例中,创建了 BufferedInputStream 和 BufferedOutputStream 两个缓冲流对象,这两个流内部都定义了一个大小为 8192 的字节数组,当调用 read()或者 write()方法读写数据时,首先将读写的数据存入定义好的字节数组,然后将字节数组的数据一次性读写到文件中,这种方式与字节流的缓冲区类似,都对数据进行了缓冲,从而有效的提高了数据的读写效率。

字符流

字符流定义及基本用法

前面我们讲过 InputStream 类和 OutputStream 类在读写文件时操作的都是字节,如果希望在程序中操作字符,使用这两个类就不太方便,为此 JDK 提供了字符流。同字节流一样,字符流也有两个抽象的顶级父类,分别是 Reader 和 Writer。其中 Reader 是字符输入流,用于从某个源设备读取字符,Writer 是字符输出流,用于向某个目标设备写入字符。Reader 和 Writer 作为字符流的顶级父类,也有许多子类,接下来通过继承关系图来列出 Reader 和 Writer 的一些常用子类,如图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rSGU0iRn-1642058941610)(http://47.107.171.232/easily-j/images/20190111/c2d5f0fd-0f90-4bb8-922a-247e2f96539e.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EUDqEAoO-1642058941611)(http://47.107.171.232/easily-j/images/20190111/eb650e26-dc33-4eb3-8ad9-6860954b7e1e.png)]

从图可以看到,字符流的继承关系与字节流的继承关系有些类似,很多子类都是成对(输入流和输出流)出现,其中 FileReader 和 FileWriter 用于读写文件,BufferedReader 和 BufferedWriter 是具有缓冲功能的流,它们可以提高读写效率。

字符流操作文件

在程序开发中,经常需要对文本文件的内容进行读取,如果想从文件中直接读取字符便可以使用字符输入流 FileReader,通过此流可以从关联的文件中读取一个或一组字符。接下来首先在 D 盘目录下新建文件“example.txt”并在其中输入字符“小海绵”,然后通过一个案例来学习如何使用 FileReader 读取文件中的字符,如例所示。

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;public class IOTest {public static void main(String[] args) {try {// 创建一个FileReader 对象用来读取文件中的字符FileReader reader = new FileReader("D:/example.txt");int ch; // 定义一个变量用于记录读取的字符while ((ch = reader.read()) != -1) { // 循环判断是否读取到文件的末尾System.out.println((char) ch); // 不是字符流末尾就转为字符打印}reader.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
}

运行结果:


小
海
绵

例实现了读取文件字符的功能。首先创建一个 FileReader 对象与文件关联,然后通过 while 循环每次从文件中读取一个字符并打印,这样便实现了 FileReader 读文件字符的操作。需要注意的是,字符输入流的 read()方法返回的是 int 类型的值,如果想获得字符就需要进行强制类型转换。

例讲解了如何使用 FileReader 读取文件中的字符,如果要向文件中写入字符就需要使用 FileWriter 类。FileWriter 是 Writer 的一个子类,接下来通过一个案例来学习如何使用 FileWriter 将字符写入文件,如例所示。

import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;public class IOTest {public static void main(String[] args) {try {// 创建一个FileWriter 对象用于向文件中写入数据FileWriter writer = new FileWriter("D:/example.txt", true);String str = "小哥哥";writer.write(str); // 将字符数据写入到文本文件中writer.write("\r\n"); // 将输出语句换行writer.close(); // 关闭写入流,释放资源} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
}

再次运行程序就可以实现在文件中追加内容的效果。

接下来通过一个案例来学习如何使用 BufferedReader 和 BufferedWriter 实现文件的拷贝,如例所示。

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;public class IOTest {public static void main(String[] args) {try {FileReader reader = new FileReader("D:/one/example.txt");// 创建一个BufferedReader 缓冲对象BufferedReader br = new BufferedReader(reader);FileWriter writer = new FileWriter("D:/one/example.txt");// 创建一个BufferdWriter 缓冲对象BufferedWriter bw = new BufferedWriter(writer);String str;while ((str = br.readLine()) != null) { // 每次读取一行文本,判断是否到文件末尾bw.write(str);bw.newLine(); // 写入一个换行符,该方法会根据不同的操作系统生成相应的换行符}br.close();bw.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
}

在例中,首先对输入输出流进行了包装,并通过一个 while 循环实现了文本文件的拷贝。在拷贝过程中,每次循环都使用 readLine()方法读取文件的一行,然后通过 write()方法写入目标文件。其中 readLine()方法会逐个读取字符,当读到回车’r’或换行’ n’时会将读到的字符作为一行的内容返回。

需要注意的是,由于包装流内部使用了缓冲区,在循环中调用 BufferedWriter 的 write()方法写字符时,这些字符首先会被写入缓冲区,当缓冲区写满时或调用 close()方法时,缓冲区中的字符才会被写入目标文件。因此在循环结束时一定要调用 close()方法,否则极有可能会导致部分存在缓冲区中的数据没有被写入目标文件。

转换流

前面提到 IO 流可分为字节流和字符流,有时字节流和字符流之间也需要进行转换。在 JDK 中提供了两个类可以将字节流转换为字符流,它们分别是 InputStreamReader 和 OutputStreamWriter。

转换流也是一种包装流,其中 OutputStreamWriter 是 Writer 的子类,它可以将一个字节输出流包装成字符输出流,方便直接写入字符,而 InputStreamReader 是 Reader 的子类,它可以将一个字节输入流包装成字符输入流,方便直接读取字符。通过转换流进行数据读写的过程如图所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NZ3r10TG-1642058941613)(http://47.107.171.232/easily-j/images/20190111/e7663ad2-4ad5-4632-a1a5-48b0d2b3b90c.png)]

接下来通过一个案例来学习如何将字节流转为字符流,为了提高读写效率,可以通过 BufferedReader 和 BufferedWriter 对转换流进行包装,具体代码如例所示。

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;public class IOTest {public static void main(String[] args) {try {FileInputStream in = new FileInputStream("D:/example.txt"); // 创建字节输入流InputStreamReader isr = new InputStreamReader(in);// 将字节流输入转换成字符输入流BufferedReader br = new BufferedReader(isr); // 对字符流对象进行包装FileOutputStream out = new FileOutputStream("D:/example.txt");// 将字节输出流转换成字符输出流OutputStreamWriter osw = new OutputStreamWriter(out);BufferedWriter bw = new BufferedWriter(osw); // 对字符输出流对象进行包装String line;while ((line = br.readLine()) != null) { // 判断是否读到文件末尾bw.write(line); // 输出读取到的文件}br.close();bw.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
}

例实现了字节流和字符流之间的转换,将字节流转换为字符流,从而实现直接对字符的读写。需要注意的是,在使用转换流时,只能针对操作文本文件的字节流进行转换,如果字节流操作的是一张图片,此时转换为字符流就会造成数据丢失。

File 类

本章前面讲解的 IO 流可以对文件的内容进行读写操作,在应用程序中还会经常对文件本身进行一些常规操作,例如创建一个文件,删除或者重命名某个文件,判断硬盘上某个文件是否存在,查询文件最后修改时间等。针对文件的这类操作,JDK 中提供了一个 File 类,该类封装了一个路径,并提供了一系列的方法用于操作该路径所指向的文件,接下来围绕 File 类展开详细讲解。

File 类的常用方法

File 类用于封装一个路径,这个路径可以是从系统盘符开始的绝对路径,如 D:/example.txt,也可以是相对于当前目录而言的相对路径,如 src/example.txt。File 类内部封装的路径可以指向一个文件,也可以指向一个目录,在 File 类中提供了针对这些文件或目录的一些常规操作。接下来首先介绍一下 File 类常用的构造方法,如表所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cDwqJsfq-1642058941614)(http://47.107.171.232/easily-j/images/20190111/6c73686e-16fc-4ed6-b676-390bb5a68b14.png)]

表中列出了 File 类的三个构造方法。通常来讲,如果程序只处理一个目录或文件,并且知道该目录或文件的路径,使用第一个构造方法较方便。如果程序处理的是一个公共目录中的若干子目录或文件,那么使用第二个或者第三个构造方法会更方便。

File 类中提供了一系列方法,用于操作其内部封装的路径指向的文件或者目录,例如判断文件/目录是否存在、创建、删除文件/目录等。接下来介绍一下 File 类中的常用方法,如表所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZdzxvURF-1642058941615)(http://47.107.171.232/easily-j/images/20190111/62735cf7-6e4a-41f1-a4cf-6c27f56dab7e.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gAGgFrG1-1642058941617)(http://47.107.171.232/easily-j/images/20190111/7e92b847-0b5a-4b18-a032-cac4fb58bff5.png)]

表中,列出了 File 类的一系列常用方法,此表仅仅通过文字对 File 类的方法进行介绍,对于初学者来说很难弄清它们之间的区别,接下来,首先在 D 盘目录下创建一个文件“example.txt”并输入内容“小海绵”,然后通过一个案例来演示 File 类的常用方法,如例所示。

import java.io.File;public class IOTest {public static void main(String[] args) {File file = new File("D:/example.txt"); // 创建File 文件对象,表示一个文件// 获取文件名称System.out.println("文件名称:" + file.getName());// 获取文件的相对路径System.out.println("文件的相对路径:" + file.getPath());// 获取文件的绝对路径System.out.println("文件的绝对路径:" + file.getAbsolutePath());// 获取文件的父路径System.out.println("文件的父路径:" + file.getParent());// 判断文件是否可读System.out.println(file.canRead() ? "文件可读" : "文件不可读");// 判断文件是否可写System.out.println(file.canWrite() ? "文件可写" : "文件不可写");// 判断是否是一个文件System.out.println(file.isFile() ? "是一个文件" : "不是一个文件");// 判断是否是一个目录System.out.println(file.isDirectory() ? "是一个目录" : "不是一个目录");// 判断是否是一个绝对路径System.out.println(file.isAbsolute() ? "是绝对路径" : "不是绝对路径");// 得到文件最后修改时间System.out.println("最后修改时间为:" + file.lastModified());// 得到文件的大小System.out.println("文件大小为:" + file.length() + " bytes");// 是否成功删除文件System.out.println("是否成功删除文件" + file.delete());}
}

运行结果:

文件名称:example.txt
文件的相对路径:D:\example.txt
文件的绝对路径:D:\example.txt
文件的父路径:D:\
文件可读
文件可写
是一个文件
不是一个目录
是绝对路径
最后修改时间为:1547187548383
文件大小为:6 bytes
是否成功删除文件true

遍历目录下的文件

在表中列举的方法中有一个 list()方法,该方法用于遍历某个指定目录下的所有文件的名称,例 8-25 中没有演示该方法的使用,接下来通过一个案例来演示 list()方法的用法,如例所示。

import java.io.File;public class IOTest {public static void main(String[] args) {File file = new File("D:/"); // 创建File 对象if (file.isDirectory()) { // 判断File 对象对应的目录是否存在String[] names = file.list(); // 获得目录下的所有文件的文件名for (String name : names) {System.out.println(name); // 输出文件名}}}
}

例中,创建了一个 File 对象,封装了一个路径,通过调用 File 的 isDirectory()方法判断路径指向的是否为存在的目录,如果存在就调用 list()方法,获得一个 String 类型的数组 names,数组中包含这个目录下所有文件的文件名。接着通过循环遍历数组 names,依次打印出每个文件的文件名。

例实现了遍历一个目录下所有的文件,有时程序只是需要得到指定类型的文件,如获取指定目录下所有的“.java”文件。针对这种需求,File 类中提供了一个重载的 list(FilenameFilterfilter)方法,该方法接收一个 FilenameFilter 类型的参数。FilenameFilter 是一个接口,被称作文件过滤器,当中定义了一个抽象方法 accept(File dir,String name),在调用 list()方法时,需要实现文件过滤器,在 accept()方法中做出判断,从而获得指定类型的文件。

为了让初学者更好地理解文件过滤的原理,接下来分步骤分析 list(FilenameFilter filter)方法的工作原理。

  • 调用 list()方法传入 FilenameFilter 文件过滤器对象。

  • 取出当前 File 对象所代表目录下的所有子目录和文件。

  • 对于每一个子目录或文件,都会调用文件过滤器对象的 accept(File dir,String name)方法,并把代表当前目录的 File 对象以及这个子目录或文件的名字作为参数 dir 和 name 传递给方法。

  • 如果 accept()方法返回 true,就将当前遍历的这个子目录或文件添加到数组中,如果返回 false,则不添加。

接下来通过一个案例来演示如何遍历指定目录下所有扩展名为.java 的文件,如例所示。

import java.io.File;
import java.io.FilenameFilter;public class IOTest {public static void main(String[] args) {// 创建File 对象File file = new File("D:/test");// 创建过滤器对象FilenameFilter filter = new FilenameFilter() {// 实现accept()方法public boolean accept(File dir, String name) {File currFile = new File(dir, name);// 如果文件名以.java 结尾返回true,否则返回falseif (currFile.isFile() && name.endsWith(".java")) {return true;} else {return false;}}};if (file.exists()) { // 判断File 对象对应的目录是否存在String[] lists = file.list(filter); // 获得过滤后的所有文件名数组for (String name : lists) {System.out.println(name);}}}
}

例的 main()方法中,定义了 FilenameFilter 文件过滤器对象 filter,并且实现了 accept()方法,在 accept()方法中对当前正在遍历的 currFile 对象进行判断,只有当 currFile 对象代表文件,并且扩展名“.java”时,才返回 true。在调用 File 对象的 list()方法时将 filter 过滤器对象传入,就得到包含所有“.java”文件名字的字符串数组。

前面的两个例子演示的都是遍历目录下文件的文件名,有时候在一个目录下,除了文件,还有子目录,如果想得到所有子目录下的 File 类型对象,list()方法显然不能满足要求,这时需要使用 File 类提供的另一个方法 listFiles()。listFiles()方法返回一个 File 对象数组,当对数组中的元素进行遍历时,如果元素中还有子目录需要遍历,则需要使用递归。接下来通过一个案例来实现遍历指定目录下的文件,如例所示。

import java.io.File;public class IOTest {public static void main(String[] args) {File file = new File("D:/test"); // 创建一个代表目录的File 对象fileDir(file);}public static void fileDir(File dir) {File[] files = dir.listFiles(); // 获得表示目录下所有文件的数组for (File file : files) { // 遍历所有的子目录和文件if (file.isDirectory()) {fileDir(file); // 如果是目录,递归调用FileDir()}System.out.println(file.getAbsolutePath()); // 输出文件的绝对路径}}
}

运行结果:

D:\test\one
D:\test\test.txt
D:\test\two

例中,定义了一个静态方法 fileDir(),方法接收一个表示目录的 File 对象。在方法中,首先通过调用 listFiles()方法把该目录下所有的子目录和文件存到一个 File 类型的数组 files 中,接着遍历数组 files,对当前遍历的 File 对象进行判断,如果是目录就重新调用 fileDir()方法进行递归,如果是文件就直接打印输出文件的路径,这样该目录下的所有文件就被成功遍历出来了。

删除文件及目录

在操作文件时,经常需要删除一个目录下的某个文件或者删除整个目录,这时大家首先会想到 File 类的 delete()方法,接下来通过一个案例来演示使用 delete()方法删除文件,如例所示。

import java.io.File;public class IOTest {public static void main(String[] args) {File file = new File("D:/test"); // 这是一个代表目录的File 对象if (file.exists()) {System.out.println(file.delete());}}
}

运行结果:

false

图的运行结果中输出了 false,这说明删除文件失败了。大家可能会疑惑,为什么会失败呢? 那是因为 File 类的 delete()方法只是删除一个指定的文件,假如 File 对象代表目录,并且目录下包含子目录或文件,则 File 类的 delete()方法不允许对这个目录直接删除。在这种情况下,需要通过递归的方式将整个目录以及其中的文件全部删除,接下来通过一个案例来演示,如例所示。

import java.io.File;public class IOTest {public static void main(String[] args) {File file = new File("D:/test"); // 创建一个代表目录的File 对象deleteDir(file); // 调用deleteDir 删除方法}public static void deleteDir(File dir) {if (dir.exists()) { // 判断传入的File 对象是否存在File[] files = dir.listFiles(); // 得到File 数组for (File file : files) { // 遍历所有的子目录和文件if (file.isDirectory()) {deleteDir(file); // 如果是目录,递归调用deleteDir()} else {// 如果是文件,直接删除file.delete();}}// 删除完一个目录里的所有文件后,就删除这个目录dir.delete();}}
}

例中,定义了一个删除目录的静态方法 deleteDir(),接收一个 File 类型的参数。在这个方法中,调用 listFiles()方法把这个目录下所有的子目录和文件保存到一个 File 类型的数组 files 中,然后遍历 files,如果是目录就重新调用 deleteDir()方法进行递归,如果是文件就直接调用 File 的 delete()方法删除。当删除完一个目录下的所有文件后,再删除当前这个目录,这样便从里层到外层递归地删除了整个目录。

需要注意的是,在 Java 中删除目录是从虚拟机直接删除而不走回收站,文件将无法恢复,因此在进行删除操作的时候需要格外小心。

字符编码

常用字符集

看战争片时,经常会看到剧中出现收发电报的情况,发报员拿着密码本将文字翻译成某种码文发出,收报员使用同样的密码本将收到的码文再翻译成文字。这个密码本其实是发送方和接收方约定的一套电码表,电码表中规定了文字和电码之间的一一对应关系。

在计算机之间,同样无法直接传输一个一个的字符,而只能传输二进制数据。为了使发送的字符信息能以二进制数据的形式进行传输,同样需要使用一种“密码本”,它叫做字符码表。字符码表是一种可以方便计算机识别的特定字符集,它是将每一个字符和一个唯一的数字对应而形成的一张表。针对不同的文字,每个国家都制定了自己的码表,下面就来介绍几种最常用的字符码表,如表所示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-esDhzKLt-1642058941619)(http://47.107.171.232/easily-j/images/20190111/86838adf-7d69-4ac1-b642-54cd02c634d5.png)]

表中列举了最常用的几种码表,通过选择合适的码表就能完成字符和二进制数据之间的转换,从而实现数据的传输。

微信公众号

【Java】9、Java IO 流相关推荐

  1. java中io流实现哪个接口_第55节:Java当中的IO流-时间api(下)-上

    标题图 Java当中的IO流(下)-上日期和时间日期类:java.util.Date 系统时间:long time = System.currentTimeMillis();public class  ...

  2. Java中的IO流(六)

    上一篇<Java中的IO流(五)>把流中的打印流PrintStream,PrintWriter,序列流SequenceInputStream以及结合之前所记录的知识点完成了文件的切割与文件 ...

  3. java中的IO流(超全)(超详解)结合实例轻松掌握

    java进阶之IO流 IO流的概念(大纲): 1.InputStream和OutputStream的继承关系图 2.Reader和Writer的继承关系图 3.文件专属流(加※为重点掌握) ※File ...

  4. 【Java基础】· IO流习题详解

    写在前面 Hello大家好, 我是[麟-小白],一位软件工程专业的学生,喜好计算机知识.希望大家能够一起学习进步呀!本人是一名在读大学生,专业水平有限,如发现错误或不足之处,请多多指正!谢谢大家!!! ...

  5. Java当中的IO流(中)

    Java当中的IO流(中) 删除目录 import java.io.File;public class Demo{public static void main(String[] args){// 目 ...

  6. Java当中的IO流-时间api(下)-上

    Java当中的IO流(下)-上 日期和时间 日期类:java.util.Date 系统时间: long time = System.currentTimeMillis(); public class ...

  7. Java基础学习—— IO流

    Java基础学习-- IO流 1 文件 1.1 文件的创建 1.2 文件常用的方法 2 IO流 2.1 FileInputStream 2.2 FileOutputStream 2.3 文件的拷贝 2 ...

  8. java io流分为,Java中的IO流按照传输数据不同,可分为和

    Java中的IO流按照传输数据不同,可分为和 答:字节流 字符流 克里斯蒂安 · 麦茨指出:想象的能指就是电影的能指,作为象征的科学,在第三视野范围内的解读,它是( ) 答:建立在共同的永久的背景之中 ...

  9. 重新java系列之IO流

    重新java系列之IO流 内容介绍 学习目标 字符输入流 字符输入流[Reader] FileReader类 构造方法 读取字符数据 使用演示: 字符输出流 字符输出流[Writer] FileWri ...

  10. 猿创征文|Java中的IO流大家族 (两万字详解)

    目录 IO流 概述 分类 四大家族 需要掌握的16个流 文件专属 FileInputstream(读 字节) FileInputStream类的其他常用方法 FileOutputStream (写 字 ...

最新文章

  1. iOS enum 定义与使用
  2. 人工智能-机器学习=深度学习-其他
  3. FAILED: Error in metadata: java.lang.RuntimeException: Unable to instantiate org.apache.解决办法
  4. 在C++中,你真的会用new吗?
  5. GCC源码自动编译-python脚本
  6. android 多媒体文件信息,Android 获取多媒体信息
  7. python: ubuntu下把py2.7改成py3
  8. mx250显卡天梯图_mx250显卡天梯图_2020最新笔记本显卡天梯图,看看你的显卡排在哪里吧...
  9. 使用Aspose在C#中将PLT转换为PDF或JPEG图像
  10. CVE-2022-21999 Windows Print Spooler(打印服务)特权提升漏洞
  11. Docker容器与本地文件相互拷贝
  12. 三国演义亲和度python_Python之三国演义源码
  13. Unity记录3.1-地图-TileMap简单使用、鼠标拖动放置Tile
  14. 字节跳动最常问的前端面试题:Node.js 基础
  15. 数组常用的API(二)
  16. 计算机组成:中断向量的相关计算
  17. 心理学c语言,心理学史练习题.doc
  18. 如何获取淘宝/天猫店铺的所有商品 API数据
  19. 机器学习常见算法思想的面试宝典
  20. python——列表基础操作

热门文章

  1. vue的过渡动画(有vue的动画库和ui库的介绍)
  2. 爬取巴比特快讯遇到状态码“521”
  3. 4.龙芯2k1000 系统制作及安装
  4. java基本数据类型Char
  5. python-Counter计数函数以及most_common函数
  6. JAVA javaweb JSP水果管理系统源码(水果进销存管理系统水果管理系统(水果进销存)
  7. 整理--linux设备驱动模型
  8. 联想微型计算机2010年,2010年10月自考02277微型计算机原理及应用真题及答案
  9. ZeroTier-全网畅连
  10. 无线通信———比较射频和蜂窝电话