IO流总结
IO这章的知识在上面一篇博客也说过一点,主要要体会一下装饰者设计模式和适配器设计模式,这样更利于我们理解复杂的IO体系结构。今天就让我们看一看。不过在讲IO
之前,我们先把文件(File)的知识简单过一下。
一、文件File
文件大家都不陌生,查看JDK帮助文档,我们知道File的定义——文件和目录(文件夹)路径名的抽象表示形式。1、构造方法
File(String pathname):根据一个路径得到File对象File(String parent, String child):根据一个目录和一个子文件/目录得到File对象
File(File parent, String child):根据一个父File对象和一个子文件/目录得到File对象
2、创建功能
public boolean createNewFile():创建文件 如果存在这样的文件,就不创建了public boolean mkdir():创建文件夹 如果存在这样的文件夹,就不创建了
public boolean mkdirs():创建文件夹,如果父文件夹不存在,会帮你创建出来
举例:
//需求1:我要在e盘目录下创建一个文件demo
File file = new File("e:\\a.txt");
大家可能会认为这里用了关键字new,那么文件File就会出来了,其实不是,因为File的定义是:文件和目录(文件夹)路径名的抽象表示形式。它仅仅是文件的表示
形式,不是真实的文件。 所以需要调用这样一句代码file.createNewFile()
//需求2:我要在e盘目录test下创建一个文件b.txt
File file = new File("e:\\test\\b.txt");System.out.println("createNewFile:" + file3.createNewFile());
运行之后可以看到控制台打印出:
Exception in thread "main" java.io.IOException: 系统找不到指定的路径。
所以注意:要想在某个目录下创建内容,该目录首先必须存在。我们可以这么做:File file1=new File("e:\\test);file1.mkdir();//先把文件夹(目录)创建出来然后在:
File file2=new File(e:\\test\\b.txt);file2.createNewFile();//在创建文件
//看下面这行代码File file = new File("e:\\liuyi\\a.txt");
System.out.println("mkdirs:" + file8.mkdirs());
你会看到a.txt是个目录,不要认为指定了.txt后缀就是文件了,要看它调用的是creatNewFile()还是mkdir();
3、删除功能
public boolean delete()这个方法比较简单,注意:
A:如果你创建文件或者文件夹忘了写盘符路径,那么,默认在项目路径下。
B:Java中的删除不走回收站。
C:要删除一个文件夹,请注意该文件夹内不能包含文件或者文件夹,所以delete一次只能删除一个文件,如果想删除一个文件夹,那必须要循环遍历了。
4、重命名功能
public boolean renameTo(File dest)如果路径名相同,就是改名。
如果路径名不同,就是改名并剪切。
需求:将G盘所有视频文件该名成“0?-网络编程(概述).avi”,原来视频文件名称为:黑马程序员_毕向东_Java基础视频教程第23天-0?-网络编程(概述).avi
<span style="font-size:18px;">public class FileDemo {执行结果:
private static File dest;
public static void main(String[] args) {
//封装目录
File file=new File("G:\\黑马入学\\网络编程1");
File[] sorcFolder = file.listFiles();
for (File f : sorcFolder) {
String name = f.getName();
//黑马程序员_毕向东_Java基础视频教程第23天-01-网络编程(概述).avi
//System.out.println(name);
int index = name.indexOf('-');
String string = name.substring(index+1);
//再次封装新的文件目录
dest = new File(file,string);
f.renameTo(dest);
}
}
}</span>
一看全部改名成功,哈哈。。。
5、判断功能
public boolean isDirectory():判断是否是目录public boolean isFile():判断是否是文件
public boolean exists():判断是否存在
public boolean canRead():判断是否可读
public boolean canWrite():判断是否可写
public boolean isHidden():判断是否隐藏
6、简单获取功能
public String getAbsolutePath():获取绝对路径public String getPath():获取相对路径
public String getName():获取名称
public long length():获取长度。字节数
public long lastModified():获取最后一次的修改时间,毫秒值
示例代码:
<span style="font-size:18px;"> public class FileDemo {执行结果是:
public static void main(String[] args) {
// 创建文件对象
File file = new File("demo\\test.txt");
System.out.println("getAbsolutePath:" + file.getAbsolutePath());
System.out.println("getPath:" + file.getPath());
System.out.println("getName:" + file.getName());
System.out.println("length:" + file.length());
System.out.println("lastModified:" + file.lastModified());
// 1432518063391
Date d = new Date(1432518063391L);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String s = sdf.format(d);
System.out.println(s);
}
}</span>
7、高级获取功能
public String[] list():获取指定目录下的所有文件或者文件夹的名称数组public File[] listFiles():获取指定目录下的所有文件或者文件夹的File数组
后者功能更为强大一点,因为返回的是File对象,所以可以得到File的各种属性。
8.文件名称过滤器——FilenameFilter
需求:判断E盘目录下是否有后缀名为.jpg的文件,如果有,就输出此文件名称public String[] list(FilenameFilter filter)
public File[] listFiles(FilenameFilter filter)
示例代码:
<span style="font-size:18px;">public class FileDemo {虽然普通的方式也能实现,这样看起来,代码更加优雅,现在我们来看看list(FilenameFilter filter)的源码
public static void main(String[] args) {
//封装e盘
File file=new File("e:\\");
// 获取该目录下所有文件或者文件夹的String数组
String[] list = file.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
//如果是文件并且是以.jpg结尾的,返回true,一旦返回true,name就会被放进String数组中,
return new File(dir, name).isFile() && name.endsWith(".jpg");
}
});
for (String string : list) {
System.out.println(string);
}
}
}</span>
<span style="font-size:18px;">public String[] list(FilenameFilter filter) { String names[] = list(); if ((names == null) || (filter == null)) { return names; } List<String> v = new ArrayList<>(); for (int i = 0 ; i < names.length ; i++) { if (filter.accept(this, names[i])) { v.add(names[i]); } } return v.toArray(new String[v.size()]); }</span>
二、IO相关操作
IO因为体系过于庞大,这里并不面面俱到,捡其重点来说。上面简单说了一下File的使用,File的读取,写入,复制。相信都是我们经常使用的功能。谈起IO流,首先我们要对它有个大致的认识,也就是IO的分类,请看下面:A:按流向分
输入流 读取数据
输出流 写出数据
B:按数据类型
字节流
字节输入流
字节输出流
字符流
字符输入流
字符输出流
如果你不知道什么是字节流,什么是字符流,我告诉你,如果电脑记事本可以打开的并且可以看得懂的,那就是可以用字符流操作的文件,当然也可以用字节流来操作,如果电脑记事本能打开,但是我们看不懂的,那就是用字节流来操作的文件,比如MP3文件、JPG文件等,现在我们来看看IO是怎么来操作File的。
1、字节流操作文件
1.1、FileOutputStream写出数据
A:操作步骤a:创建字节输出流对象
b:调用write()方法
c:释放资源
B:示例代码:
FileOutputStream fos = new FileOutputStream("fos.txt");
fos.write("hello".getBytes());
fos.close();
1.2、FileInputStream读取数据
FileInputStream读取数据A:操作步骤
a:创建字节输入流对象
b:调用read()方法
c:释放资源
B:代码体现:
FileInputStream fis = new FileInputStream("fos.txt");
//方式1,我们一次读取一个字节
int by = 0;
while((by=fis.read())!=-1) {
System.out.print((char)by);
}
//方式2 我们一次读取一个字节数组
byte[] bys = new byte[1024];
int len = 0;
while((len=fis.read(bys))!=-1) {
System.out.print(new String(bys,0,len));
}
fis.close();
1.3、四种方式复制文件效率比较
FileInputStream、FileOutputStream这两个类我们称之为基本字节流,相对于基本字节流,IO中还有高效字节流BufferedInputStream、BufferedOutputStream看完了这个,那么问题来,哪种方式好呢?
下面我们来看一个例子:
需求:把G:\IO流(IO流小结图解).avi复制到当前项目目录下的copy.avi中
字节流四种方式复制文件:
* 基本字节流一次读写一个字节
* 基本字节流一次读写一个字节数组
* 高效字节流一次读写一个字节
* 高效字节流一次读写一个字节数组
G:\IO流(IO流小结图解).av的信息如下:
示例代码:
<span style="font-size:18px;">public class CopyAVI {执行结果:
public static void main(String[] args) throws IOException {
long start = System.currentTimeMillis();
method1("G:\\IO流(IO流小结图解).avi", "copy1.avi");
//method2("G:\\IO流(IO流小结图解).avi", "copy2.avi");
//method3("G:\\IO流(IO流小结图解).avi", "copy3.avi");
//method4("G:\\IO流(IO流小结图解).avi", "copy4.avi");
long end = System.currentTimeMillis();
System.out.println("共耗时:" + (end - start) + "毫秒");
}
// 高效字节流一次读写一个字节数组:
public static void method4(String srcString, String destString)throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcString));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destString));
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bos.close();
bis.close();
}
// 高效字节流一次读写一个字节:
public static void method3(String srcString, String destString)throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcString));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destString));
int by = 0;
while ((by = bis.read()) != -1) {
bos.write(by);
}
bos.close();
bis.close();
}
// 基本字节流一次读写一个字节数组
public static void method2(String srcString, String destString)throws IOException {
FileInputStream fis = new FileInputStream(srcString);
FileOutputStream fos = new FileOutputStream(destString);
byte[] bys = new byte[1024];
int len = 0;
while ((len = fis.read(bys)) != -1) {
fos.write(bys, 0, len);
}
fos.close();
fis.close();
}
// 基本字节流一次读写一个字节
public static void method1(String srcString, String destString)throws IOException {
FileInputStream fis = new FileInputStream(srcString);
FileOutputStream fos = new FileOutputStream(destString);
int by = 0;
while ((by = fis.read()) != -1) {
fos.write(by);
}
fos.close();
fis.close();
}
}</span>
方法一执行:
方法二执行:
方法三执行:
方法四执行:
四种方式的性能差异,通过这个执行结果,想必大家也知道了 ,所以在实际开发中选择哪一个来操作文件,相信大家已经有了选择了,反正我是喜欢用方法四!!
2、字符流操作文件
大家可能会问,操作文件已经有了字节流,为什么还要说字符流呢,哎,谁叫咱们是中国人呢,汉字比较复杂,一个汉字站了两个字节,所以直接用字符流操作不够方便所以就有了字符流。我们来看看字符流的体系结构。2.1字符流体系结构
|--字符流|--字符输入流
Reader
int read():一次读取一个字符
int read(char[] chs):一次读取一个字符数组
|--InputStreamReader
|--FileReader
|--BufferedReader
String readLine():一次读取一个字符串
|--字符输出流
Writer
void write(int ch):一次写一个字符
void write(char[] chs,int index,int len):一次写一个字符数组的一部分
|--OutputStreamWriter
|--FileWriter
|--BufferedWriter
void newLine():写一个换行符
void write(String line):一次写一个字符串
字符流的读写是由基类Reader和Writer来完成的,它们各有两个子类来帮助我们来操作文件。Reader类的子类BufferedReader与Writer的子类BufferedWriter,我们一定可以猜到这是高效字符流操作文件使用的!那么,Reader类的子类InputStreamReader与Writer的子类OutputStreamWriter是干什么的呢?从类的名称来来看,前面是字节的,后面是字符的,我告诉你,这两个类就是转换流,把字节流转换成字符流,那么这有什么用呢?-----键盘录入!!!一会你们就知道了。。。。。注意OutputStreamWriter还有子类FileWriter,InputStreamReader还有子类FileReader。
2.1、字符流操作文件
我们先看一个字符流操作文件的例子。需求:把当前项目目录下的a.txt内容复制到当前项目目录下的b.txt中
数据源:
a.txt -- 读取数据 -- 字符转换流 -- InputStreamReader -- FileReader -- BufferedReader
目的地:
b.txt -- 写出数据 -- 字符转换流 -- OutputStreamWriter -- FileWriter -- BufferedWriter
示例代码:
<span style="font-size:18px;">public class CopyFileDemo {代码解读: bw.newLine();这句话的意思是换行,调用的效果和我们写“\r\n”是一样的,但是newLine可以夸平台的,这就是区别!
public static void main(String[] args) throws IOException {
// 封装数据源
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
// 封装目的地
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
// 两种方式其中的一种一次读写一个字符数组,当然此处你也可以一次读取一个字节的方法
// char[] chs = new char[1024];
// int len = 0;
// while ((len = br.read(chs)) != -1) {
// bw.write(chs, 0, len);
// bw.flush();
// }
// 还有一种方式读写数据,这就是BufferedWriter、BufferedReader的强大之处。。。。
String line = null;
//下面三句话,我习惯连着写,写入、换行、刷新,动作相当连贯有木有啊!!!!!!
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
// 释放资源
bw.close();
br.close();
}
}</span>
readLine()这个方法有点厉害,因为它一次读一行!!!
博客写到这里,我们发现,复制文件文件起码有5种方式,前面所讲的1.基本字节流一次读写一个字节、2.基本字节流一次读写一个字节数组、3.高效字节流一次读写一个字节、4.高效字节流一次读写一个字节数组共4种,再加上上面的一种,共5种,而且第五种是最好的,个人感觉。。。。
3、IO编程实战
3.1、复制单级文件夹
/** 需求:复制指定目录下的指定文件,并修改后缀名。
* 指定的文件是:.java文件。
* 指定的后缀名是:.txt
* 指定的目录是:txt
* 数据源:G:\designModel
* 目的地:G:\txt
*
* 分析:
* A:封装目录
* B:获取该目录下的java文件的File数组
* C:遍历该File数组,得到每一个File对象
* D:把该File进行复制
* E:在目的地目录下改名
*/
* 数据源:G:\designModel如下图所示:
示例代码:
<span style="font-size:18px;">public class CopyFolder {执行结果:
public static void main(String[] args) throws IOException {
//A:封装目录
File srcFolder=new File("G:\\designModel");
//封装目的地
File destFolder=new File("G:\\myNote");
//如果目的地文件不存在,就创建一个
if(!destFolder.exists()){
destFolder.mkdir();
}
//B:获取该目录下的java文件的File数组
File[] srcFiles = srcFolder.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
// 文件过滤器,得到.java文件,去除其他类型的文件
return new File(dir,name).isFile()&&name.endsWith(".java");
}
});
// C:遍历该File数组,得到每一个File对象
for (File file : srcFiles) {
String name = file.getName();
File newFile=new File(destFolder,name);
copyFile(file,newFile);
}
//E:在目的地目录下改名
for (File file :destFolder.listFiles()) {
String name = file.getName();
String newName = name.replace(".java", ".txt");
File newFile=new File(destFolder,newName);
file.renameTo(newFile);
}
}
// D:把该File进行复制
private static void copyFile(File file, File newFile) throws IOException {
BufferedInputStream bis=new BufferedInputStream(new FileInputStream(file));
BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream(newFile));
byte [] bys=new byte[1024];
int len=0;
while((len=bis.read(bys))!=-1){
bos.write(bys,0,len);
}
//关闭资源
bis.close();
bos.close();
}
}</span>
3.2、复制多级文件夹
思路分析
数据源:G:\\
目的地:G:\\
A:封装数据源File
B:封装目的地File
C:判断该File是文件夹还是文件
a:是文件夹
就在目的地目录下创建该文件夹
获取该File对象下的所有文件或者文件夹File对象
遍历得到每一个File对象
回到C
b:是文件
复制(字节流)
示例代码:
<span style="font-size:18px;">public class CopyFolders{
public static void main(String[] args) throws IOException {
// 封装数据源File
File srcFile = new File("G:\\");
// 封装目的地File
File destFile = new File("E:\\");
// 复制文件夹的功能
copyFolder(srcFile, destFile);
}
private static void copyFolder(File srcFile, File destFile)throws IOException {
// 判断该File是文件夹还是文件
if (srcFile.isDirectory()) {
// 文件夹
File newFolder = new File(destFile, srcFile.getName());
newFolder.mkdir();
// 获取该File对象下的所有文件或者文件夹File对象
File[] fileArray = srcFile.listFiles();
for (File file : fileArray) {
copyFolder(file, newFolder);
}
} else {
// 文件
File newFile = new File(destFile, srcFile.getName());
copyFile(srcFile, newFile);
}
}
//复制文件
private static void copyFile(File srcFile, File newFile) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(newFile));
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
}
bos.close();
bis.close();
}
}</span>
3.3 键盘录入5个学生信息(姓名,语文成绩,数学成绩,英语成绩),按照总分从高到低存入文本文件
思路分析:A:创建学生类
B:创建集合对象
因为要排序,所以用TreeSet<Student>
C:键盘录入学生信息存储到集合
D:遍历集合,把数据写到文本文件
<span style="font-size:18px;">public class Student {
// 姓名
private String name;
// 语文成绩
private int chinese;
// 数学成绩
private int math;
// 英语成绩
private int english;
//构造方法和setter/getter方法省去了。。。。。。。。
}</span>
<span style="font-size:18px;">public class StudentIO { public static void main(String[] args) throws IOException { // 创建集合对象(采用匿名内部类的方式,创建带有比较器的集合对象) TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() { @Override public int compare(Student s1, Student s2) { int num = s2.getSum() - s1.getSum(); int num2 = num == 0 ? s1.getChinese() - s2.getChinese() : num; int num3 = num2 == 0 ? s1.getMath() - s2.getMath() : num2; int num4 = num3 == 0 ? s1.getName().compareTo(s2.getName()): num3; return num4; } }); // 键盘录入学生信息存储到集合 for (int x = 1; x <= 5; x++) { Scanner sc = new Scanner(System.in); System.out.println("请录入第" + x + "个的学习信息"); System.out.println("姓名:"); String name = sc.nextLine(); System.out.println("语文成绩:"); int chinese = sc.nextInt(); System.out.println("数学成绩:"); int math = sc.nextInt(); System.out.println("英语成绩:"); int english = sc.nextInt(); // 创建学生对象 Student s = new Student(); s.setName(name); s.setChinese(chinese); s.setMath(math); s.setEnglish(english); // 把学生信息添加到集合 ts.add(s); } // 遍历集合,把数据写到文本文件 BufferedWriter bw = new BufferedWriter(new FileWriter("students.txt")); //以下三行代码连写 bw.write("学生信息如下:"); bw.newLine(); bw.flush(); bw.write("姓名\t语文成绩\t数学成绩\t英语成绩"); bw.newLine(); bw.flush(); for (Student s : ts) { StringBuilder sb = new StringBuilder(); sb.append(s.getName()).append("\t").append(s.getChinese()) .append("\t").append(s.getMath()).append("\t") .append(s.getEnglish()); bw.write(sb.toString()); bw.newLine(); bw.flush(); } // 释放资源 bw.close(); System.out.println("学习信息存储完毕"); }}</span>输入:
执行结果:
好啦,如果上面的例子可以弄懂,IO就基本没有什么问题啦,哈哈。。。