day20(下)_IO流5(递归穷举与删除,Properties,PrintStream,PrintWriter,SequenceInputStream)
1.对目录和文件递归穷举
/* 列出指定目录下的文件或者文件夹,包含子目录中的内容 也就是列出目录下所有的内容 例如:new File(d:\\); 在d盘下还有一个abc目录而abc下还有文件和目录,而用list()方法只能获取到 d盘下的文件和目录 */ package filedemo; import java.util.Arrays; import java.io.File; class FileFilterDemo2{ /* 采用了递归(重复调用使用自身功能),即首先遍历该目录下所有文件/目录 一旦遍历到目录,在调用自身进行遍历. */ public static String level(int n){ StringBuilder sb=new StringBuilder(); sb.append("|-"); while((n--)!=0) sb.insert(0," ");//不断的向"|-"前插入空格 return sb.toString(); } public static void getFiles(File f,int level){ System.out.println(level(level)+f); level++;//递归调用为下一级目录 File[] dirFiles=f.listFiles();//获取到该目录下所有的文件和目录,其中File对象都采用File(File parent,String child)构造 if(dirFiles==null||dirFiles.length==0)//说明传入的pathName不表示目录或目录为空,那么不需要遍历 return; else for(File file : dirFiles){ if(file.isDirectory())//当file中的对象的child依然是目录接着遍历 getFiles(file,level); else System.out.println(level(level)+file); } } public static void main(String[] args){ getFiles(new File("f:\\a"),0); } }关于递归注意事项:
/* 递归注意: 一定要有出口(也就是限定条件),不然相当于死循环 递归次数过多,会导致OutOfMemoryError(内存溢出) */ class RecursionTest{ /*十进制->二进制*/ public static void toBin(int num){ if(num>0) { toBin(num/2); System.out.print(num%2); } } /*求前n项和*/ public static int getSum(int n){ if(n==1) return 1; else return n+getSum(n-1); } public static void main(String[] args){ toBin(10); System.out.println("\n"+getSum(80000));//不断在内存中开辟空间->OutOfMemoryError(内存溢出) } }
2.递归删除指定目录:
/* 删除一个带内容的目录 删除原理: 在windows中,删除目录从目录里面往外删除 从里往外删->递归 */ package filedemo; import java.io.File; class RemoveDir{ public static void removeDir(File f){ File[] allFile=f.listFiles(); if(allFile==null||allFile.length==0)//空文件夹/文件 直接删除 System.out.println(f.delete()); else{//遍历到最内层->删除,删除完内层->逐步往外删 for(File files : allFile){ if(files.isFile()) System.out.println(files.delete()); else removeDir(files); } System.out.println(f.delete());//当for执行完,说明该目录下文件已经全部删除,需要删除该目录 } } public static void main(String[] args){ removeDir(new File("h:\\a")); } }对于运行结果,(依然是上面遍历的测试目录)会删除9次,会有9个boolean值并且必须都为true,否则可能为同一个文件/目录多次删除
3.File练习:
/* 获取到指定目录中所有java文件的路径,并输出到一个文件中. 思想: ①对目录进行递归搜索 ②对于文件只要以.java结尾文件 ③把.java文件路径存入ArrayList集合中. */ package filedemo; import java.io.File; import java.io.IOException; import java.io.BufferedWriter; import java.io.FileWriter; import java.util.ArrayList; import java.util.List; class JavaFilesList{ public static void getJavaFiles(File dir,List<File> list){ File[] files=dir.listFiles(); if(files!=null && files.length!=0) for(File f : files) if(f.isDirectory()) getJavaFiles(f,list); else if(f.getName().endsWith(".java")) list.add(f); } public static void writeFile(List<File> list,String javaListFile){ BufferedWriter bfw=null; File[] javaFiles=list.toArray(new File[list.size()]);//集合转数组 try{ bfw=new BufferedWriter(new FileWriter(javaListFile)); for(File f : javaFiles){ bfw.write(f.getAbsolutePath());//获取到绝对路径写入,list存入File对象不一定封装的是绝对路径,可能是相对路径 bfw.newLine(); bfw.flush(); } bfw.write("一共"+list.size()+"个java文件"); } catch(IOException e){ throw new RuntimeException("写文件异常"); } finally{ try{ if(bfw!=null) bfw.close(); } catch(IOException e){ throw new RuntimeException("关闭流异常"); } try{ Runtime.getRuntime().exec("notepad.exe "+javaListFile);//写完后,自动用记事本打开查看内容 } catch(IOException e){ e.printStackTrace(); } } } public static void main(String[] args){ List<File> list=new ArrayList<File>(); getJavaFiles(new File("g:\\JavaCode"),list); System.out.println(list.size()); writeFile(list,"f:\\JavaFiles.txt");//写入文件 } }将制定文件夹从源路径复制到目的路径下:
/* 采用递归方法遍历文件夹中的内容 例如:C:\s\abc->d:\ 将abc文件夹及文件夹的所有文件/文件夹拷贝到d盘下 AbsolutePath abc 1.txt ->d:\abc def ->d:\abc\ def 2.txt ->d:\abc\def\ 2.txt ghk ->d:\abc\def\ ghk 3.txt ->d:\abc\def\ghk\ 3.txt mnl ->d:\abc\mnl */ /* 递归过程: 1. src: c:\s\abc dest: d:\ dest<-d:\abc 创建abc copyFile(c:\s\abc\1.txt,d:\abc\1.txt) copyDir(c:\s\abc\def,d:\abc) 2. src:c:\s\abc\def dest:d:\abc dest<-d:\abc\def 创建def copyFile(c:\s\abc\def\2.txt,d:\abc\def\2.txt) copyDir(c:\s\abc\def\ghk,d:\abc\def) 3.... */ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; class CopyDirectory{ public static void copyFile(File src,File dest)throws IOException{ FileInputStream fis=new FileInputStream(src); FileOutputStream fos=new FileOutputStream(dest); byte[] byteArr=new byte[1024*1024]; int bytes=0; while((bytes=fis.read(byteArr))!=-1) fos.write(byteArr,0,bytes); fis.close(); fos.close(); } public static void copyDir(File src,File dest)throws IOException{ File[] files=src.listFiles(); dest=new File(dest.getPath()+"\\"+src.getName());//例如:第一次 创建 d:+\+abc dest.mkdir(); // 第二次 创建 d:\abc +\+ def for(File f : files) if(f.isFile()) copyFile(new File(f.getAbsolutePath()),new File(dest.getPath()+"\\"+f.getName()));//例如:当遍历到1.txt时 //C:\s\abc\1.txt,d:\abc\1.txt else//对于文件夹操作 copyDir(new File(f.getAbsolutePath()),dest);//例如:当遍历到def时 //C:\s\abc\def,d:\abc } public static void main(String[] args)throws IOException{ copyDir(new File("C:\\s\\abc"),new File("d:\\"));//abc文件夹以及文件夹中的内容拷贝到D盘下 System.out.println(new File("d:\\def\\"+"\\"+"\\"+"abc").getPath()); System.out.println(new File("d:\\def\\"+"abc")); /*以上两者结果相同(d:\def\abc),也就是说文件系统中会去除多余的\,但是d:\\与d:结果不同,一个为d:\\,一个为d:
*/ } }
4.Properties类方法:
/* 在System类中的获取系统属性用到Properties Properties 类表示了一个持久的属性集。 Properties 可保存在流中或从流中加载。 属性列表中每个键及其对应值都是一个字符串。 该集合是和IO技术相结合的集合容器 该对象特点:可以用于键值对形式的配置文件(.ini) 在加载数据时,需要数据有固定格式:Key=Value */ package propertydemo; import java.util.Properties; import java.util.Set; import java.io.FileReader; import java.io.FileWriter; import java.io.BufferedReader; import java.io.IOException; class PropertiesDemo{ /* 自定义方法将文件中的数据存储到Properties集合中 该文件中的数据是以键值对形式存在 例如:info.txt: zhangsan=18 lisi=25 ... */ public static void storeInProperties()throws IOException{ BufferedReader bfr=new BufferedReader(new FileReader("info.txt")); Properties pro=new Properties(); String line=null; while((line=bfr.readLine())!=null) if(!line.startsWith("#")){//注释信息不再加入集合 String[] keyValue=line.split("=");//以"="分割 pro.setProperty(keyValue[0],keyValue[1]); } System.out.println(pro); bfr.close(); } /* 利用Properties中的load方法完成 void load(InputStream inStream) 从输入流中读取属性列表(键和元素对)。 void load(Reader reader) 按简单的面向行的格式从输入字符流中读取属性列表(键和元素对)。 */ public static void storeInProperties_2()throws IOException{ FileReader fr=new FileReader("info.txt"); Properties pro=new Properties(); pro.load(fr); System.out.println(pro); pro.list(System.out);// void list(PrintStream out) //将属性列表输出到指定的输出流。 /* 此时如果改变了键值对信息,但是改变后的信息 依然在内存中,如果想把改变反映到文件中,需要用到 store方法 */ pro.setProperty("zhangsan","30"); System.out.println(pro); FileWriter fw=new FileWriter("info.txt"); pro.store(fw,"heihei");//heihei为注释信息 //在文件中为#heihei fr.close(); fw.close(); } /*Properties存储和获取方法*/ public static void propertiesMethodDemo(){ Properties prop=new Properties(); prop.setProperty("zhang","15");//底层在使用HashTable<Object,Object>的put方法 prop.put("li","30"); System.out.println(prop); Set<String> names=prop.stringPropertyNames();//返回Key的set集合 for(String n : names) System.out.println(n+"="+prop.getProperty(n)); } public static void main(String[] args)throws IOException{ propertiesMethodDemo(); System.out.println(); storeInProperties(); System.out.println(); storeInProperties_2(); } }
5.Properties的load与store练习:
/* 需求:用于记录应用程序运行次数.如果使用次数已到,那么 给出注册提示. 分析: 很容易想到的是:计数器 可是该计数器 用程序的退出,该计数器也在内存中消失了 下一次在启动该程序,又重新开始从0计数. 这样不是所需要的 程序即使结束,该计数器的值也存在 下次程序启动会先加载该计数器的值 并+1后重新存储起来 所以需要建立一个配置文件.用于记录软件使用次数 该配置文件使用键值对的形式 这样便于阅读数据,并操作数据 键值对数据是map集合. 数据是以文件形式存储,使用io技术 那么map+io-->properties 配置文件可以实现应用程序数据共享 */ package propertiestest; import java.util.Properties; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.File; class PropertiesTest{ /*每次程序运行获取使用次数(counts),并在获取完之后,++counts存入到配置文件中*/ public static void checkCounts()throws IOException{ File f=new File("set.ini"); Properties pro=new Properties(); if(!f.exists())//文件第一次不存在需要创建 f.createNewFile(); FileReader fr=new FileReader(f); pro.load(fr); int count=0; String value=pro.getProperty("counts"); if(value!=null)//新建文件中没有counts,此时不再获取,只写 count=Integer.parseInt(pro.getProperty("counts")); if(count>=3){//该软件只能够使用三次 System.out.println("免费次数已用完"); return;//>=3不再写入直接返回 } FileWriter fw=new FileWriter(f); pro.setProperty("counts",(++count)+""); pro.store(fw,"number of use"); fr.close(); fw.close(); /* 关于pro.store(): 所有写入各个项后,刷新输出流(内部在把属性集写入输出流后调用了flush) 此方法返回后,输出流仍保持打开状态。 但没有关闭写入流 */ } public static void main(String[] args)throws IOException{ checkCounts(); } } /* 注意:①配置文件不存在,需要创建 ②需要写入键值对 */
6.PrintStream与PrintWriter
/* 打印流: 该流提供了打印方法,可以将各种数据类型的数据都原样打印 字节打印流:PrintStream PrintStream(File file) file- 同下 PrintStream(String fileName) fileName - 要用作此打印流目标的文件名称。 如果存在该文件,则将其大小截取为零; 否则,创建一个新文件。将输出写入文件中,并对其进行缓冲处理。 PrintStream(OutputStream out) 字符打印流:PrintWriter PrintWriter(File file) PrintWriter(OutputStream out)根据现有的 OutputStream 创建不带自动行刷新的新 PrintWriter。此便捷构造方法创建必要的中间 OutputStreamWriter,后者使用默认字符编码将字符转换为字节。PrintWriter(String fileName) PrintWriter(Writer out)//比PrintStream多了一个可以接收字符输出流的构造函数 */ package printdemo; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.IOException; import java.io.PrintStream; import java.io.PrintWriter; import java.io.FileWriter; class PrintStreamDemo{ public static void main(String[] args)throws IOException{ BufferedReader bufr =new BufferedReader(new InputStreamReader(System.in)); //PrintWriter out=new PrintWriter(System.out);//PrintWriter通用性比较强,在写入流后,需要刷新/*
System.out在内部会被封装为new BufferedWriter(new OutputStreamWriter(System.out))
*/ //PrintWriter out=new PrintWriter(System.out,true);//autoFlush-boolean 变量;如果为 true,则 println、printf 或 format 方法将刷新输出缓冲区,下面由于用到println所以可以不用out.flush PrintWriter out=new PrintWriter(new FileWriter("PrintWriter.txt"),true);//true含义同上,这样写可以把new FileWriter中的数据刷新到文本中,而不用在out.flush String line=null; while((line=bufr.readLine())!=null){ out.println(line);//将值打印初始化的流对象中 //out.flush();//刷新的是初始化时的流对象 } bufr.close(); out.close(); } }
7.SequenceInputStream:
/* SequenceInputStream(顺序输入流): 表示其他输入流的逻辑串联。 它从输入流的有序集合开始, 并从第一个输入流开始读取, 直到到达文件末尾, 接着从第二个输入流读取, 依次类推,直到到达包含的最后一个输入流的文件末尾为止。 该类的由来: 例如: 已知:四个文件:src1.txt,src2.txt,src3.txt,dest.txt; 需求:把前三个文件中数据写入到dest.txt中 分析: src1.txt--->FileReader("src.txt")--->FileWriter("dest.txt",true)-->dest.txt src2.txt,src3.txt同理 每个源和目的需要单独的IO流对象操作,非常麻烦 SequenceInputStream作用:对多个源依次操作,更加方便. */ package sequenceinputstream; import java.io.*; import java.util.*; class SequenceDemo{ public static void main(String[] args)throws IOException{ /* SequenceInputStream(Enumeration<? extends InputStream> e) 该构造函数形参为枚举->集合Vector<E>的elements返回Enumeration<E> */ Vector<FileInputStream> vector=new Vector<FileInputStream>(); vector.add(new FileInputStream("f:\\src1.txt")); vector.add(new FileInputStream("f:\\src2.txt")); vector.add(new FileInputStream("f:\\src3.txt")); Enumeration<FileInputStream> en=vector.elements(); SequenceInputStream sis=new SequenceInputStream(en); BufferedOutputStream bufos=new BufferedOutputStream(new FileOutputStream("f:\\dest.txt")); //FileOutputStream fis=new FileOutputStream("f:\\dest.txt"); int aByte=0; //byte[] b=new byte[1024]; while((aByte=sis.read())!=-1){ bufos.write(aByte); bufos.flush(); } /* int bytes; while((bytes=sis.read(b))!=-1) fis.write(b,0,bytes); } */ bufos.close(); sis.close(); }}
文件切割和合并综合练习:
/* 切割文件:其实就是一个源(该文件)对应多个目的(多个碎片文件) 切割 ①需要一个输入流对象关联该文件 ②为了控制每次读取大小,自定义一个缓冲(数组) ③循环读取,每次读取将读取的数据,通过输出流写入一个文件中,直至源文件末尾(-1) 合并 多个源对应一个目的,这里可以采用SequenceInputStream */ import java.io.FileInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.io.SequenceInputStream; import java.util.Enumeration; import java.util.Iterator; class SplitFileTest{ /*当文件不太大时,指定的缓冲区可以为分割的文件大小*/ public static void splitFile_1(String file)throws IOException{ /*以切割图片为例*/ FileInputStream fis=new FileInputStream(file); byte[] b=new byte[1024*1024];//每次最多分割1MB FileOutputStream fos=null; int bytes=0,count=1; while((bytes=fis.read(b))!=-1){ fos=new FileOutputStream("f:\\part"+(count++)+".part");//每次新建一个流对象关联一个新的文件 fos.write(b,0,bytes); fos.close();//每次循环关闭上次的流对象 } fis.close(); } /*当文件特别大(例如2GB),而需要按400MB分割,不建议定义400MB缓冲区,定义小点缓冲区40MB,使切割文件满足制定大小*/ public static File[] splitFile_2(File f)throws IOException{ FileInputStream fis=new FileInputStream(f); byte[] byteArr=new byte[1024*1024]; ArrayList<File> arrList=new ArrayList<File>(); int bytes=0; int count=0; File wf=null; FileOutputStream fos=null; while((bytes=fis.read(byteArr))!=-1){ if(wf==null||wf.length()==2*1024*1024){ if(fos!=null) fos.close();//关闭与上一个文件相关联的写入流对象 wf=new File("C:\\Users\\ZhangHongQ\\Desktop\\java复习\\"+(++count)+".part"); arrList.add(wf); fos=new FileOutputStream(wf); } fos.write(byteArr,0,bytes); } fis.close(); return arrList.toArray(new File[arrList.size()]); } /*对以上分割的文件进行合并*/ public static void mergeFile(File[] file)throws IOException{ ArrayList<FileInputStream>al=new ArrayList<FileInputStream>(); for(File f : file) al.add(new FileInputStream(f)); final Iterator<FileInputStream>it=al.iterator(); SequenceInputStream sis=new SequenceInputStream(new Enumeration<FileInputStream>(){ public boolean hasMoreElements(){ return it.hasNext(); } public FileInputStream nextElement(){ return it.next(); } }); FileOutputStream fos=new FileOutputStream(new File("C:\\Users\\ZhangHongQ\\Desktop\\java复习\\Bandari.mp3")); int aByte=0; while((aByte=sis.read())!=-1) fos.write(aByte); sis.close(); fos.close(); } public static void main(String[] args)throws IOException{ File[] file=splitFile_2(new File("F:\\SONG\\Bandari - 爱尔兰风笛.mp3"));//例如:8.93MB的MP3文件 System.out.println(Arrays.toString(file));//2MB 2MB 2MB 2MB 934KB mergeFile(file); } }