1、File类概述
File是文件和目录路径名的抽象表示形式。 用来将文件或者文件夹封装成对象,方便对文件与文件夹的属性信息进行操作。
前面说到的“流”,它只能操作数据,想要操作由数据封装成的文件的信息,必须使用File对象。File对象可以作为参数传递给流的构造函数。
2、File的常见方法
了解File类中的常用方法。
File类常见方法:
1,创建。boolean createNewFile():在指定位置创建文件,如果该文件已经存在,则不创建,返回false。和输出流不一样,输出流对象一建立创建文件,而且如果文件已经存在,新文件会覆盖旧文件。boolean mkdir():创建文件夹。boolean mkdirs():创建多级文件夹。
2,删除。boolean delete():删除失败返回false。如果文件正在被使用,则删除不了返回false。void deleteOnExit();在程序退出时删除指定文件。3,判断。boolean exists():文件是否存在.isFile():测试此抽象路径名表示的文件是否是一个标准文件。isDirectory():测试此抽象路径名表示的文件是否是一个目录。isHidden():测试此抽象路径名指定的文件是否是一个隐藏文件。isAbsolute():测试此抽象路径名是否为绝对路径名。4,获取信息。String getName():返回由此抽象路径名表示的文件或目录的名称。String getPath():将此抽象路径名转换为一个路径名字符串。String getParent():返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回 null。File getAbsolutePath():返回此抽象路径名的绝对路径名形式。long lastModified():返回此抽象路径名表示的文件最后一次被修改的时间。long length():返回由此抽象路径名表示的文件的长度。
方法示例
package pack;
import java.io.*;
import java.util.Properties;public class FileDemo
{public static void main(String[] args) throws IOException {
// ConsMethod();method_5();} //创建一个File对象,这里只是在各级目录下创建新 File 实例,并没有真正创建文件或者文件夹,这些需要通过File的方法创建public static void ConsMethod() {//File(String pathname):将“a.txt”封装成为File对象,可以将已有的和未出现的文件或者文件夹封装成对象。File f1 = new File("a.txt");//下面2种方法其实一样,只不过一个表现为父目录,一个先将父目录封装为文件对象//File(String parent, String child) File f2 = new File("G:\\java","b.txt");//=File f2 = new File("G:\\java\\b.txt");//File(File parent, String child) File g = new File("G:\\java");File f3 = new File(g,"c.txt");System.out.println(f1);System.out.println(f2);System.out.println(f3);/*结果如下:打印文件的路径a.txt:相对路径G:\java\b.txt:绝对路径G:\java\c.txt“\\”是目录封装符,也可以写作“/”,在不同操作系统下有不同的目录封装符,我们可以使用“separator”字段来表示,保证其跨平台性。File g = new File("G:\\java"); = File g = new File("G:"+File.separator+"java");*/ }//添加删除public static void method_1() throws IOException{File f1 = new File("a.txt");System.out.println(f1.createNewFile());//在“F:\研究生学习资料\java\eclipse\eclipse\workspace\Test”下创建一个“a.txt”文件,且控制台显示“true”//再运行一次,文件还在,控制台显示“false”,创建失败——文件已经存在不会重复创建System.out.println(f1.delete());//控制台返回“true”,且原目录下“a.txt”文件被删除,再运行一次返回false,无法重复删除//对deleteOnExit方法,即使文件操作发生异常,最后虚拟机退出也一定会删除文件(20-02.8.00解析)//创建文件夹File dir = new File("G:\\abc"); System.out.println("mkdir:"+dir.mkdir());//mkdir:true,并且相应路径下多了一个abc文件夹}//判断public static void method_2()throws IOException{//判断文件能否执行File f1 = new File("file.txt");System.out.println("execute:"+f1.canExecute());//execute:false:文件不存在,只有File对象,不可以执行//在相对应的目录下放FileDemo.java文件File f2 = new File("FileDemo.java");System.out.println("execute:"+f2.canExecute());//execute:true:文件存在,可以执行//判断文件是否存在System.out.println(f1.exists());//falseSystem.out.println(f2.exists());//true }//判断2public static void method_3()throws IOException{File f = new File("haha.txt"); // f.createNewFile();//加上这个:dir:false;file:true,创建名为haha的txt文件
// f.mkdir();//加上这个:dir:true;file:false,创建名为haha.txt的文件夹System.out.println("dir:"+f.isDirectory());//dir:false:不是目录System.out.println("file:"+f.isFile());//file:false:因为文件根本不存在(没有通过creatNewFile()创建)//记住在判断文件对象是否是文件或者目录时,必须要通过exists,先判断该文件对象封装的内容是否存在。System.out.println(f.isAbsolute());//false,不是绝对路径,是相对路径File f1 = new File("G:\\java");System.out.println(f1.isAbsolute());//false,是绝对路径}//获取public static void method_4()throws IOException{File f = new File("file.txt");//按相对路径起名//getPath:封装什么路径就获取什么路径;getAbsolutePath:获取绝对路径。System.out.println("path:"+f.getPath());//path:file.txtSystem.out.println("abspath:"+f.getAbsolutePath());//F:\研究生学习资料\java\eclipse\eclipse\workspace\Test\file.txtSystem.out.println("parent:"+f.getParent());//null//该方法返回的是绝对路径中的父目录。如果获取的是相对路径,返回null。//如果相对路径中有上一层目录那么该目录就是返回结果。}//重命名public static void method_5(){//public boolean renameTo(File dest):注意要变名字的文件必须事先存在,而且变名后原来的文件会消失(如这里f1必须存在)File f1 = new File("G:\\FileDemo.java");//在该位置放置这样一个文件File f2 = new File("G:\\hahah.java");System.out.println("rename:"+f1.renameTo(f2));//rename:true,且FileDemo.java名字变为hahah.java}
}
3、File的特殊方法
listRoots()、list()、list(FilenameFilter filter)、listFiles()的示例如下:
package pack;
import java.io.*;
import java.util.Properties;public class FileDemo
{public static void main(String[] args) throws IOException {
// listRootsDemo();
// listDemo();
// listDemo_2();listFileDemo();} public static void listRootsDemo() throws IOException{//static File[] listRoots() 列出可用的文件系统根。 File[] file = File.listRoots();//遍历for(File f:file){System.out.println(f);}/*C:\D:\E:\F:\G:\H:\J:\*/}public static void listDemo(){//String[] list():返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。File file = new File("G:\\");//如果这里返回一个文件而不是文件夹,会发生空指针异常,因为文件不是目录,返回的数组为空。//调用list方法的file对象必须是封装了一个目录,该目录还必须存在。String[] names = file.list();for(String name : names){System.out.println(name);}/*列出当前目录下所有问价以及文件夹——包含隐藏文件$RECYCLE.BINjavaSystem Volume Information*/}//文件名过滤方法示例public static void listDemo_2(){File dir = new File("G:\\");//先查看该文件夹下的所有文件String[] names = dir.list();for(String name: names) {System.out.println(name);}System.out.println();//String[] list(FilenameFilter filter) 返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中满足指定过滤器的文件和目录。//FilenameFilter:实现此接口的类实例可用于过滤器文件名。那么我们需要创建一个类来实现FilenameFilter接口,重写accept方法//这里FilenameFilter只有accept这一个方法,可以使用匿名内部类// boolean accept(File dir, String name):测试指定文件是否应该包含在某一文件列表中。 String[] arr = dir.list(new FilenameFilter() {public boolean accept(File dir , String name){
// if(name.endsWith(".java"))
// return true;
// else
// return false;
// list()方法在依据FilenameFilter对象的accept方法的返回值判断文件是否存储到arr数组中return name.endsWith(".java");//上面可以直接写作这一句(注意平时代码必须这样写!!!)}});
//这里list(FilenameFilter filter)方法需要一个FilenameFilter类型的参数,我们用匿名内部类创建一个重写accept()方法的FilenameFilter对象,
//传进给list()方法即可,这里不需要调用accept()方法,因为list()方法传入的是实现FilenameFilter接口的类的对象,我们使用匿名内部类
//就相当于将一个这种类的对象存入list,list方法底层会自己通过这个对象调用重写的accept()方法System.out.println(arr.length);for(String name : arr){System.out.println(name);}}//listFile()方法:将当前目录下的文件与文件夹对象封装为File对象,而不仅仅是list()方法的返回其String类型的名称public static void listFileDemo(){File dir = new File("G:\\");// File[] listFiles():返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。 File[] files = dir.listFiles();for(File f : files){System.out.println(f.getName()+"----"+f.length());//返回文件与文件夹名字与文件大小}}
}
4、递归
什么是递归?
列出指定目录下文件或者文件夹,包含子目录中的内容。也就是列出指定目录下所有内容。因为目录中还有目录,只要使用同一个列出目录功能的函数完成即可。
在列出过程中出现的还是目录的话,还可以再次调用本功能,也就是函数自身调用自身。
这种表现形式,或者编程手法,称为递归。递归要注意:(见视频20-07,9.30)
1、限定条件。(不注意条件程序容易陷入死循环)
2、要注意递归的次数。尽量避免内存溢出。
递归的示例如下:
package pack;
import java.io.*;
import java.util.Properties;public class FileDemo
{public static void main(String[] args) throws IOException {
//直接遍历G盘等根目录下文件就会发生空指针异常,而遍历根目录下一级就没有这种情,这是因为根目录下有一些隐藏文件java无法删除,这样返回的file数组为空,发生空指针异常。File dir = new File("G:\\test\\");showDir(dir);} public static void showDir(File dir) {//先返回dir所指目录下的所有文件以及文件夹的File对象File[] files = dir.listFiles();System.out.println("目录:"+dir);for(int x=0; x<files.length; x++){
// System.out.println(files[x]);//文件与文件夹名称全部打印,我们还想知道文件夹里面的内容if(files[x].isDirectory())//如果还是目录,就调用showDir()方法继续遍历showDir(files[x]);//因为已经将文件夹封装为File对象,所以其可以作为showDir的参数elseSystem.out.println(files[x]);}/** 第二部分,将各级目录下的内容全部打印出来*/}
}
下面我们通过几个方法演示一下递归的原理
package pack;
import java.io.*;
import java.util.Properties;public class FileDemo
{public static void main(String[] args) throws IOException {
// toBin_2(6);int n = getSum(10);//我们发现传入的数太大,会因为递归太多次而发送内存溢出System.out.println(n);} //我们用取一个十进制数二进制的方法为例public static void toBin_1(int num) {while(num>0){System.out.println(num%2);num = num/2;}
/*反向打印了6的二进制
0
1
1
*/}//其实上面这个方法一直在执行循环,那么其实可以用递归的方法来实现(20-07,11.00)public static void toBin_2(int num) {if(num>0){toBin_2(num/2);//直到num/2不大于0,递归就会停止System.out.println(num%2); }}
/*正向打印了6的二进制——因为toBin_2(num/2);在前,是从num=0开始结束递归,从最里面一层开始打印,既从尾打印到头。
1
1
0*///2个数相加——试用递归public static int getSum(int num) {if(num == 1)return 1;elsereturn num+getSum(num-1);}
}
我们前面在使用递归打印的时候发现目录显示得不是很清楚,可以通过如下方法来进行优化。
package pack;
import java.io.*;
import java.util.Properties;public class FileDemo
{public static void main(String[] args) throws IOException {File dir = new File("G:\\test");showDir(dir, 0);} //首先,创建打印每一层文件格式的类public static String getLevel(int level){StringBuilder sb = new StringBuilder();sb.append("|--");//先添加开头部分for(int x=0; x<level; x++)//用循环添加每一层格式多出来的部分{
// sb.append("| ");sb.insert(0, "| ");//从头开始插入这部分效果更加好看}return sb.toString();}public static void showDir(File dir , int level){System.out.println(FileDemo.getLevel(level)+dir.getName());//打印首层文件的名字level++;//将层级自加1,到下一层的文件夹中File[] files = dir.listFiles();//获取首层文件夹中文件与文件夹的File对象for(int x=0; x<files.length; x++)//根据这一层文件夹与文件的数量循环遍历判断{if(files[x].isDirectory())showDir(files[x],level);//递归继续遍历文件夹elseSystem.out.println(FileDemo.getLevel(level)+files[x].getName());}}
}
/*效果
|--test
| |--a.txt
| |--haha
| | |--info.txt
| |--java
| | |--dbv
| | | |--SequenceDemo.java
| | | |--SplitFile.java
| | |--RunCount.java
| | |--trge
| | |--递归2.bmp
| |--SplitFile.java
| |--递归.bmp
*/
5、删除带内容的目录
示例如下
/*
删除一个带内容的目录。
删除原理:在window中,删除目录从里面往外删除的。既然是从里往外删除,就需要用到递归。
注意!!!java的删除不会存放到回收站!!!
*/
package pack;
import java.io.*;public class RemoveDirDemo
{public static void main(String[] args) throws IOException {File dir = new File("G:\\test");//添加判断隐藏目录之后可以操作根目录RemoveDir(dir);} public static void RemoveDir(File dir){File[] files = dir.listFiles();for(int x=0 ; x<files.length; x++){
//再判断一下隐藏,java无法访问隐藏目录,就可以对根目录进行操作。当然删除的话不添加,是不是隐藏都删除,只不过根目录下一些隐藏文件无法访问if(!(files[x].isHidden()) && files[x].isDirectory())RemoveDir(files[x]);//是文件夹就继续删除elseSystem.out.println(files[x].getName()+"--file--"+files[x].delete());}System.out.println(dir.getName()+"--dir--"+dir.delete());//将dir文件夹里面的内容删除后,将这个文件夹本身也删除}
}
6、练习——创建java文件列表,并存储到一个txt文件中
示例如下
/*
toString():返回File对象的绝对路径
getName():获取File对象的名字——包含后缀
*/
/*
练习
将一个指定目录下的java文件的绝对路径,存储到一个文本文件中,建立一个java文件列表文件。思路:
1、对指定的目录进行递归。
2、获取递归过程所以的java文件的路径。
3、将这些路径存储到集合中。
4、将集合中的数据写入到一个文件中。*/
package pack;
import java.io.*;
import java.util.*;public class JavaFileListDemo
{public static void main(String[] args) throws IOException {File dir = new File("G:\\test");//创建要遍历目录的File对象
//创建ArrayList集合用于保存获取到的“.java”文件的File对象,因为要存储的是对象,而且数目不确定,要求有序,便于查询,因此用ArrayList//我们获取文件对下后其实可以直接写入,但是保存到一个集合中便于做其他操作。List<File> list = new ArrayList<File>();//调用fileToList()方法,将操作目录的File对象保存到List集合中fileToList(dir,list);//创建新的File对象,该对象是最后生成的保存路径文件的对象File file = new File("G:\\","SaveJave.txt");//将list集合与最后保存文件的路径传入writeToFile()方法writeToFile(list,file);//将list集合与最后存储的文件的File对象存入} //将文件存入list集合的方法public static void fileToList(File dir , List<File> list){File[] files = dir.listFiles();//创建dir目录下文件与文件夹的对象//用for循环遍历files数组的每一个元素for(int x=0 ; x<files.length ; x++){if(files[x].isDirectory())fileToList(files[x],list);//是目录的话将目录以及list传入,继续递归添加文件对象到list//判断获取的文件是否是“.java”文件。先获取文件的名字,再判断其结尾else if(files[x].getName().endsWith(".java")) list.add(files[x]);//将文件添加到list中}}//将“.java”文件的绝对路径写入文件的方法public static void writeToFile(List<File> list , File file) throws IOException{//这里只需要写入缓冲区,不需要读取缓冲区。我们直接获取list集合中文件File对象的绝对路径(String)//再将绝对路径写入到文件中即可BufferedWriter bufw = null;try{//创建缓冲区写入流的对象bufw = new BufferedWriter(new FileWriter(file));//FileWriter可以接收File对象//接下来遍历集合listfor(File f : list){String path = f.getAbsolutePath();//获取list中文件的绝对路径bufw.write(path);//将path写入文件bufw.newLine();bufw.flush();}}catch(IOException e){throw e;//处理不了异常,往外抛出,方法处得throws IOException(),想处理就抛RuntimeException}finally{try{if(bufw != null)bufw.close();}catch(IOException e){throw e;//处理不了异常,往外抛出}}}
}
7、Properties类
Properties的特点如下(见视频20-11的解析)
Properties是Hashtable的子类,也就是说它具备map集合的特点。而且它里面存储的键值对都是字符串。(不需要用泛型描述)Properties是集合中和IO技术相结合的集合容器。该对象的特点:可以用于键值对形式的配置文件(所谓的配置文件就是软件中的配置信息存储的文件)。
那么在加载数据时,需要数据有固定格式:键=值。我们软件会将其配置信息以键值的形式(Properties类操作)保存在相关文件中,每次运行到相关内容的时候,都要获取相关的配置文件,操作数据以加载相关的配置信息,这个时候就需要用到IO流技术(IO流相关类操作)。
Properties的示例如下
package pack;
import java.io.*;
import java.util.*;public class PropertiesDemo
{public static void main(String[] args) throws IOException {
// setAndGet();
// method_1();loadDemo();} // 设置和获取元素。public static void setAndGet(){//创建Properties对象——类似于集合对象Properties prop = new Properties();//Object setProperty(String key, String value):调用 Hashtable 的方法 put。 prop.setProperty("zhangsan", "23");prop.setProperty("lisi", "24");System.out.println(prop);//获得类似于map集合的串:{zhangsan=23, lisi=24}//获取相关键的值String getProperty(String key):用指定的键在此属性列表中搜索属性。 String value = prop.getProperty("lisi");System.out.println("value:"+value);//value:24prop.setProperty("lisi",89+"");//修改键的值//遍历方法:Set<String> stringPropertyNames():返回此属性列表中的键集,其中该键及其对应值是字符串 Set<String> spn = prop.stringPropertyNames();//获取Set对象,注意这里必须带上泛型——按照文档中给的技术来//可以用迭代器也可以用foreachIterator<String> it = spn.iterator();//获取迭代器while(it.hasNext()){String keys = it.next();String values = prop.getProperty(keys);System.out.println(keys+"---"+values);
// zhangsan---23
// lisi---89}}//演示,如何将流中的数据存储到集合中。/*想要将info.txt中键值数据存到集合中进行操作。1、用一个流和info.txt文件关联。2、读取一行数据,将该行数据用"="进行切割。3、等号左边作为键,右边作为值。存入到Properties集合中即可。*/public static void method_1() throws IOException{//操作的是字符串,高效:使用读取缓冲区来BufferedReader来读取文件信息BufferedReader bufr = new BufferedReader(new FileReader("G:\\info.txt"));//创建Properties类的对象,方便操作属性Properties prop = new Properties();String line = null;while((line = bufr.readLine()) != null){//每读取一行就用“=”将键与值分开,该数组有2个值,第一个是键,第二个是相应的值String[] arr = line.split("="); prop.setProperty(arr[0], arr[1]);//设置属性}bufr.close();//注意将流关闭System.out.println(prop);//{zhangsan=23, lisi=34, wangwu=25, nfsjv=43}}//java提供了专门的方法读取信息文件的键与值public static void loadDemo()throws IOException{//void load(InputStream inStream):从输入流中读取属性列表(键和元素对)。 FileInputStream fis = new FileInputStream("G:\\info.txt");Properties prop = new Properties();prop.load(fis);//将InputStream类型的流对象存入,该流对象指向一个info文件
// System.out.println(prop);//{zhangsan=23, lisi=34, wangwu=25, nfsjv=43}prop.setProperty("vsrg", "56");//设置新的键与值//为了将设置好的新的键与值存入info.txt,我们创建一个新的字节写入流FileOutputStream fos = new FileOutputStream("G:\\info.txt");// void store(OutputStream out, String comments) (comment是注释)//以适合使用 load(InputStream) 方法加载到 Properties 表中的格式,将此 Properties 表中的属性列表(键和元素对)写入输出流。prop.store(fos, "haha");//将prop中的键与值写入输出流System.out.println(prop);//void list(PrintStream out):将属性列表输出到指定的输出流。
// prop.list(System.out);//我们指定prop的输出为控制台fis.close();fos.close();//注意关闭
/** 第一次直接输出{vsrg=56, zhangsan=23, lisi=34, wangwu=25, nfsjv=43} * 用list后格式好看很多
-- listing properties --
vsrg=56
zhangsan=23
lisi=34
wangwu=25
nfsjv=43
*/}
}
Properties练习
/*
用于记录应用程序运行次数,如果使用次数已到,那么给出注册提示。很容易想到的是:计数器。
可是该计数器定义在程序中,随着程序的运行而在内存中存在,并进行自增。可是随着该应用程序的退出,该计数器也在内存中消失了。
下一次在启动该程序,又重新开始从0计数,这样不是我们想要的。程序即使结束,该计数器的值也存在。下次程序启动在会先加载该计数器的值并加1后在重新存储起来。
所以要建立一个配置文件,用于记录该软件的使用次数。该配置文件使用键值对的形式,这样便于阅读数据,并操作数据。
键值对数据是map集合,数据是以文件形式存储,使用io技术。那么map+io -->properties.配置文件可以实现应用程序数据的共享。
*/
package pack;
import java.io.*;
import java.util.*;//配置信息的后缀要么是.properties,要么是.xml
public class RunCountDemo
{public static void main(String[] args) throws IOException {Properties prop = new Properties();//在操作文件的时候,先把文件封装成为File对象!封装完之后可以对文件进行操作。File file = new File("G:\\count.ini"); //如果文件不存在,我们自己创建一个if(!file.exists())file.createNewFile();//前面这样判断完之后文件一定存在,那么流一定不会报文件不存在的异常//通过InputStream流来读入文件信息FileInputStream fis = new FileInputStream(file);//前面已经通过File对象链接相关文件,这里只需要将File对象存入即可prop.load(fis);//将流信息加载到prop对象中String value = prop.getProperty("time");//获取time键所对应的值int count = 0;//创建计数器//判断这一次读取进来的值是否为空,不为空就将其转换为count并判断使用次数是否到//这个时候count已经被赋值为文件中记录的值,不再是前面的0if(value!=null){count = Integer.parseInt(value);//将count转换为int类型if(count>=5){System.out.println("您好,使用次数已到,拿钱!");return ;//这里的return代表程序已经结束}}//运行一次就将计数器自加1count++;prop.setProperty("time", Integer.toString(count));//将新的计数值count转换为String类型并存入prop//注意,输出流必须写到这里,然后相应的配置才能通过输出流写到文件,如果写到上面和读取流在一起,就无法将Properties相应配置写入文件FileOutputStream fos = new FileOutputStream(file);//同样通过file对象链接相关文件即可prop.store(fos,"");//将prop中的键值信息加载到fos流,fos流再写入相应文件fis.close();fos.close();}
}
8、PrintWriter类与PrintStream类
PrintWriter与PrintStream类的特点如下
打印流:
该流提供了打印方法,可以将各种数据类型的数据都原样打印。
之前我们写出的时候还需要用字符或者字符串写出,是比较麻烦的,这里直接传入什么数据就打印什么数据。字节打印流:PrintStream
构造函数可以接收的参数类型:
1、file对象。File
2、字符串路径。String
3、字节输出流。OutputStream字符打印流:PrintWriter
构造函数可以接收的参数类型:
1、file对象。File
2、字符串路径。String
3、字节输出流。OutputStream(字符打印流也可以接收字节流输出对象)
4、字符输出流,Writer。
示例如下:
package pack;
import java.io.*;
import java.util.*;//配置信息的后缀要么是.properties,要么是.xml
public class PrintStreamDemo
{public static void main(String[] args) throws IOException {//注意,读取键盘的常规操作!记死!BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));//接收一个Writer类的子类FileWriter类的对象PrintWriter out = new PrintWriter(new FileWriter("G:\\lkj.txt"),true);//true:autoFlush - boolean 变量;如果为 true,则 println、printf 或 format 方法将刷新输出缓冲区//如果这里写成字符串路径,将不会自动刷新,只有等out关闭的时候,才会刷新将内容加载到文件。或者是手动添加flush方法//而对于流,流会每读取一行就刷新并将内容写入文件。尽量用流来描述!//接下来读取键盘输入,读到over就停止String line = null;while((line=bufr.readLine())!=null){if(line.equals("over"))break;out.println(line);//写入并换行,使用println不需要用flush刷新,因为上面设置PrintWriter构造方法为true,会自动刷新//也可以写作:out.write(line);out.flush();但是这里没有newLine不能换行,所以还是不要用这种方法}out.close();bufr.close();}
}
9、合并流
SequenceInputStream :SequenceInputStream 表示其他输入流的逻辑串联。它从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。 也就是可以合并多个流对象(20-16,1.40)示例如下:
package pack;
import java.io.*;
import java.util.*;//多个源对应一个目的
public class SequenceDemo
{public static void main(String[] args) throws IOException {//SequenceInputStream(Enumeration<? extends InputStream> e) //通过记住参数来初始化新创建的 SequenceInputStream,该参数必须是生成运行时类型为 InputStream 对象的 Enumeration 型参数。//既然接收的是Enumeration对象,我们用Vector对象来存储要合并的流对象Vector<FileInputStream> v = new Vector<FileInputStream>();v.add(new FileInputStream("G:\\lkj1.txt"));//添加FileInputStream对象v.add(new FileInputStream("G:\\lkj2.txt"));v.add(new FileInputStream("G:\\lkj3.txt"));//使用Enumeration对象来遍历Enumeration en = v.elements();//将Enumeration对象存入SequenceInputStream对象SequenceInputStream sis = new SequenceInputStream(en);FileOutputStream fos = new FileOutputStream("G:\\Sequence.txt");//创建一个输出流//SequenceInputStream只支持数组类型的读取或者字符类型的读取byte[] buf = new byte[1024];int len = 0;while((len=sis.read(buf)) != -1){fos.write(buf,0,len);//注意加len限制写入有效内容}sis.close();fos.close();}
}
10、切割流
切割流的示例如下
package pack;
import java.io.*;
import java.util.*;//多个源对应一个目的
public class SplitFile
{public static void main(String[] args) throws IOException {
// spiltFile();mergeFile();}//切割流+一个读取流对应多个写出流public static void spiltFile() throws IOException{//先用FileInputStream读取一个要分裂的文件FileInputStream fis = new FileInputStream("G:\\pic.bmp");//接下来创建一个写入流对象的引用,由于需要多个写入流对应不同文件,我们这里先将引用赋值null,后面再为其赋予对象FileOutputStream fos = null;//接下来用fis读取每一个buf数组的数据,就创建一个写入流对象,并存入一个文件int len = 0;int count = 1;//创建一个计数器用于区分每一个分裂文件的文件名byte[] buf = new byte[1024*1024];//按照1M的大小来切割文件while((len=fis.read(buf)) != -1){fos = new FileOutputStream("G:\\"+(count++)+".part");//切割后得到碎片文件,因此我们自己创建一个后缀为“.part”fos.write(buf,0,len);//将这一次数组遍历到的内容写入这一次对应的文件fos.close();//由于文件只需要在一次循环中使用一次,因此每个循环的结尾关闭写出流。}fis.close();}//切割完之后我们可可以试着合并public static void mergeFile() throws IOException{//我们需要Enumeration对象,当然可以使用前面的Vector集合来做//但是,Vector集合的效率比较低,而且,使用Enumeration对象的时候,也是在底层调用Enumeration的hasElements()方法与elements()方法//因此我们可以创建ArrayList集合的迭代器,用迭代器的hasNext()方法与next()方法//将Enumeration的hasElements()方法与elements()方法重写//创建ArrayList对象ArrayList<FileInputStream> al = new ArrayList<FileInputStream>();//用循环将之前分裂的3个文件所对应的读取流添加到alfor(int x=1; x<=3 ; x++){al.add(new FileInputStream("G:\\"+x+".part"));}final Iterator<FileInputStream> it = al.iterator();//it用于匿名内部类,匿名内部类使用的局部变量必须为final//创建Enumeration对象,用匿名内部类的方式重写hasElements()方法与elements()方法Enumeration<FileInputStream> en = new Enumeration<FileInputStream>() {//重写2个方法之后,后面合并流调用en的hasElements()方法与elements()方法的时候,其实是在操作al的迭代器方法//这样就可以操作ArrayList数组;之前没有重写之前,hasElements()方法与elements()方法操作的是Vector集合@Overridepublic boolean hasMoreElements() {return it.hasNext();}@Overridepublic FileInputStream nextElement() {// TODO Auto-generated method stubreturn it.next();}};FileOutputStream fos = new FileOutputStream("G:\\merge.bmp");//解析:因为SequenceInputStream需要一个Enumeration类型的对象作为传入参数,并且底层调用hasMoreElement()与nextElement()方法,这些方法只能操作Vector集合。但是Vector集合效率不高。我们用Iterator的hasNext()与next()方法重写hasMoreElement()与nextElement()方法,这样hasMoreElement()与nextElement()方法就可以操作ArrayList集合,而ArrayList集合的效率高SequenceInputStream sis = new SequenceInputStream(en);int len = 0;byte[] buf = new byte[1024*1024];while((len=sis.read(buf)) != -1){fos.write(buf,0,len);//合并后写入文件}fos.close();sis.close();}
}
11、就业班补充
补充1:
分隔符
/*static String pathSeparator 与系统有关的路径分隔符,为了方便,它被表示为一个字符串。(返回字符串类型)static char pathSeparatorChar 与系统有关的路径分隔符。(返回字符类型)static String separator 与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。static char separatorChar 与系统有关的默认名称分隔符。操作路径:路径不能写死了C:\develop\a\a.txt windowsC:/develop/a/a.txt linux"C:"+File.separator+"develop"+File.separator+"a"+File.separator+"a.txt"
*/String pathSeparator = File.pathSeparator;System.out.println(pathSeparator);//路径分隔符 windows:分号; linux:冒号:String separator = File.separator;System.out.println(separator);// 文件名称分隔符 windows:反斜杠\ linux:正斜杠/
补充2:
正斜杠、反斜杠与双斜杠,参考下面几篇文章
路径中 斜杠/和反斜杠\ 的区别
https://blog.csdn.net/openxw/article/details/79927869
关于文件路径的反斜杠正斜杠和双斜杠问题
关于路径反斜杆的转义字符问题
假设,对于一个路径“D:\资源\黑马就业班\01.JavaSE Java语言基础\08.File类与IO流\第1节 File类”,我们在java中的字符串使用的时候必须写为“D:\资源\黑马就业班\01.JavaSE Java语言基础\08.File类与IO流\第1节 File类”,因为字符串中“\”为转义字符,必须再加一个“\”表示""。
补充3:
相对路径创建对象的注意点
import java.io.File;
import java.io.IOException;public class FileTest
{public static void main(String[] args) throws IOException{/*如果我们想要通过相对路径创建一个“G:\test\test-level1-1\test-level2-1\Demo02Recurison.java”的文件对象,打印绝对路径发现打印的是“G:\idea_java_project\demo-project1\Demo02Recurison.java”既无法通过相对路径创建我们想创建的路径下的文件对象,会在当前项目的文件夹下创建一个同名的文件*/File dir = new File("Demo02Recurison.java");System.out.println(dir.getAbsolutePath());}
}
补充4:
关于使用java创建文件的目的地
package lkj.demo1;import java.io.File;
import java.io.IOException;public class FileTest
{public static void main(String[] args) throws IOException{//如果我们使用相对路径直接创建文件,会在项目下的文件夹创建文件File file1 = new File("1.txt");System.out.println(file1.createNewFile());//如果我们使用绝对路径创建文件,就会在指定的目录下面创建相应的文件File file2 = new File("G:\\idea_java_project\\demo-project1\\demo-module1\\2.txt");System.out.println(file2.createNewFile());}
}
补充5:
递归的注意事项:
- 递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。
- 在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出。
- 构造方法,禁止递归。(编译报错:构造方法是创建对象使用的,一直递归会导致内存中有无数多个对象,直接编译报错)
递归的使用前提:当调用方法的时候,方法的主体不变,每次调用方法的参数不同,可以使用递归
递归导致栈内存移除原理图
递归计算1-n和的原理图
递归——文件搜索案例
package com.itheima.demo02.Recursion;import java.io.File;/*练习:递归打印多级目录需求:遍历c:\\abc文件夹,及abc文件夹的子文件夹只要.java结尾的文件c:\\abcc:\\abc\\abc.txtc:\\abc\\abc.javac:\\abc\\ac:\\abc\\a\\a.jpgc:\\abc\\a\\a.javac:\\abc\\bc:\\abc\\b\\b.javac:\\abc\\b\\b.txt*/
public class Demo05Recurison {public static void main(String[] args) {File file = new File("c:\\abc");getAllFile(file);}/*定义一个方法,参数传递File类型的目录方法中对目录进行遍历*/public static void getAllFile(File dir){//System.out.println(dir);//打印被遍历的目录名称File[] files = dir.listFiles();for (File f : files) {//对遍历得到的File对象f进行判断,判断是否是文件夹if(f.isDirectory()){//f是一个文件夹,则继续遍历这个文件夹//我们发现getAllFile方法就是传递文件夹,遍历文件夹的方法//所以直接调用getAllFile方法即可:递归(自己调用自己)getAllFile(f);}else{//f是一个文件,直接打印即可/*c:\\abc\\abc.java只要.java结尾的文件1.把File对象f,转为字符串对象*///String name = f.getName();//abc.java//String path = f.getPath();//c:\\abc\\abc.java//String s = f.toString();//c:\\abc\\abc.java//把字符串,转换为小写//s = s.toLowerCase();//2.调用String类中的方法endsWith判断字符串是否是以.java结尾//boolean b = s.endsWith(".java");//3.如果是以.java结尾的文件,则输出/*if(b){System.out.println(f);}*/if(f.getName().toLowerCase().endsWith(".java")){System.out.println(f);}}}}
}
补充6:
文件过滤器优化
java.io.FileFilter
是一个接口,是File的过滤器。 该接口的对象可以传递给File类的listFiles(FileFilter)
作为参数, 接口中只有一个方法。
boolean accept(File pathname)
:测试pathname是否应该包含在当前File目录中,符合则返回true。
分析:
- 接口作为参数,需要传递子类对象,重写其中方法。我们选择匿名内部类方式,比较简单。
accept
方法,参数为File,表示当前File下所有的子文件和子目录。保留住则返回true,过滤掉则返回false。保留规则:- 要么是.java文件。
- 要么是目录,用于继续遍历。
- 通过过滤器的作用,
listFiles(FileFilter)
返回的数组元素中,子文件对象都是符合条件的,可以直接打印。
代码实现:
public class DiGuiDemo4 {public static void main(String[] args) {File dir = new File("D:\\aaa");printDir2(dir);}public static void printDir2(File dir) {// 匿名内部类方式,创建过滤器子类对象File[] files = dir.listFiles(new FileFilter() {@Overridepublic boolean accept(File pathname) {return pathname.getName().endsWith(".java")||pathname.isDirectory();}});// 循环打印for (File file : files) {if (file.isFile()) {System.out.println("文件名:" + file.getAbsolutePath());} else {printDir2(file);}}}
}
代码实现2——Lambda表达式优化
package com.itheima.demo03Filter;import java.io.File;/*在File类中有两个和ListFiles重载的方法,方法的参数传递的就是过滤器File[] listFiles(FileFilter filter)java.io.FileFilter接口:用于抽象路径名(File对象)的过滤器。作用:用来过滤文件(File对象)抽象方法:用来过滤文件的方法boolean accept(File pathname) 测试指定抽象路径名是否应该包含在某个路径名列表中。参数:File pathname:使用ListFiles方法遍历目录,得到的每一个文件对象File[] listFiles(FilenameFilter filter)java.io.FilenameFilter接口:实现此接口的类实例可用于过滤器文件名。作用:用于过滤文件名称抽象方法:用来过滤文件的方法boolean accept(File dir, String name) 测试指定文件是否应该包含在某一文件列表中。参数:File dir:构造方法中传递的被遍历的目录String name:使用ListFiles方法遍历目录,获取的每一个文件/文件夹的名称注意: 两个过滤器接口是没有实现类的,需要我们自己写实现类,重写过滤的方法accept,在方法中自己定义过滤的规则*/
public class Demo02Filter {public static void main(String[] args) {File file = new File("c:\\abc");getAllFile(file);}/*定义一个方法,参数传递File类型的目录方法中对目录进行遍历*/public static void getAllFile(File dir){//传递过滤器对象 使用匿名内部类/*File[] files = dir.listFiles(new FileFilter() {@Overridepublic boolean accept(File pathname) {//过滤规则,pathname是文件夹或者是.java结尾的文件返回truereturn pathname.isDirectory() || pathname.getName().toLowerCase().endsWith(".java");}});*///使用Lambda表达式优化匿名内部类(接口中只有一个抽象方法)/*File[] files = dir.listFiles((File pathname)->{return pathname.isDirectory() || pathname.getName().toLowerCase().endsWith(".java");});*/File[] files = dir.listFiles(pathname->pathname.isDirectory() || pathname.getName().toLowerCase().endsWith(".java"));/*File[] files = dir.listFiles(new FilenameFilter() {@Overridepublic boolean accept(File dir, String name) {//过滤规则,pathname是文件夹或者是.java结尾的文件返回truereturn new File(dir,name).isDirectory() || name.toLowerCase().endsWith(".java");}});*///使用Lambda表达式优化匿名内部类(接口中只有一个抽象方法)/*File[] files = dir.listFiles((File d, String name)->{//过滤规则,pathname是文件夹或者是.java结尾的文件返回truereturn new File(d,name).isDirectory() || name.toLowerCase().endsWith(".java");});*///File[] files = dir.listFiles((d,name)->new File(d,name).isDirectory() || name.toLowerCase().endsWith(".java"));for (File f : files) {//对遍历得到的File对象f进行判断,判断是否是文件夹if(f.isDirectory()){//f是一个文件夹,则继续遍历这个文件夹//我们发现getAllFile方法就是传递文件夹,遍历文件夹的方法//所以直接调用getAllFile方法即可:递归(自己调用自己)getAllFile(f);}else{//f是一个文件,直接打印即可System.out.println(f);}}}
}
过滤器原理图如下:
补充7:
Properties的使用(注意Properties首先是一个集合,然后才与IO流相关联,我们必须先用集合的思想对其进行思考)
package com.itheima.demo07.Prop;import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
import java.util.Set;/*java.util.Properties集合 extends Hashtable<k,v> implements Map<k,v>Properties 类表示了一个持久的属性集。Properties 可保存在流中或从流中加载。Properties集合是一个唯一和IO流相结合的集合可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用属性列表中每个键及其对应值都是一个字符串。Properties集合是一个双列集合,key和value默认都是字符串(也就是不需要再写泛型)*/
public class Demo01Properties {public static void main(String[] args) throws IOException {show03();}/*可以使用Properties集合中的方法load,把硬盘中保存的文件(键值对),读取到集合中使用void load(InputStream inStream)void load(Reader reader)参数:InputStream inStream:字节输入流,不能读取含有中文的键值对Reader reader:字符输入流,能读取含有中文的键值对使用步骤:1.创建Properties集合对象2.使用Properties集合对象中的方法load读取保存键值对的文件3.遍历Properties集合注意:1.存储键值对的文件中,键与值默认的连接符号可以使用=,空格(其他符号)2.存储键值对的文件中,可以使用#进行注释,被注释的键值对不会再被读取3.存储键值对的文件中,键与值默认都是字符串,不用再加引号*/private static void show03() throws IOException {//1.创建Properties集合对象Properties prop = new Properties();//2.使用Properties集合对象中的方法load读取保存键值对的文件prop.load(new FileReader("09_IOAndProperties\\prop.txt"));//prop.load(new FileInputStream("09_IOAndProperties\\prop.txt"));//3.遍历Properties集合Set<String> set = prop.stringPropertyNames();for (String key : set) {String value = prop.getProperty(key);System.out.println(key+"="+value);}}/*可以使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储void store(OutputStream out, String comments)void store(Writer writer, String comments)参数:OutputStream out:字节输出流,不能写入中文Writer writer:字符输出流,可以写中文String comments:注释,用来解释说明保存的文件是做什么用的不能使用中文,会产生乱码,默认是Unicode编码一般使用""空字符串使用步骤:1.创建Properties集合对象,添加数据2.创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地3.使用Properties集合中的方法store,把集合中的临时数据,持久化写入到硬盘中存储4.释放资源*/private static void show02() throws IOException {//1.创建Properties集合对象,添加数据Properties prop = new Properties();prop.setProperty("赵丽颖","168");prop.setProperty("迪丽热巴","165");prop.setProperty("古力娜扎","160");//2.创建字节输出流/字符输出流对象,构造方法中绑定要输出的目的地//FileWriter fw = new FileWriter("09_IOAndProperties\\prop.txt");//3.使用Properties集合中的方法store,将Properties集合中的键值对数据加载到写出流,持久化写入到硬盘中存储//prop.store(fw,"save data");//4.释放资源//fw.close();//字节流不能写出中文,否则会出现乱码//这里的直接留不需要关闭,因为匿名对象使用完之后会自动将该流释放prop.store(new FileOutputStream("09_IOAndProperties\\prop2.txt"),"");}/*使用Properties集合存储数据,遍历取出Properties集合中的数据Properties集合是一个双列集合,key和value默认都是字符串Properties集合有一些操作字符串的特有方法Object setProperty(String key, String value) 调用 Hashtable 的方法 put。String getProperty(String key) 通过key找到value值,此方法相当于Map集合中的get(key)方法Set<String> stringPropertyNames() 返回此属性列表中的键集,其中该键及其对应值是字符串,此方法相当于Map集合中的keySet方法*/private static void show01() {//创建Properties集合对象Properties prop = new Properties();//使用setProperty往集合中添加数据prop.setProperty("赵丽颖","168");prop.setProperty("迪丽热巴","165");prop.setProperty("古力娜扎","160");//prop.put(1,true);//使用stringPropertyNames把Properties集合中的键取出,存储到一个Set集合中Set<String> set = prop.stringPropertyNames();//遍历Set集合,取出Properties集合的每一个键for (String key : set) {//使用getProperty方法通过key获取valueString value = prop.getProperty(key);System.out.println(key+"="+value);}}
}
补充8:
打印流PrintStream示例1
package com.itheima.demo05.PrintStream;import java.io.FileNotFoundException;
import java.io.PrintStream;/*java.io.PrintStream:打印流PrintStream 为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。PrintStream特点:1.只负责数据的输出,不负责数据的读取2.与其他输出流不同,PrintStream 永远不会抛出 IOException3.有特有的方法,print,printlnvoid print(任意类型的值)void println(任意类型的值并换行)构造方法:PrintStream(File file):输出的目的地是一个文件PrintStream(OutputStream out):输出的目的地是一个字节输出流PrintStream(String fileName) :输出的目的地是一个文件路径PrintStream extends OutputStream继承自父类的成员方法:- public void close() :关闭此输出流并释放与此流相关联的任何系统资源。- public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。- public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。- public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。- public abstract void write(int b) :将指定的字节输出流。注意:如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表 97->a如果使用自己特有的方法print/println方法写数据,写的数据原样输出 97->97*/
public class Demo01PrintStream {public static void main(String[] args) throws FileNotFoundException {//System.out.println("HelloWorld");//创建打印流PrintStream对象,构造方法中绑定要输出的目的地PrintStream ps = new PrintStream("10_IO\\print.txt");//如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表 97->aps.write(97);//如果使用自己特有的方法print/println方法写数据,写的数据原样输出 97->97ps.println(97);ps.println(8.8);ps.println('a');ps.println("HelloWorld");ps.println(true);//释放资源ps.close();}
}
打印流PrintStream示例2
package com.itheima.demo05.PrintStream;import java.io.FileNotFoundException;
import java.io.PrintStream;
/*可以改变输出语句的目的地(打印流的流向)输出语句,默认在控制台输出使用System.setOut方法改变输出语句的目的地改为参数中传递的打印流的目的地static void setOut(PrintStream out)重新分配“标准”输出流。*/
public class Demo02PrintStream {public static void main(String[] args) throws FileNotFoundException {System.out.println("我是在控制台输出");//控制台输出这一句话PrintStream ps = new PrintStream("10_IO\\目的地是打印流.txt");System.setOut(ps);//把输出语句的目的地改变为打印流的目的地System.out.println("我在打印流的目的地中输出");//在相应的文件中输出这句话ps.close();}
}