IO流(其二)
五.转换流
在实际开发中,我们经常需要让字符流于字节流相互进行转换,此时我们可以使用InputStreamReader和OutputStreamWriter两个类。这两个类可以说是链接字节流和字符流的桥梁。
1.InputStreamReader
InputStreamReader是用于将字节流转化成字符流的类,它是继承自Reader类的。其实现原理是:将写入流中的字节解码成字符数据。常见操作如下:
① 键盘录入
BufferedReader bufr = new BufferedReader(
new InputStreamReader(System.in)) ; // 此处使用BufferedReader是为了提高效率
② 从硬盘读取文件数据
BufferedReader bufr = new BufferedReader(
new InputStreamReader(new FileInputStream("已有的文件位置"))) ;
2.OutputStreamWriter
OutputStreamWriter是用于将字符流转化成字节流的类,通过字符流操作字节流的对象,将字符转成字节,它是继承自Writer类的。其实现原理是:将写入流中的字符编码成字节。常见操作如下:
① 向控制台输出数据
BufferedWriter bufw = new BufferedWriter(
new OutputStreamWriter(System.out)) ; // 此处使用BufferedReader是为了提高效率
② 向硬盘文件中传输数据
BufferedWriter bufw = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream("目的文件位置"))) ;
通过以下关于字节流字符流相互转换的代码分析图来进一步深入了解InputStreanmReader和OutputStreamWriter实现字节字符相互转换的基本原理:
3.流的操作归规律
在操作流对象的实际开发中,会出现很多种情况的源和目的。根据其规律总结出以下四个步骤来判断流对象的使用:
① 明确源和目的
源(来源):InputStream / Reader ---- 读入内存
目的:OutputStream / Writer ---- 写入设备
② 明确数据是否是纯文本数据
源(来源):Reader ---- 纯文本 ;InputStream ---- 非纯文本
目的:Writer ---- 纯文本 ;OutputStream ---- 非纯文本
③ 明确具体设备
源(来源)设备:FileReader / FileInputStream ---- 硬盘 ;System.in ---- 键盘 ;
数组 ---- 内存 ;Socket流 ---- 网络
目的设备:FileWriter / FileOutputStream ---- 硬盘 ;System.out ---- 键盘 ;
数组 ---- 内存 ;Socket流 ---- 网络
④ 是都需要其他额外功能
例如: 需要高效(缓冲区),则可以使用Buffer……
六.File类
File类是用来将文件或者文件夹(目录)封装成对象进行操作的类,它的引入将方便对文件夹(目录)或者文件进行各种操作。在IO流中也可以将File对象直接作为参数传入到流对象中。
1.创建File类对象
① File f1 = new File("F:\\a.txt") ;
② File f2 = new File("F:\\","a.txt") ;
③ File f = new File("F:\\") ;
File f3 = new File(f,"a.txt") ;
④ File f4 = new File("F:"+File.separator+"a.txt") ;
//以上四种代码编写方式,都可以将一个已存在的或者不存在的文件或目录封装成File对象
2.File类对象常用方法
2.1 获取( File file = new File("F:\\a.txt") ;)
file.getName(); ---- 获取文件对象的名称
file.getAbsolutePath(); ---- 获取文件对象的绝对路径
file.getParent(); ---- 获取父目录,该方法返回new File()里的文件上一级目录
file.getPath(); ---- 获取文件对象的相对路径
file.lenth(); ---- 获取文件对象的长度 // 返回的是long型数据
file.lastModifiled(); ---- 获取文件对象最后一次修改时间
2.2 创建文件
file.creatNameFile(); ---- 在文件对象指定位置创建文件 // 该方法的返回值是boolean型
// 和输出流不一样,如果文件不存在,则创建;如果文件存在,则不创建,此方法不会覆盖原有文件(输出流需要在狗仔方法中指定true)
2.3 创建目录( File dir = new File("F:\\a") ;)
dir.mkdir(); ---- 创建单个文件夹 //该方法的返回值是boolean类型
dir.mkdirs(); ---- 创建多级目录
2.4 删除文件/目录
file.delete(); ---- 删除文件 // 该方法的返回值是boolean型,当文件正在被流操作时删除不了
file.deleteOnExit(); ---- 再退出时删除文件
dir.delete(); ---- 删除目录 // 当目录中有文件时将删除失败,该方法的返回值是boolean型
2.5 判断
file.exits(); ---- 判断文件是否存在
file.isFile(); ---- 判断是否是文件
dir.isDirectory(); ---- 判断是否是目录
file.isAbsolute(); ---- 判断是否是绝对路径
2.6 移动
renameTo(File dest); ---- 重新命名此抽象路径名表示的文件,同时也可以对其进行移动
范例:
File a = new File(“C:\\a.txt”);
File b = new File(“D:\\b.txt”);
a.renameTo(b);
File b = new File(“D:\\b.txt”);
a.renameTo(b);
2.7 系统根目录和容量获取
File[] files = file.listRoot();
// 返回一个File对象数组(在Windows系统下返回的是盘符("C:\","d:\",……))
getFreeSpace(); ---- 返回此抽象路径名指定的分区中未分配的字节数
getTotalSpace(); ---- 返回此抽象路径名指定的分区大小
getUsableSpace(); ---- 返回此抽象路径名指定的分区上可用于此虚拟机的字节数
// 以上三个方法的返回值都是long型
list(); ---- 返回一个字符串数组,这些字符串指定的是此抽象路径名表示的目录中的文件和目录
// 注意:当使用File对象list()方法时,该对象封装的必须是目录,否则会抛出空指针异常。当所封装的对象是系统级目录的时候,同样会抛出空指针异常。
通过以下对文件目录的深度遍历,来进一步了解File类对象的方法以及文件过滤器的使用:(注意:注释中包含知识点和注意点)
先定义一个过滤器:
<span style="font-size:14px;">package cn.itzixue.file;
import java.io.File;
import java.io.FilenameFilter;
//自定义过滤器要实现FilenameFilter接口
public class FilterBy implements FilenameFilter {
private String suffix = null ;
FilterBy(String suffix){
this.suffix = suffix ;
}
//此处所传入的name是需要进行过滤的文件名
public boolean accept(File dir, String name) {
return name.endsWith(suffix) ;
}
}</span>
深度遍历以及过滤出包含(.java)后缀的文件明:
<span style="font-size:14px;">package cn.itzixue.file;
import java.io.File;
public class FileTraverse {
/*
* 范例:深度遍历某目录,并过滤出某后缀名的文件
*
* 思路:
* 指定好一个目录,利用递归的方式将其遍历。
* 利用list(FilenameFilter filter)方法,定义一个过滤器,对遍历到的文件进行过滤
*/
public static void main(String[] args) {
//将目录封装成File对象
File dir = new File("F:"+File.separator+"demo") ;
//定义一个计数器控制输出
int count = 0 ;
listAll(dir,count) ;
System.out.println("----------------------") ;
listFilterName(dir,".java") ;
}
public static void listFilterName(File dir,String suffix) {
//使用list()方法可以遍历指定目录下的文件(包括隐藏文件),并通过过滤器过滤,返回其文件名称
String[] names = dir.list(new FilterBy(suffix)) ;
for(String name : names){
System.out.println(name) ;
}
}
//该方法通过递归实现遍历
public static void listAll(File dir,int count) {
System.out.println(show(count)+"dir : "+dir.getAbsolutePath()) ;
count++ ;
//通过File对象的listFiles()方法可以遍历指定目录下的所有文件,并将遍历到的文件以File对象方式返回
File[] files = dir.listFiles() ;
for(int x=0;x<files.length;x++){
//判断是或否为目录,如果是,继续调用listAll()方法;若不是目录,则将遍历到的文件的绝对路径返回
if(files[x].isDirectory()){
listAll(files[x],count) ;
}else
System.out.println(show(count)+"file : "+files[x].getAbsoluteFile()) ;
}
}
public static String show(int count) {
StringBuilder strb = new StringBuilder() ;
strb.append("|--") ;
for(int x=0;x<count;x++){
//insert()方法可以在指定的位置插入一段字符
strb.insert(0,"| ") ;
}
return strb.toString() ;
}
}</span>
3.递归
通过以上代码,可以发现在方法中再次调用了自己本身的方法,这种方式叫做递归。
1.递归的定义
函数自身直接或者间接的调用到了自身。一个功能在被重复使用,并每次使用时,参与运算的结果和上一次调用有关。这时可以用递归来解决问题。
2.使用递归操作时注意点
1.递归一定明确条件。否则容易栈溢出
2.注意递归的次数,避免因为过多次的递归操作导致内存溢出
通过以下删除包含内容的目录,进一步加深对递归操作的认识: (注意:注释中包含知识点和注意点)
package cn.itzixue.file;
import java.io.File;
public class DirDelete {
/*
* 范例:将有内容的目录删除
*
* 思路:
* 先进行深度遍历,再利用递归的方式从里往外进行删除
*
*/
public static void main(String[] args) {
File dir = new File("F:"+File.separator+"demo") ;
removeAll(dir) ;
}
public static void removeAll(File dir) {
File[] files = dir.listFiles() ;
for(File file : files){
if(file.isDirectory()){
removeAll(file) ;
}else{
System.out.println(file+" : "+file.delete()) ;
}
}
System.out.println(dir+" : "+dir.delete()) ;
}
}