学习目的
- 了解java流的概念
- 了解java对流的分类
- 掌握java流的对象创建,以及常用方法的使用
- 掌握java流常见的应用场景
- 掌握重点流:FileStream、PrintStream、ObjectStream
- 了解序列化与反序列化的概念和使用
I/O流
概念
流 在java中指的是文件的转移过程,通常是指文件在计算机硬盘和计算机内存之间的传递方式。该传递过程包括了文件数据和方向。-
字节流继承结构图
-
字符流继承结构图
流与文件路径
输入流对文件的读取和输出流对文件的写入,都需要提前知道文件的路径在哪,一般使用相对路径和绝对路径来存储文件。
- 相对路径:在IDEA或eclipse中,相对路径是项目/工程的根目录,即项目所在的路径就是相对路径。项目工程所在的根目录才是相对路径起点,注意项目底下的子模块路径不是相对路径的起点;
- 绝对路径:绝对路径是任意盘符下所保存的文件路径,引用绝对路径的文件时,必须将绝对路径从盘符开始具体写明。
1、 IO流与Properties
- .properties文件
以.properties为后缀名的文件是java中的属性配置文件,通常存储一些可改变的配置信息。该文件的存储内容与其后缀名一样,采用properties集合存储key=value,即采用Map的键值对形式存储,且properties的键与值得类型只能为String类型。在properties文件中允许key重复,但key重复时,会覆盖value值。 - .properties文件原由
在开发中,经常遇到改变频繁的数据,因此可以将这些数据单独写到一个配置文件中,使用程序动态读取。将来只需要修改这个文件的数据内容,java代码不需要改动,不需要重新编译,服务器也不需要重启,就可以拿到动态的信息。 - IO读取.properties文件
将.properties文件作为一个File对象,使用输入流来读取该文件的内容,就可以获取得到需要的配置信息,可以让配置信息更加灵活多变,且不会触及修改java源代码(修改违背开闭原则)。 - 示例(假设已存在对应的.properties文件)
//Properties是一个Map集合,key和value都是String类型
//将XXX.properties文件中的数据读取,就是加载到Properties对象当中
// 新建一个输入流对象
FileReader reader = new FileReader("路径\\XXX.properties");
// 新建一个Map集合(具体是Hashtable的子类Properties)
Properties pro = new Properties();
// 调用Properties对象的load方法将属性配置文件中的数据加载到Map集合中
pro.load(reader); // 文件中的数据顺着管道加载到Map集合中
// 通过Properties的key来获取对应value
String username = pro.getProperty("username");
System.out.println(username);
一.字节字符流
流概念
文件由字符或字节构成,将文件加载到内存 或 将内存数据输出到文件,需要有输入和输出流的支持。因此可以把输入和输出流分为了两个,字节输入 和 字符输入,字节输出流 和 字符输出流。以流的方向分类
- 输入流:Input,Java中 将文件或其它输入设备的数据 从硬盘加载到内存的过程;
- 输出流:Output,Java中 将内存中的数据 保存到硬盘中的文件或其他输出设备的过程。
- 以读取文件数据的方式分类
- 字节流Stream:以Stream结尾命名的流都是字节流,字节流一次读取1个字节byte(即8个二进制位)。
- 字符流Reader、Writer:以Reader或Writer 结尾命名的流都是字符流,字符流一次读取一个字符char(两个字节16个二进制位)。
-
流关系图
- 区别
- 字节流:每次读取一个字节,使用byte[]数组存储、读写多个字节;
- 字符流:每次读取一个字符,流本身自带并使用char[]数组存储、读写多个字符。
1.1 字节输入流
概念
InputStream 就是字节输入流,是一个抽象类。所有继承了 InputStream 的类都是字节输入流,InputStream是用来从文件中读取基本数据类型值的 Java API接口。主要子类
- 文件输入流
: - 对象输入流
: - 过滤输入流
: - 缓存输入流
:
- 字段
- int MAX_SKIP_BUFFER_SIZE = 2048:用于确定 跳过时要使用的最大缓冲区大小。
- 常用方法
- int read() :从输入流读取下一个数据字节,读到文件末尾时返回 -1;
- int read(byte[] b) :从输入流中每次最多读取b.length个字节,并将读取的数据存储在缓冲区数组b中;
- int read(byte[] b, int off, int len) :将输入流中 从偏移量off开始的最多len个数据字节读入byte数组b中;
- long skip(long n):跳过和丢弃此输入流中数据的 n个字节(从下标0开始);
- void close() :关闭此输入流并释放与该流关联的所有系统资源,finally中执行释放。
1.2 字节输出流
- 概念
OutputStream 就是字节输出流,是一个抽象类,所有继承了 OutputStream 的类都是字节输出流。OuputStream是用来将 基本数据类型数据写入文件的 Java API接口。 - 主要子类
- 文件输出流
: - 对象输出流
: - 过滤输出流
: - 缓存流
: - 打印流
:
- 主要方法
- void write(int b):将指定的字节内容写入此输出流,通过输出流将字节数据写到文件中;
- void write(byte[] b) :将 b.length个字节从指定的字节数组写入此输出流;
- void write(byte[] b, int off, int len) :将指定字节数组中 从偏移量off 开始的 len个字节写入此输出流;
- void flush() :write()写完后,刷新此输出流,并强制写出所有管道中缓冲的输出字节;
- void close() :关闭此输出流并释放与此流有关的所有系统资源。
1.3 字符输入流
概念
Reader 就是字符输入流,是一个抽象类,所有继承了 Reader 的类都是字符输入流。Reader是用于读取字符流的抽象类,子类必须实现其read(char[], int, int) 和 close()方法。同时,多数子类将重写其定义的一些方法,以提供更高的效率和其他功能。主要子类
- 缓存输入流
: - 字节转字符输入流
: - 字符文件输入流
:
- 字段
- Object lock:用于同步 针对此流的操作的对象。
- 主要方法
- Reader():无参构造,创建一个新的字符输入流对象,并同步自身;
- Reader(Object lock):带参构造,创建一个新的字符输入流对象,并同步给定的对象;
- int read() :读取单个字符,如果已读到文件末尾,则返回 -1;
- int read(char[] cbuf) :将输入流读取得到的字符数据存入char[]数组;
- int read(char[] cbuf, int off, int len) :从数组的偏移量off开始,将len个字符读入数组的某一部分;
- long skip(long n):读取某个文件时跳过n个字符,从文件下标0开始;
- void close() :关闭该流并释放与此流有关的所有系统资源。
1.4 字符输出流
概念
Writer 就是字符输出流,是一个抽象类,所有继承了 Writer 的类都是字符输出流。Writer是写入字符流的抽象类,子类必须实现其write(char[], int, int)、flush() 和 close()方法,完成数据到文件的写入。同时,多数子类将重写其定义的一些方法,以提供更高的效率和其他功能。主要子类
- 缓存流
: - 字节到字符转换流
: - 字符文件输出流
: - 打印流
:
- 主要方法
- Writer append(char c) :将指定字符追加到此 writer;
- void write(int c) :往文件中写入单个字符,写入前先清空文件原内容;
- void write(char[] cbuf) :往文件中写入字符数组,每次最多写入cbuf.length个字符;
- void write(char[] cbuf, int off, int len) :往字符数组的某一部分,从字符数组的下标off开始,写入len个长度字符到文件中;
- void write(String str) :往文件中写入字符串;
- void write(String str, int off, int len) :往文件中写入字符串的某一部分,从字符串下标off开始,写入len个长度字符;
- void flush() :刷新此输出流,将管道中的数据刷出写入到文件中;
- void close() :关闭此流并释放与此流有关的所有系统资源,但要先执行 flush() 刷新它。
1.5 flush()方法
flush 的含义是刷新缓冲区,就是将缓存区中的数据写到磁盘上,而不再把数据放到内存里。在执行 os.close()时,会默认执行 os.flush(),编写代码时可以不用显示的调用。
二、文件流
- 概念
文件流是指在程序开发中,完成对文件的读写操作的流称为文件流。
该流用于读取和写入普通文本、图像、音频、视频等格式文件。 - 分类
- 文件字节输入流
- 文件字节输出流
- 文件字符输入流
- 文件字符输出流
- 区别
- 字节流:每次读取一个字节,使用byte[]数组存储、读写多个字节;可以读取所有的形式文件(文本、视频、音频、图片等);
- 字符流:每次读取一个字符,使用char[]数组存储、读写多个字符;只适合于读取普通文本文件。
2.1 文件字节输入流
- 概念
FileInputStream是从文件系统(硬盘)中的某个文件中 获得输入字节。主要按照字节的方式读取硬盘上的文件内容,每次读取一个字节。FileInputStream 用于读取诸如图像数据之类的原始字节流。
<文件内容在计算机底层都是以 二进制的补码形式存在,1字节 = 8位>。 - 常用方法
- FileInputStream(String name):带参构造方法,根据指定文件路径创建字节输入流对象;
- int read() :从输入流读取文件的下一个数据字节,若已到达文件末尾则返回 -1;
- int read(byte[] b) :从 输入流中每一次读取 最多b.length个字节,并将读取的内容存储在缓冲区的byte数组中;
- int available():每一次读取时,剩余未读的字节个数,结合 read(byte[] b) 方法作为byte[ available() ]数组的容量 最佳,但过大时会超出byte数组的容量;
- long skip(long n):跳过n个字节"选择"读取,返回读取时实际跳过的字节数;
- void close() :关闭此输入流并释放与该流关联的所有系统资源。
-
FileInputStream读取文件图解
- 代码实现
//创建文件输入流引用
FileInputStream fis = null;
try {
//创建 从指定路径读取文件的 输入流对象,
fis = new FileInputStream("c:\\test.txt");
int b = 0;//存储每次读取文件返回的字节数据 个数
while ((b = fis.read()) != -1) {
//System.out.print(b);//输出读取到的单个字节
System.out.print((char)b);//将读取得到的单个字节 转换成字符输出
}
//采用read(byte[] bytes)方法读取时,结合使用available()作为bytes数组的容量,但数据过大时不能使用
// byte[] bytes = new byte[fis.available()];
//采用字节数组存储字节流读取文件的数据
byte[] b1 = new byte[4];
int readcount = 0;//存储每次读取数组 返回的字节个数
//当返回读取的字节个数不为0
while ((readcount = fis.read(b1)) != -1){
//将每一次读取byte数组中的数据转换为字符串输出,每次读取几个就输出几个
String s1 = new String(b1,0,readcount);
System.out.print(s1);//
}
}catch(FileNotFoundException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}finally { //当输入流为空时,关闭流,释放内存
try {
if (fis != null){
is.close();
}
}catch(IOException e) {}
}
- 注意点
- 常常使用 fis.read() != -1判断是否读取到了文件末尾;
- 使用 byte数组存储读取文件的数据时,想要每次读取几个就输出几个,就使用一个变量temp 接收每次读取的个数;
- 采用字节输入流读取汉字文本文件时,因为每个汉字占据两个字节,而字节输入流每次只能读取一个字节,导致每个汉字的读取分为"不完整的"两部分,从而产生乱码。
2.2 文件字节输出流
概念
文件输出流是 将数据写入文件的输出流。"写入文件"是否可用 或 能否被创建,取决于基础平台,某些平台一次只允许一个文件流对象打开文件进行写入。若所涉及的文件已经打开,则此类中的构造方法将失败(一个文件无法被两个输出流同时写入数据)。特点
文件字节输出流按照 字节方式写出文件,适用于写入诸如图像数据之类的原始字节的流。经典案例
文件复制:首先判断该文件是否存在。文件存在,则(采用输入流)读取文件,读取后将该文件(采用输出流)另写一份保存到磁盘上,完成文件的复制备份。-
文件字节输出图
常用方法
- FileOutputStream(String name):带参构造,创建一个 向指定文件写入数据的 字节输出流对象,写入时默认清空文件的所有内容再写入;
- FileOutputStream(String name, boolean append):带参构造,创建一个 向指定文件写入数据的 字节输出流对象;第二个参数为 true,表示将字节追加写入文件末尾处,而不是清空文件再写入文件开始处;
- void write(int b):输出流将指定的字节b写入文件,一次写入一个长度的字节;
- void write(byte[] b) :输出流每次将最多 b.length 个字节从指定 byte 数组写入此文件,通常byte数组是由输入流读取得到的"复制内容";
- void writeBytes(byte b[], int off, int len, boolean append):输出流将数组的一部分内容写入文件,并使用append判断是清空原文件内容写入,或是在原文件的末尾追加写入;
- void close() :关闭此输出流 并释放与此流有关的所有系统资源。
- 代码实现
//创建文件输出流引用
FileOutputStream fos = null;
try {
//创建 指定路径写出文件的 输出流文件对象,若文件不存在则在该路径新建文件,若文件存在则清空文件的内容再写入
fos = new FileOutputStream("d:\\test.txt.bak");
//创建一个输出流对象,写入时不清空已存在文件的内容,而是在文件末尾追加写入的内容
// fos = new FileOutputStream("d:\\test.txt.bak",true);
//写入单个字符
fos.write(1);
//写入字符串
fos.write("Hello world!");
//写入char数组
char[] ac = {'我','是','中','国','人'};
fos.write(ac);
//写入完成,刷新管道
fos.flush();
System.out.println("文件复制完毕!");
}catch(FileNotFoundException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}finally {
// 分开try,不要一起try, 一起try时,其中一个出现异常,可能会影响到另一个流的关闭。
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
- 注意点
- 对于FileOutPutStream要写入的文件,若文件在对象创建前已存在,直接对已存在文件进行写入;若文件不存在,则会在指定路径下新建文件再写入;
- FileOutPutStream对象的创建:调用构造器时,注意区分单参构造和双参构造,单参构造会清空写入文件的所有内容再将新数据写进;双参构造append若是true时,则不会清空文件内容,而在文件末尾追加写入的数据。
2.3 文件字符输入流
- 概念
FileReader是用来读取字符文件的便捷类。主要按照 字符(双字节)的方式读取文件内容,即每次读取两个字节。<底层使用两个字节位00110101 10111001>。 -
文件字符输入流图
- 常用方法
- FileReader(String fileName):带参构造,在指定路径下创建一个字符输入流对象,读取文件数据(底层调用FileInputStream对象的创建方式);
- int read() :从输入流读取单个字符,以int类型变量接收读取的字符;
- int read(char[] c) :从输入流 将字符读入数组,一次读入最多c.length个长度字符;
- void close() :关闭该流并释放与此流有关的所有系统资源。
- 代码实现
//创建字符输入流引用
FileReader fileReader = null;
try {
//创建实际的字符输入流对象
fileReader = new FileReader("");
//读取单个字符
fileReader.read();
//用于存储 每次读取返回的字节个数
int readcount = 0;
char[] ac = new char[8];
//读取数组
fileReader.read(ac);
while ((readcount = fileReader.read(ac)) !=-1){
//读取数组的一部分
fileReader.read(ac, 0, readcount);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
2.4 文件字符输出流
- 概念
FileWriter是用来写入字符文件的便捷类,每一次输出按照字符方式(双字节)写出文件。 -
文件字符输出流图
- 常用方法
- FileWriter(String fileName):带参构造,创建一个 向指定名称的文件中写入数据的 字符输出流对象;
- FileWriter(String fileName, boolean append):带参构造,创建一个 向指定名称的文件中写入数据的 字符输出流对象;第二个参数为 true,表示将字节写入文件末尾处,而不是写入文件开始处;
- void flush() :刷新此输出流,将可能残留在管道中的数据刷出到文件中(一般在关闭流之前刷新);
- void close() :关闭此流并释放与此流有关的所有系统资源,但要先执行 flush() 刷新;
- void write(int c) :在输出流中写入单个字符;
- void write(String str) :在输出流中写入字符串。
- 代码实现
//创建一个文件字符输出流引用
FileWriter fileWriter = null;
try {
//创建实际的字符输出流对象
fileWriter = new FileWriter("");
//创建实际的字符输出流对象,true表示在输出文件后面进行append追加内容
//fileWriter = new FileWriter("",true);
//写入单个字符
fileWriter.write(1);
//写入字符串
fileWriter.write("Hello world!");
//写入char数组
char[] ac = {'我','是','中','国','人'};
fileWriter.write(ac);
//写入完成,刷新管道
fileWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (fileWriter != null){
try {
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
三、缓冲流
概念
缓冲流主要是通过减少物理读取次数(程序从硬盘中读取文件的次数)的流,通过将硬盘数据读取到一个缓冲管道中,下一次直接从缓冲管道中读取,减少内存与硬盘的交流次数。缓冲流是为了提高效率而存在的。分类
- 缓冲字节输入流
- 缓冲字节输出流
- 缓冲字符输入流
- 缓冲字符输出流
- 特点
- 构造方法节点流:缓冲流的构造方法中需要一个节点流作为第一参数,不像文件流直接使用文件路径或文件创建对象,缓冲流必须使用父类的字节输入/输出流、字符输入输出流的对象作为第一参数;
- 流自带缓冲区:缓冲流中自带有数据缓冲区,无需像文件流一样创建自定义byte[]数组或char[]数组存储 字节/字符输入流读取文件的数据;
- 节点流、包装流与处理流
- 节点流:指的是在一个流的构造方法内还需要另外一个流作为参数,而作为参数的流就称为节点流,在关闭流释放资源时,只需关闭外部流,不需要关闭节点流(外部流的close()底层自动关闭节点流);
- 包装流/ 处理流:指的是使用节点流作为其构造方法参数的流,实际上对文件进行操作处理的流。
3.1 缓冲字节输入流
- 概念
BufferedInputStream为另一个输入流InputStream扩展功能,添加了缓冲输入、支持 mark() 和 reset()方法的能力。在创建 BufferedInputStream 时,会创建一个内部缓冲区数组,在读取或跳过流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节。 - 特点
- mark()方法 记录输入流中的某个点;
- reset()方法使得在 从包含的输入流中获取新字节 之前,再次读取自最后一次标记后读取的所有字节。
- 字段
- int DEFAULT_BUFFER_SIZE = 8192:默认字符缓冲区的大小;
- int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8:该流支持的最大缓冲区大小;
- volatile byte buf[]:存储 从字节输入流中读取的数据 的内部缓冲数组;
- int count:比缓冲区中最后一个有效字节的索引 大1的索引;
- int pos:缓冲区中的当前位置position;
- int markpos = -1:最后一次调用mark()方法时 缓冲区的位置。
- 常用方法
- BufferedInputStream(InputStream in):带参构造,创建一个默认缓冲区大小的字节缓存输入流对象,底层使用输入流 in;
- BufferedInputStream(InputStream in, int size):带参构造,创建一个size长度的指定缓冲区大小的 字节缓存输入流对象,底层使用输入流 in;
- int read():从输入流中读取数据的下一个字节;
- int read(byte b[], int off, int len):将输入流中的 len个数据字节读入byte 数组中,从数组的下标off开始存储;
- long skip(long n):跳过和丢弃此输入流中数据的 n个字节;
- void mark(int readlimit):在此输入流中标记当前的位置;
- void close():关闭此输入流并释放与该流关联的所有系统资源。
//创建字节输入流对象--缓冲流中的节点流
FileInputStream fis = new FileInputStream("");
//创建缓冲字节输出流对象--参数为节点流
BufferedInputStream bis = new BufferedInputStream(fis);
//创建缓冲字节输出流对象--参数为节点流,且缓冲流本身自带的缓冲区大小为128字节
BufferedInputStream biss = new BufferedInputStream(fis,128);
//查看缓冲流本身缓冲区的大小
bis.available();
//读取单个字节
bis.read();
byte[] b1 = new byte[32];
//读取字节数组
bis.read(b1);
int readcount = 0;
//读取字节数组的一部分
bis.read(b1,0,readcount);
//关闭流资源,释放内存
bis.close();
3.2 缓冲字节输出流
- 概念
BufferedOutputStream类是实现缓冲的输出流。通过设置这种输出流,应用程序可以将各个字节写入底层输出流InputStream中,不必针对每次字节写入都调用底层系统。 - 字段
- byte buf[]:存储数据的内部缓冲区;
- int count:缓冲区中的有效字节数。
- 常用方法
- BufferedOutputStream(OutputStream out):带参构造,创建一个默认缓冲区大小为8192的 字节缓存输出流对象,底层使用输出流 out;
- BufferedOutputStream(OutputStream out, int size):带参构造,创建一个指定size长度的缓冲区大小的 字节缓存输入流对象,底层使用输出流 out;
- void flushBuffer():刷新此字符缓冲区;
- void write(int b):将字符数据b写入缓冲输出流;
- void write(byte b[], int off, int len):将字符数组中指定len个长度的数据写入缓冲输出流,从数组中的下标off开始;
- void flush():刷新此字符缓冲输出流,将管道中的数据刷新到文件中。
//创建字节输出流对象--作为缓冲流的节点流
FileOutputStream fos = new FileOutputStream("");
//创建缓冲字节流输出对象
BufferedOutputStream bos = new BufferedOutputStream(fos);
//创建缓冲字节流输出对象,且缓冲流自身的缓冲区大小为28字节
//BufferedOutputStream boss = new BufferedOutputStream(fos,128);
//写出单个字节
bos.write(12);
//写出字节数组
byte[] b1 = new byte[128];
bos.write(b1);
int writecount = 0;
//写出字节数组的一部分
bos.write(b1,0,writecount);
//刷新缓冲流
bos.flush();
//关闭缓冲流
bos.close();
3.3 缓冲字符输入流
- 概念
BufferedReader是从字符输入流中读取文本(不是直接从文件中),缓冲各个字符,从而实现字符、数组和行的高效读取。 BufferedReader可以指定缓冲区的大小,或者使用默认大小。大多数情况下,默认值足够大。 - 特点
缓冲指定文件的输入。如果没有缓冲,每次调用 read()或 readLine()方法都会导致从文件中读取字节,再将其转换为字符后返回,是极其低效的。 - 字段
- Reader in:字符输入流对象(节点流),用于BufferedReader构造方法中的第一参数;
- char cb[]:流本身自带用于存储 从字符输入流中读取的数据的字符数组;
- int defaultCharBufferSize = 8192:默认自身缓冲区的大小为8192字节。
- 常用方法
- BufferedReader(Reader in):创建一个默认大小输入缓冲区的缓冲字符输入流,需要一个节点流Reader作为对象;
- BufferedReader(Reader in, int sz):带参构造,创建一个指定sz大小输入缓冲区的缓冲字符输入流;
- int read():从字符输入流中读取单个字符;
- read(char cbuf[], int off, int len):将字符读入到数组中的某一部分;
- String readLine(): 从字符输入流中直接读取一个文本行,遇到指定字符时某行终止(换行 '\n'、回车 '\r' 、回车后跟着的换行),该方法本身读取不到换行符;
- long skip(long n):从字符输出流中读取时,跳过指定n个字符,从下标0开始;
- void close():关闭该缓冲流,释放内存。
3.4 缓冲字符输出流
- 概念
BufferedWriter将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。可以指定缓冲区的大小,或接受默认的大小。大多数情况下,默认值足够大。 - 特点
将缓冲 PrintWriter对文件的输出。如果没有缓冲,每次调用 print()方法都会将字符转换为字节,再写入到文件,是极其低效的。 - 字段
- Writer out:字符输出对象,用于BufferedWriter构造方法中;
- char cb[]:用于存储 从字符输入流中读取的数据的 字符数组;
- int defaultCharBufferSize = 8192:默认缓冲区大小8192字节。
- 常用方法
- BufferedWriter(Writer out):带参构造,创建一个使用默认大小输出缓冲区的缓冲字符输出流;
- BufferedWriter(Writer out, int sz):带参构造,创建一个使用给定大小输出缓冲区的新缓冲字符输出流;
- void ensureOpen():确认该输出流打开(正在使用可输出);
- void write(int c):写入单个字符, c-是指定要写入字符;
- void write(char cbuf[], int off, int len):将字符数组中的某一部分写入流,从字符数组的下标off开始,写入len个长度字符;
- void write(String s, int off, int len):将字符串的某一部分写入流,从字符串的下标off开始,写入len个长度字符;
- void newLine():往流中写入一个行分隔符;
- void flush():刷新该流的缓冲,将管道中缓冲的数据刷新到文件中;
- void flushBuffer():刷新缓冲数据;
- void close():关闭此流,关闭前要先使用flush()刷新管道中的缓冲数据。
四、转换流
- 概念
转换流就是将一种流的方法转换成另一种流的方式。通常是字节流转字符流(对比基本数据类型的转换,小转大可以自动转,大转小会损失精度,单字节容纳不下双字节)。 - 主要子类
- 字节转字符输入
: - 字节转字符输出
:
- 用途
作为一个"包装类",将字节流转换为字符流,结合缓冲流的构造方法中的节点流一起使用更合适。
4.1 字节转字符输入
- 概念
InputStreamReader就是将字节流输入流转换成字符输入流,由字面理解就是InputStream → Reader。它使用指定的字符编码读取字节 并将其解码为字符,使用的字符集可以由名称指定、显式给定,或者接受平台默认的字符集。 - 特点
- 字段
- StreamDecoder sd:流解码器对象,用于处理字节转字符期间的编码和解码问题。
- 常用方法
- InputStreamReader(InputStream in):带参构造,创建一个使用默认字符集的 字节转字符输入对象,底层使用字节输入流对象in;
- InputStreamReader(InputStream in, String charsetName):带参构造,创建使用指定字符集的 字节转字符输入对象;
- InputStreamReader(InputStream in, Charset cs):带参构造,创建使用给定字符集的 字节转字符输入对象;
- InputStreamReader(InputStream in, CharsetDecoder dec):带参构造,创建使用给定字符集解码器的 字节转字符输入对象;
- boolean ready():判断此流是否已经准备好用于读取,读取前判断再使用;
- int read():读取单个字符,如果已到达流的末尾,则返回 -1 ;
- int read(char cbuf[], int offset, int length):将字符读入到数组中的某一部分,从数组下标offset开始存储,读取length个长度;
- void close():关闭该流并释放与之关联的所有资源。
- 代码示例
//创建字符输入流
FileReader fr = new FileReader("");
//创建字节缓冲流--内部参数为字符流对象
BufferedReader br1 = new BufferedReader(fr);
//创建字节输入流
FileInputStream fio = new FileInputStream("");
//将字节输入流fio 转成 字符输入流
InputStreamReader isr = new InputStreamReader(fio);
//创建字节缓冲流--内部参数为字符流对象
BufferedReader br = new BufferedReader(isr);
//合并-字节流--字节转字符流--字符缓冲流(字符流对象参数)
BufferedReader bfr = new BufferedReader(new InputStreamReader(new FileInputStream("")));
bfr = new BufferedReader(new InputStreamReader(System.in));
String s = null;
while ((s=br.readLine()) != null) {
System.out.println(s);
//读取到字母 q时退出循环
if ("q".equals(s)) {
break;
}
}
if (bfr != null) {
bfr.close();
}
4.2 字节转字符输出
- 概念
OutputStreamWriter就是将字节流输出流转换成字符输出流,由字面理解就是OutputStream → Writer。它使用指定的字符编码将 要写入流中的字符编码成字节,使用的字符集可以由名称指定、显式给定,否则接受平台默认的字符集。 - 特点
- 字段
- StreamDecoder sd:流解码器对象,用于处理字节转字符期间的编码和解码问题。
- 常用方法
- OutputStreamWriter(OutputStream out):带参构造,创建使用默认字符编码的 字节转字符输出对象;
- OutputStreamWriter(OutputStream out, String charsetName):带参构造,创建使用指定字符集的 字节转字符输出对象;
- OutputStreamWriter(OutputStream out, Charset cs):带参构造,
创建使用给定字符集的 字节转字符输出对象; - OutputStreamWriter(OutputStream out, CharsetEncoder enc):带参构造, 创建使用给定字符集编码器的 字节转字符输出对象;
- write(int c):写入单个字符;
- write(char cbuf[], int off, int len):将字符数组中的某一部分写入流,从数组的下标off开始,写入len个长度;
- void write(String str, int off, int len):将字符串中的某一部分写入流,从字符串的下标off开始,写入len个长度;
- void flushBuffer():刷新此流缓冲区;
- void flush():刷新该流的缓冲,将管道中缓冲的数据全都写入文件;
- void close():关闭此流,但要先flush()刷新它。
//创建字节输出流对象
FileOutputStream fos = new FileOutputStream("");
//将字节输出流--转换--字符输出流
OutputStreamWriter osw = new OutputStreamWriter(fos);
//创建字节输出流对象
FileOutputStream fous = new FileOutputStream("");
//将字节输出流--转换--字符输出流
OutputStreamWriter os = new OutputStreamWriter(fous);
//将字符输出流--转换--缓冲字符输出流
BufferedWriter bw = new BufferedWriter(os);
//合并-字节输出流--字符输出流--缓冲字符输出流()
BufferedWriter buw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("")));
buw.flush();
五、打印流
- 概念
打印流即从程序到内存输出的流,由java程序向控制台打印输出,即完成对屏幕打印的重定向。 - 特点
System.out 就是打印输出,默认输出到控制台。而打印流 PrintStream可以重定向输出到文件,System.out.println不再输出到屏幕,而是输出到指定文件。 - 分类
- 打印字节流
- 打印字符流
- 输出重定向原理
System.out默认是PrintStream向控制台输出;
PrintStream 构造时,使用指定输出路径的输出流对象作为参数,导致PrintStream的输出不再默认,而是向指定输出路径输出,从而完成输出重定向。但前提也需要System.setOut才可以修改输出方向。
- System.setOut(new PrintStream(new OutPutStream("指定路径")));
5.1 打印字节流
- 概念
PrintStream构造器底层需要一个输出流作为参数(节点流),因此是其他输出流的扩展,为其他输出流添加更多功能。PrintStream默认完成由java程序向控制台打印输出,即完成对屏幕打印的重定向。 - 特点
- PrintStream永远不会抛出 IOException;
- PrintStream在写入 byte数组之后自动调用 flush()方法完成刷新;
- 调用println()方法,表示写入一个换行符或字节 ('\n');
- PrintStream不需要close()关闭;
- 特点
System.out就是 PrintStream,System.out默认输出到控制台。
而当打印流PrintStream使用 指定路径的输出流作为节点流参数 来构造时,可以重定向输出到文件,System.out.println不再输出到屏幕,而是输出到指定文件。 - 字段
- boolean autoFlush:自动刷新标志;
- BufferedWriter textOut:字符输出对象;
- OutputStreamWriter charOut字节转字符输出对象;
- Formatter formatter:格式化程序,对输出流中的数据格式化;
- 常用方法
- void flush():刷新该流的缓冲,通过将所有缓冲的输出字节写入到底层输出流;
- void close():关闭流,通过刷新流然后关闭底层输出流完成此操作;
- void write(int b):将指定的字节b写入此流,如果字节为新行 且启用了自动刷新,则调用 flush 方法;
- void write(byte[] buf,int off, int len):将byte 数组的 len长度个字节从指定下标 off开始写入此流。如果启用自动刷新,则调用 flush 方法;
- void print(Object obj):打印数据,按照平台的默认字符编码将 String.valueOf(obj)生成的字符串转换为字节,并完全以write(int)方法的方式写入这些字节;
- void println():通过写入行分隔符字符串终止当前行,行分隔符字符串由系统属性line.separator 定义,不一定是单个换行符 '\n';
- PrintStream format(String format, Object args):使用指定格式字符串和参数 将格式化字符串写入此输出流中;
- PrintStream append(CharSequence csq):将指定字符序列csq添加到此输出流;
- PrintStream append(char c):将指定字符c添加到此输出流。
- 代码示例
//创建字节输出流引用
FileOutputStream os = null;
try {
System.out.println("asdfkjfd;lldffdfdrerere");//未改变输出方向,默认向控制台输出
//创建具体的输出流对象
os = new FileOutputStream("c:/console.txt");
//创建标准输出流对象--采用节点流参数,将指定文件输出的方向
PrintStream ps = new PrintStream(os);
//PrintStream ps = System.out;
System.setOut(ps);//改变输出方向
//System.setOut(new PrintStream(os));//合并--改变输出方向
System.out.println("asdfkjfd;lldffdfdrerere");//向指定输出流的文件console.txt输出
}catch(FileNotFoundException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}finally { //关闭流资源,释放内存
try {
if (os != null) {
os.close();
}
}catch(IOException e) {}
}
5.2 打印字符流
- 概念
打印输入流,相当于System.in,完成 接收屏幕输入。 - 特点
- 字段
-Writer out:字符输出流对象,构造器时使用;
- boolean autoFlush:自动刷新标志;
- Formatter formatter:格式化对象,对输出流中的数据格式化;
- PrintStream psOut = null:字节打印流对象;
- String lineSeparator:行分隔符,用于print()方法中进行行分隔的标志;
- 常用方法
- PrintWriter (Writer out):带参构造,创建一个不带自动行刷新的字符打印流对象,底层调用字节输出流对象;
- void flush():刷新该流的缓冲,底层重写了Flushable接口和Writer类的方法;
- void close():关闭该流并释放与之关联的所有系统资源;
- void write(int c):写入单个字符c;
- void write(char buf[], int off, int len):写入字符数组的某一部分,从数组的下标off开始,写入len个长度;
- void write(char buf[]):写入字符数组,此方法不能从 Writer类继承,因为它必须取消 I/O 异常;
- void write(String s, int off, int len):写入字符串的某一部分;
- void newLine():往流中写入一个行分隔符;
- void print(Object obj):打印数据,按照平台的默认字符编码将 String.valueOf(obj)生成的字符串转换为字节,并完全以write(int)方法的方式写入这些字节;
- void println():通过写入行分隔符字符串终止当前行,底层调用newLine()方法;
- PrintWriter append(CharSequence csq):将指定字符序列csq添加到此输出流;
- PrintWriter append(char c):将指定字符c添加到此输出流。
六、对象流<掌握>
- 概念
对象流是可以将 Java对象 转换成二进制进行文件操作的流,通常对象流的输入和输出是以字节单位进行的。 - 分类
- 对象字节输入流
- 对象字节输出流
- 常用方法
- writeObject(Object obj):向硬盘下的指定文件打印输出该obj对象;
- readObject():从硬盘指定的文件中读取出对象到内存中。
6.1 对象字节输入流
- 概念
ObjectInputStream类是对 使用了 ObjectOutputStream类写入的基本数据和对象进行反序列化的对象输入流,从磁盘中将对象反序列化到内存中。 - 特点
- 字段
- 常用方法
- 代码示例
//创建文件输入流引用
FileInputStream fis = null;
//创建对象输入流引用
ObjectInputStream ois = null;
//创建实际的文件输入流对象--作为节点流参数
fis = new FileInputStream("students");
//创建实际的对象输入流对象--完成反序列化的流
ois = new ObjectInputStream(fis);
//将文件中的对象反序列化到内存中--返回的是Object类型对象
Object o = ois.readObject();
//将反序列化的对象进行类型强转
Student st = (Student)o;
//获取反序列对象的各个属性
System.out.println(st.getName());
System.out.println(st.getAge());
if (ois != null){
ois.close();
}
if (fis != null){
fis.close();
}
}
6.2 对象字节输出流
- 概念
ObjectOutputStream是一个用于将java对象分段写入(序列化)到硬盘中的对象输出流。 - 特点
- static class Caches:静态内部类--缓存,使用HashMap存储数据,使用ReferenceQueue参考队列存储消息。
- 字段
- 常用方法
- 代码示例
//创建需要进行序列化的java对象
Student stu = new Student("zhangsan",23);
//创建文件输出流引用--作为节点参数
FileOutputStream fos = null;
//创建对象输出流引用--将java序列化写出的流
ObjectOutputStream oos = null;
//创建实际的文件输出流对象--指定输出路径
fos = new FileOutputStream("stu");
//创建实际的对象输出流对象,使用节点流参数
oos = new ObjectOutputStream(fos);
//将java对象序列化写出
oos.writeObject(stu);
//刷新输出流
oos.flush();
//关闭流资源
if (fos != null){
fos.close();
}
if (oos != null){
oos.close();
}
}
6.3 序列化与反序列化
- 概念
Serilizable接口是一个序列化接口,是标记接口,该接口内没有任何方法。实现该接口只是作为一个标记给JVM看,然后JVM认为实现了该接口的类型对象可以进行序列化,因此该类的所有属性和方法都会自动序列化,不必关心具体序列化的过程。 -
图解
- 前提
需要进行序列化或反序列化的对象必须实现Serilizable接口。
//必须实现Serilizable接口作为JVM认可的序列化标记
public class Student implements Serializable {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
//提供get()和set()方法,并重写toString()方法
}
6.3.1 序列化
- 概念
对象流将 Java对象转换成二进制 写入磁盘的过程叫做序列化,底层采用输出流作为节点流参数。 - 特点
每一个java对象都会是较大的文件,因此序列化时,对象输出流会将Java对象进行切割分段,再写入到硬盘文件当中。 - 代码示例
//创建需要序列化的java对象
Student stu = new Student("haha",21);
//创建对象输出流引用--序列化使用的写出流
ObjectOutputStream obj = null;
try {
//创建实际的对象输出流对象--采用文件输出流作为参数,指定序列化路径
obj = new ObjectOutputStream(new FileOutputStream("students"));
//将需要序列化的java对象写出序列化到硬盘中
obj.writeObject(stu);
} catch (IOException e) {
e.printStackTrace();
}finally {
if (obj != null){
try {
obj.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 注意点
- 序列化多个对象:当需要同时序列化多个对象时,提前将多个对象存储到数组或集合当中<建议使用泛型>,在序列化时ObjectOutputStream.writeObject(集合对象)即可一次序列化多个对象。采用集合序列化多个对象到硬盘中的文件,存储的也是一个集合对象,而不是散开的单个泛型对象。
- 若想采用每次序列化单个对象的方式逐次序列化多个对象,那么会在序列化到第二个对象时出现错误。
6.3.2 反序列化
- 概念
对象流从磁盘读出完整 Java 对象到内存中的过程叫做反序列化,底层采用输入流作为节点流参数。 - 代码示例
//创建反序列化的对象输入流引用
ObjectInputStream ois = null;
try {
//创建实际的对象输入流对象,使用文件输入流作为参数
ois = new ObjectInputStream( new FileInputStream("c:/Person.dat"));
//反序列化,将对象从磁盘读出,并转化为对应类型的对象
Person person = (Person)ois.readObject();
//获取反序列化后对象的属性
System.out.println(person.name);
}catch(ClassNotFoundException e) {
e.printStackTrace();
}catch(FileNotFoundException e) {
e.printStackTrace();
}catch(IOException e) {
e.printStackTrace();
}finally { //关闭流,释放资源
try {
if (ois != null) {
ois.close();
}
}catch(IOException e) {}
}
- 注意点
反序列化多个对象:若序列化文件中的对象是集合对象(即使集合里面存储了多个泛型对象),那么反序列化时 ObjectInputStream.readObject()读取返回的obj也是一个集合对象,因此返回时可以采用集合引用<建议使用泛型>来接收返回的集合对象。再对返回的集合对象进行迭代遍历和获取集合元素(单个对象)的信息。
6.4 transient关键字
- 概念
transient意为游离的。在一个需要序列化的类中,明确有些属性需要序列化,而其他属性不需要被序列化时,使用transient关键字修饰该属性将不会被序列化。 - 作用
只需要实现Serilizable接口,将不需要序列化的属性添加关键字transient修饰,序列化对象的时候,该属性就不会序列化。 - 使用场景
如一个用户类有一些敏感信息<密码,银行卡号>,为了安全起见,不希望在网络操作中被传输(被序列化,包括本地序列化缓存)。因此在该属性上加上 transient关键字,以免被序列化。即transient修饰的字段的生命周期仅存于调用者的内存中,而不会写到磁盘里持久化。
class Person implements Serializable{
String name;
transient String password;//采用 transient 关键字修饰此属性,序列化时会忽略<即不会被序列化>
}
6.5 serialVersionUID 属性
- 概念
serialVersionUID 意为序列化版本号,序列化的对象在存储时都有一个标识版本号,使用该属性修饰的类保证了序列化版本号唯一。 - 特点
- serialVersionUID属性使用public static final修饰;
- 若不使用序列化版本号,在多次对原类修改添加后进行序列化时,会产生多个版本不一样的对象,反序列化时也会不清楚到底反序列化哪一个版本;
- 实现Serilizable接口时,系统默认提供序列化版本号,但是强烈建议程序员自定义类时手动添加写上。
- 作用
- java验证类唯一性的标志之一:java的序列化机制是通过判断类的序列化ID号来验证 序列化对象版本一致性的;
- 保证序列化与反序列化的文件相同且唯一:拥有serialVersionUID属性修饰的类,即使其实例对象在进行一次序列化,之后无论对该对象进行多少次添加或修改,重新对"新对象"序列化时,得到的还是同一个对象;
- 防止序列化兼容问题:解决序列化时对象不唯一问题,对象修改后也不出现序列化的版本问题,而是拥有统一的版本号。
-
图解serialVersionUID
- 使用方式
class Person implements Serializable{
//加入版本号,防止序列化兼容问题,解决序列化时不一致
private static final long serialVersionUID = -11111111L;
String name;
int age;
boolean sex;
}
- IDEA生成序列化版本号
File --> Settings --> Editor --> Inspections -->搜索serializable --> Serializable Class without serialVersionUID --> 打钩 --> ok --> 对实现了Serializable接口的类 atl+回车 --> Add serialVersionUID
七、数据流
- 概念
数据流既是数据专属的流,用于读取文件中的数据及其数据类型,或用于写出数据及其数据类型,数据流读取保存或写入生成的文件不是普通文本文件,不能直接打开。 - 分类
- DataOutputStream:数据输出流,可以将数据连同数据的类型一并写入文件,写出的文件不是普通文本文件;
- DataInputStream:数据输入流,对于DataOutputStream写出的文件,只能使用DataInputStream流去读,并且读取时需要提前知道写入时的数据顺序。读的顺序需要和写的顺序一致。才可以正常取出数据。
- 作用特点
- 安全性:使用DataOutputStream写出的数据连带其数据类型,更加安全易读;
- 加密性:对于DataOutputStream写出的文件必须要DataInputStream流去读,否则乱码,更加安全加密。
- DataOutputStream
FileOutputStream fos = new FileOutputStream("data");
// 创建数据专属的字节输出流--使用节点流
DataOutputStream dos = new DataOutputStream(fos);
//合并--
//DataOutputStream dos = new DataOutputStream(new FileOutputStream("data"));
// 写数据
byte b = 100;
short s = 200;
int i = 300;
long l = 400L;
float f = 3.0F;
double d = 3.14;
boolean sex = false;
char c = 'a';
// 把数据以及数据的类型一并写入到文件当中。
dos.writeByte(b);
dos.writeShort(s);
dos.writeInt(i);
dos.writeLong(l);
dos.writeFloat(f);
dos.writeDouble(d);
dos.writeBoolean(sex);
dos.writeChar(c);
// 写出完成时,刷新管道
dos.flush();
// 关闭外层流(包装流)
dos.close();
- DataInputStream
FileInputStream fis = new FileInputStream("data");
// 创建数据专属的字节输入流--使用节点流
DataInputStream dis = new DataInputStream(fis);
//合并--
DataInputStream dis = new DataInputStream(new FileInputStream("data"));
// 开始读取DataOutputStream写出的文件--前提知道写出的数据顺序
byte b = dis.readByte();
short s = dis.readShort();
int i = dis.readInt();
long l = dis.readLong();
float f = dis.readFloat();
double d = dis.readDouble();
boolean sex = dis.readBoolean();
char c = dis.readChar();
//关闭流资源
dis.close();
八、File 类
- 概念
File 类是文件和目录路径名的抽象表示形式,指的是一类可以直接对文件进行操作的类(不管是硬盘上的还是内存缓存中的)。 - 特点
File类不是具体的文件,只是一条路径,可以是目录也可以是具体文件所在目录,底层封装了文件的路径和路径状态。 - 字段
- String path:文件的路径,可以是根目录或绝对路径;
- PathStatus status = null:路径的状态,
- enum PathStatus { INVALID, CHECKED }:
- int prefixLength:文件前缀长度;
- char separatorChar = fs.getSeparator():文件分隔符。
- 常用方法
- File(String pathname):构造方法,根据指定路径创建一个File文件类对象;
- File(String parent, String child):构造方法,
- String getName():
- booLean exists():判断 根据指定路径创建的文件类对象是否存在,即在指定路径下是否能找到对应文件;
- boolean createNewFile():若指定路径下的文件类对象不存在,则在该路径下以文件形式创建相应的文件;
- boolean mkdir():若指定路径下的文件类对象不存在,则在该路径下以目录形式创建相应的目录文件;
- boolean mkdirs():若指定路径下的文件类对象不存在,则在该路径下以多级目录形式创建相应的目录文件;
- String getParent():获取此路径下创建的文件类对象的文件父目录;
- File getParentFile():获取此路径下创建的文件类对象的文件父目录,并根据该目录生成新的文件类对象;
- String getPath():获取该文件类对象的原文件路径;
- String getAbsolutePath():获取该文件类对象的文件绝对路径;
- boolean isDirectory():判断该文件类对象是否是一个目录;
- boolean isFile():判断该文件类对象是否是一个文件;
- boolean isHidden():判断该文件类对象是否是一个隐藏文件;
- long lastModified(): 获取该文件类对象的文件最后一次的修改时间,返回的是自1970年1月1日开始的毫秒数;
- long length():获取该文件类对象的文件大小;
- File[] listFiles():获取当前目录下所有的子文件,返回的是由多级子目录构成的文件数组,可通过foreach循环遍历该数组得到所有的子文件。
- 代码实例
//根据指定路径创建一个文件类对象
File f = new File("");
// 判断该路径下的创建的文件类对象 (文件)是否存在
System.out.println(f.exists());
// 如果路径下文件不存在,则以文件的形式创建出来
/*if(!f1.exists()) {
// 以文件形式新建
f1.createNewFile();
}*/
// 如果路径下文件不存在,则以单级目录的形式创建出来
/*if(!f1.exists()) {
// 以目录的形式新建。
f1.mkdir();
}*/
// 如果路径下文件不存在,则以多级目录的形式创建出来
File f2 = new File("D:/a/b/c/d/e/f");
/*if(!f2.exists()) {
// 以多重目录的形式新建。
f2.mkdirs();
}*/
File f3 = new File("D:\\course\\01-开课\\学习方法.txt");
// 获取文件的父路径
String parentPath = f3.getParent();
// 获取文件的父路径文件
File parentFile = f3.getParentFile();
//获取文件的绝对路径
System.out.println("获取绝对路径:" + parentFile.getAbsolutePath());
常见面试题
- 使用字节文件流实现一个文件的复制?
答:
创建字节输入流对象FileInputStream;
创建字节输出流对象FileOutPutStream;
输入流对象从指定路径下read()读取文件,并将读取的内容使用字节数组byte[]存储;
使用变量返回值判断文件是否有内容可读;
输出流对象将输入流读取的字节数组中的内容write()写出;
刷新输出流;
关闭所有流,释放资源;
重点:一边读,一边写,每次读取多少个就写出多少个。
public class StreamCopyTest {
public static void main(String[] args) {
//创建字节输入流引用
FileInputStream fis = null;
//创建字节输出流引用
FileOutputStream fos = null;
try {
//创建实际的字节输入流对象
fis = new FileInputStream("");
//创建实际的字节输出流对象--输出文件若存在则覆盖,不存在则新建
fos = new FileOutputStream("");
//创建字节数组存储输入流读取的数据
byte[] bs = new byte[1024 * 1024];//1024kb = 1byte,1mb = 1024kb
//用于计算每次读取文件返回的字节个数,-1表示已读到文件末尾
int readcount = 0;
//一边读取,一边输出,读取多少个,输出多少个
while ((readcount = fis.read(bs)) != -1){
fos.write(bs,0,readcount);
}
//输出完成时,必须刷新管道
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭流资源,释放内存
if (fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- 使用字符文件流实现一个文件的复制?
答:
创建字符输入流对象FileReader
创建字符输出流对象FileWriter
创建字符数组char[]存储字符输入流read读取的文件数据
定义变量返回值 判断文件是否有数据可读
字符输入流从文件读取数据
字符输出流将读取到的数据写出到文件
刷新输出流
关闭所有资源
public class WriterCopyTest {
public static void main(String[] args) {
//创建字符输入流引用
FileReader fr = null;
//创建字符输出流引用
FileWriter fw = null;
try {
//创建实际的字符输入流对象
fr = new FileReader("");
//创建实际的字符输出流对象,若输出文件已存在则覆盖,若不存在则新建
fw = new FileWriter("");
//创建字符数组存储输入流每一次读取的数据
char[] cs = new char[1024*1024];
//用于判断每一次读取文件返回的字节个数,-1表示已读到文件末尾
int readchar = 0;
//一边读取,一边写出,读取多少个写出多少个
while ((readchar = fr.read(cs)) != -1){
fw.write(cs,0,readchar);
}
//输出完成时,必须刷新管道
fw.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭流资源,释放内存
if (fr != null){
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fw != null){
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- 使用一个字节缓冲流 或 字符缓冲流实现一个文件的复制?
答:
BufferInputStream和BufferOutPutStream
BufferReader和BufferWriter
- 实现从D盘的多个目录及文件复制到C盘下?
答:
使用File类获取得到多个目录(数组)
遍历File数组中得到的所有File对象
判断File数组中的File对象是目录还是文件
(是目录继续往下遍历,是文件则直接复制到另外一个盘下)
确定源目录的路径 和 目标目录的路径
对File对象采用IO流进行边读边写
如何确定对应的目录路径?
public static void main(String[] args) {
// 拷贝源
File srcFile = new File("D:\\course\\02-JavaSE\\document");
// 拷贝目标
File destFile = new File("C:\\a\\b\\c");
// 调用方法完成拷贝
copyDir(srcFile, destFile);
}
/**
* 拷贝目录
* @param srcFile 拷贝源
* @param destFile 拷贝目标
*/
private static void copyDir(File srcFile, File destFile) {
if(srcFile.isFile()) {
// srcFile如果是一个文件(文件下面不再有目录),递归结束
// 并且是文件即需要拷贝,复制--一边读一边写
FileInputStream in = null;
FileOutputStream out = null;
try {
// 从源路径读取文件
in = new FileInputStream(srcFile);
// 处理得出要复制到的目标路径--路径的转换很难很重要,需要多重字符串截取和拼接
//先获取目标路径的盘符根目录--再对源路径盘符下的子目录路径进行复制拼接
String path = (destFile.getAbsolutePath().endsWith("\\") ? destFile.getAbsolutePath() : destFile.getAbsolutePath() + "\\") + srcFile.getAbsolutePath().substring(3);
//将读取的文件写入到新的盘符目录下
out = new FileOutputStream(path);
// 一边读一边写,
byte[] bytes = new byte[1024 * 1024]; // 一次复制1MB
int readCount = 0;
while((readCount = in.read(bytes)) != -1){
out.write(bytes, 0, readCount);
}
//写入完成时刷新管道
out.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{ //关闭流资源,释放内存
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return;
}
// 获取源路径下的所有子目录(文件)
File[] files = srcFile.listFiles();
for(File file : files){
// 获取所有文件(包括目录和文件)的绝对路径
//若是源路径下的File是一个目录,则在目标盘符下创建对应目录;若是一个File文件,则结束递归开始复制
if(file.isDirectory()){
// 在目标盘符下新建对应的目录
//D:\course\02-JavaSE\document\JavaSE进阶讲义 源目录
//C:\course\02-JavaSE\document\JavaSE进阶讲义 目标目录
//源file目录
String srcDir = file.getAbsolutePath();
//新file要复制到的新目录
String destDir = (destFile.getAbsolutePath().endsWith("\\") ? destFile.getAbsolutePath() : destFile.getAbsolutePath() + "\\") + srcDir.substring(3);
//使用新目录创建出File文件对象
File newFile = new File(destDir);
if(!newFile.exists()){
newFile.mkdirs();
}
}
// 递归调用--很难想到使用递归
copyDir(file, destFile);
}
}