接前面那篇字节流的总结继续说起~~
字符流:FileWriter 和 FileReader
字符流:
输出流根:Writer(抽象类)
|–OutputStreamWriter(子类,转换流)(构造:必须得有一个字节流OutputStream)
|–FileWriter(子类,字符流)(构造:File,String)(写入的方法:无,使用父类的)
|–BufferedWriter
输入流根:Reader(抽象类)
|–InputStreamReader(子类,转换流)(构造:必须得有一个字节流InputStream)
|–FileReader(子类,字符流);(构造:File,String)(读取的方法:无,使用父类的)
|–BufferedReader
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Demo {
public static void main(String[] args) {
//1.构造输入输出流
try {
//读取时,文件必须存在
FileReader in = new FileReader("b.txt");
//输出流,文件可以不存在
FileWriter out = new FileWriter("c.txt");
//复制
//方式一:一次读取一个字符
/*int n = 0;
while((n = in.read()) != -1){
out.write(n);
out.flush();
}*/
//方式二:一次读取一个字符数组
char[] charArray =new char[1024];
int len = 0;
while((len = in.read(charArray)) != -1){
out.write(charArray,0,len);
out.flush();
}
//释放资源
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
转换流实际上本身也就是字符流,所以我们使用字符流进行复制文件时,是可以写成转换流的“形”的,如:
使用字符流复制文本文件:
1.读取数据:InputStreamReader – “a.txt”
2.写入数据:OutputStreamWriter – “b.txt”
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public static void main(String[] args) {
//1.实例化输入、输出流
try {
//文件必须存在
InputStreamReader in = new InputStreamReader(new FileInputStream("a.txt"));
//文件可以不存在
OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream("b.txt"));
//一次读写一个字符
/*int n = 0;
while((n = in.read())!= -1){
out.write(n);
out.flush();
}*/
//一次读写一个字符数组
char[] charArray = new char[1024];
int len = 0;
while((len = in.read(charArray))!= -1){
out.write(charArray,0,len);
out.flush();
}
//释放资源:
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
带缓冲区的字符流:
输出流:BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
构造函数:
BufferedWriter(Writer out): 创建一个使用默认大小输出缓冲区的缓冲字符输出流。
输入流:BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而提供字符、数组和行的高效读取。
构造函数:
BufferedReader(Reader in): 创建一个使用默认大小输入缓冲区的缓冲字符输入流。
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Demo {
public static void main(String[] args) {
//1.构造一个BufferedWriter
try {
BufferedWriter out = new BufferedWriter(new FileWriter("d.txt"));
//写入数据:一样的具有5个写入的方法
//这里使用写入字符串
out.write("Hellojava中国你好");
//3.释放资源
out.close();
//构造一个输入流
BufferedReader in = new BufferedReader(new FileReader("d.txt"));
//一次读取一个字符
/*int n = in.read();
System.out.println("读取的int值:" + n);
System.out.println("读取的字符:" + (char)n);*/
//一次读取一个字符数组
char[] charArray = new char[1024];
int len = 0;
while((len = in.read(charArray)) != -1){
System.out.println(new String(charArray,0,len));
}
//释放资源
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
字符缓冲区流的特有功能:
输出流:BufferedWriter: newLine():输出一个换行;
输入流:BufferedReader: readLine():一次读取一行数据;如果到达行末尾,返回null
实现一个复制,一次读取一行数据,一次写入一个行数据
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 Demo {
public static void main(String[] args) {
//1.实例化输入输出流
try {
BufferedReader in = new BufferedReader(new FileReader("a.txt"));
BufferedWriter out = new BufferedWriter(new FileWriter("f.txt"));
//一次读取一行数据,然后写入
String row = null;
while((row = in.readLine()) != null){
out.write(row);
// out.write("\r\n");
out.newLine();//输出一个换行符
out.flush();
}
//释放资源
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
输入输出流的字节流和字符流讲解了这么多,主要还是要合理合适地应用到文件的读取与写入的操作过程中,故下面的这个单级文件夹的目录的复制和多级文件夹的目录的复制可以更加深入的来理解这些输入输出流的应用。
复制单级文件夹中指定文件并修改文件名称;
复制单级文件夹中指定文件并修改文件名称
将D盘aaa下的所有.java文件,
复制到E盘aaa_copy下,然后将所有.java文件重命名为.txt
1.首先做单级目录的指定文件的复制;
2.我们尝试在复制的同时,修改文件的后缀名
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo {
public static void main(String[] args) {
File srcFile = new File("D:\\aaa");
File destFile = new File("E:\\aaa_copy");
try {
copyFileAndRename(srcFile,destFile);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void copyFileAndRename(File srcFile, File destFile) throws IOException {
//判断目标目录是否存在,如果不存在就建立一个
if(!destFile.exists()){
destFile.mkdir();
}
//获取出源目录下的所有文件,遍历时,筛选出所有的.java文件,然后拷贝并重命名
File[] fileArray = srcFile.listFiles();
for(File f : fileArray){
//判断f是否是文件,并且以.java结尾
if(f.isFile() && f.getName().endsWith(".java")){
//进行拷贝
BufferedInputStream in = new BufferedInputStream(new FileInputStream(f));
//获取原文件名
String fileName = f.getName();//Xxxx.java
//将源文件名的.java改为.txt
fileName = fileName.replaceAll(".java", ".txt");
//使用destFile和新文件名建立一个File对象
File newDestFile = new File(destFile,fileName);//E:\\aaa_copy,Xxxx.txt
//建立输出流;
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(newDestFile));
byte[] byteArray = new byte[1024];
int len = 0;
while((len = in.read(byteArray)) != -1){
out.write(byteArray,0,len);
out.flush();
}
in.close();
out.close();
System.out.println("拷贝文件:从:" + f.getAbsolutePath() + " 到:" + newDestFile + " 拷贝完成!");
}
}
}
}
复制多级目录:
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/*
- 复制多级目录:
- 从D:\bbb 复制到 E:\
- 1.建立源目录:File
- 2.建立目标目录:File
- 3.传入到方法:
- 4.判断源目录是一个目录么?
-
是:
-
4.1:判断目标目中是否有此目录,如果没有,创建;
-
4.2:获取源目录中所有的文件和目录;File[]数组
-
4.3:遍历File[]数组
-
4.4:回到3(方法的递归调用)
-
否:
-
直接复制
- 特别注意需要处理的地方:
- 1.在方法内,进行递归调用时以及复制文件时,要计算出新的"源目录"和"目标目录"
*/
public class Demo {
public static void main(String[] args) {
File srcFile = new File("D:\\bbb");
File destFile = new File("E:\\");
try {
copyFile(srcFile,destFile);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("复制完毕!");
}
private static void copyFile(File srcFile, File destFile) throws IOException {//srcFile-->D:\\bbb,destFile-->E:\\
//4.判断源目录是一个目录么?
if(srcFile.isDirectory()){//是一个目录
//4.1:判断目标目中是否有此目录,如果没有,创建;
destFile = new File(destFile,srcFile.getName());
if(!destFile.exists()){
destFile.mkdir();
System.out.println("创建目录:" + destFile.getAbsolutePath());
}
//4.2:获取源目录中所有的文件和目录;File[]数组
File[] fileArray = srcFile.listFiles();
//4.3:遍历File[]数组
for(File f : fileArray){//f-->D:\\bbb\\xxx destFile --> E:\\bbb
//4.4.获取每一个File,进行递归调用,这里注意新的"srcFile"和"destFile"
copyFile(f,destFile);//("E:\\bbb")
}
}else{//是文件
//拷贝
BufferedInputStream in = new BufferedInputStream(new FileInputStream(srcFile));
File f = new File(destFile,srcFile.getName());
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(f));
byte[] byteArray =new byte[1024];
int len = 0;
while((len = in.read(byteArray)) != -1){
out.write(byteArray,0,len);
out.flush();
}
in.close();
out.close();
System.out.println("复制文件:" + srcFile.getAbsolutePath() + " 到:" + f.getAbsolutePath() + " 复制完毕!");
}
}
}
总结:
从上面的一张图我们可以大致的了解到Java的I/O体系内容,有字节流、转换流和字符流,根据上述代码测试并比较后得出结论:相对而言,字节流的复制速度是低于同样复制读入方式的字符流速度的;数组的读入速度是高于单个字节或字符读入方式的速度的;带缓冲区的输入输出速度是高于基本输入输出流速度的。因此,推荐或者说常用的是带缓冲区的输入输出流形式的。读入文本文件,使用字节流和字符流都可以;但对于其他类型的文件,只能使用字节流进行读取和写入,所以某种程度上字节流的适用范围是广于字符输入输出流的。
上述代码中可以总是看到in.flush( )或in.close( ),在使用I/O流的操作中需要养成及时释放资源的习惯,一般执行close( )时,其内部会首先进行flush( )。字符流会将数据进行临时存储,根据编码表解析成二进制数据后才进行传输,在底层其实也使用了字节流。递归的使用大大简便了问题的复杂度,这是以牺牲运算速度换取问题的有效解决的方法。但递归方法在使用时,需要根据程序需求设定好递归的出口,检验并保证递归能够顺利完成,也是保证了代码的鲁棒性。