File类和IO流
提及到递归
1.File 类
1.1File 引入
java中专门用于操作文件或者文件夹的一个类 (File:文件(.java,.txt文件 .csv文件 .xls .xlsx) 目录(文件夹))(对文件或者文件夹进行增删改查)
File类能够实现以下功能:
上传头像,或者导入导出数据,清空文件,统计一下目录中的文件数量等等...
为什么需要使用文件?
使用文件存储数据永久保存,断电数据不会丢失...
使用文件读取效率不高,而且操作复杂(处理业务数据一般不会使用文件流操作) 数据库
1.2File类
常用构造方法:
new File(String filePath)
filePath:文件的路径(相对路径【相对于当前的项目目录】 建议使用,绝对路径【相对于磁盘中的位置】);
注意:由于操作系统不同,操作路径不能写死
获取当前系统的路径分隔符
String pathSeparator = File.pathSeparator;
System.out.println(pathSeparator);//路径分隔符 :
String separator = File.separator;
System.out.println(separator); //文件名称分隔符 /
常用方法:
exists():判断文件是否存在(存在返回true ,不存在返回false)
createNewFile():创建新文件
delete():删除文件或者删除空的文件夹
length():返回文件的大小,单位是字节(byte)
isFile():判断是否是一个文件(是文件返回true 不是文件返回false)
isDirectory():判断是否是一个文件夹(是文件夹返回 true,不是文件夹返回false)
getName():获得文件或者目录的名字
getPath():获得文件的相对路径
getAbsolutePath():绝对路径
getParent():获得上一级目录的路径
mkdir(): 创建指定的单层目录
mkdirs():创建多级目录
listFile():得到文件夹里面全部子目录 返回的是一个File对象数组 File[]
操作文件的实例代码:
public static void main(String[] args) throws IOException {
File file1=new File("file/test01.txt");
doFile(file1);
}
public static void doFile(File file) throws IOException {
//1.判断文件是否存在,不存在创建文件,存在不创建
if(!file.exists()) {
file.createNewFile();
System.out.println("创建文件成功...");
}else {
System.out.println("文件已经存在了...");
}
System.out.println("获得文件的大小:"+file.length()+"字节");
System.out.println(file.isFile()?"是文件":"不是文件");
System.out.println("获得文件的名字:"+file.getName());
System.out.println("获得文件的相对路径:"+file.getPath());
System.out.println("获得文件的绝对路径:"+file.getAbsolutePath());
System.out.println("获得上一级目录的路径:"+file.getParent());
System.out.println("判断是否是一个目录:"+file.isDirectory());
if(file.exists()) {
file.delete();
System.out.println("删除文件成功...");
}else {
System.out.println("文件不存在...");
}
}
文件夹的操作
public static void main(String[] args) {
File file1 =new File("file01");
if(!file1.exists()) {
file1.mkdir();
}else {
System.out.println("目录已经存在...");
}
//1.mkdir()只能创建单层目录,mkdirs()可以创建多层目录
File file2=new File("file02/test01/test02");
if(!file2.exists()) {
System.out.println(file2.mkdirs()?"创建成功":"多级目录创建失败");
}else {
System.out.println("目录已经存在...");
}
}
//子目录操作
public static void main(String[] args) {
File file=new File("file03");
if(file.exists()&&file.isDirectory()) {
File[] fileList=file.listFiles();
for(File f:fileList) {
System.out.println(f.getPath());
}
}
System.out.println(file.delete()?"删除成功":"删除失败");
//delete()只能删除文件和空文件夹 删除非空文件夹思路,先从内部删除,然后再删除外面的(由内至外删除)
}
1.3递归
定义:递归,指在当前方法内调用自己的这种现象。
递归分为两种,直接递归和间接递归。
直接递归称为方法自身调用自己。间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法。
调用:
1.当有多个函数相互调用时,按照后调用先返回的原则,上述函数之间信息传递和控制转移必须借助栈来实现,
即系统整个运行程序运行时所需要的数据空间安排在一个栈中,每当调用一个函数时,就在栈顶分配一个存储区,进行压栈操作,
每当一个函数退出时,就释放它的存储区,就行出栈操作,当前运行的函数永远都在栈顶位置。
2.A函数调用A函数和A函数调用B函数在计算机看来没有任何区别,只是我们日常的思维方式理解比较怪异而已。
注意:
1.递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出。
2.构造方法不能递归
public void method(){
method();
}
一般情况下,递归可以使用循环替代,但是它们各有其优缺点:
递归:
易于理解
速度慢
存储空间大
循环:
不易于理解
速度快
存储空间小
1.3.1计算1~num的和,使用递归完成
public class DiGuiDemo {
public static void main(String[] args) {
int n = 5;
int sum = getSum(n);
System.out.println(sum);
}
public static int getSum(int n) {
if(n == 1){
return 1;
}
return n + getSum(n-1);
}
}
1.3.2汉诺塔
规则:
将A塔上面的盘子借助B移到B;
再将A柱子的第n个盘子直接移到C;
再将B柱子上的n -1个盘子借助A移到C。
描述:
if(n > 1) {
先把A柱子上n-1个盘子从A借助C移动到B;
再将A柱子的第n个盘子直接移到C;
再将B柱子上的n-1个盘子借助A移到C。
}
伪算法:
void hannuota(int n,char A,char B,char C) {
if(1 == n)
printf(“将编号为%d的盘子直接从%c的柱子移到%c柱子”,n,A,C);
else {
hannuota(n - 1,A,C,B);
printf(“将编号为%d的盘子直接从%c的柱子移到%c柱子”,n,A,C);
hannuota(n – 1,B,A,C);
}
}
图解:递归过程中会保存局部变量数据,所以只需执行这几步就行。
1.4递归搜索非空文件夹
判断File是否是目录(文件夹),
是目录 先打印目录名字,再获取其子目录File[],然后将子目录进行遍历,判断是文件还是目录,
如果是文件则直接打印输出其文件名字,
如果是目录,打印目录名字,再获取其子目录File[],然后将子目录进行遍历,判断是文件还是目录
是文件则直接打印输出其文件名字。
如果是目录,......
得出结论什么时候使用递归(当子File属于目录的时候使用递归)
代码实现:
//2.递归检索复杂目录(统计一下安装包里面有多少个文件以及文件夹组成)
//3.再统计一下有多少个java文件?
public static void getFile(File file) {
//1.只有目录才有递归的必要
if(file.isDirectory()) {
//count++;
//System.out.println(file.getAbsolutePath());
File[] fs=file.listFiles();
for(File f:fs) {
if(f.isDirectory()) {//是目录递归
getFile(f);
}else {
//是文件则打印信息以及统计
String fileName=f.getName();
if(fileName.endsWith(".java")) {
System.out.println(f.getAbsolutePath());
count++;
}
}
}
}
}
1.5归删除非空文件夹
判断是否是目录
是目录是空的直接删除
是目录 不是空的 listFiles()得到所有的子目录,遍历子目录,判断如果是文件则直接删除,
如果是目录 是空的直接删除
是目录 不是空的 listFiles()得到所有的子目录,遍历子目录,判断如果是文件则直接删除,
全部删除完毕之后记得删除文件夹
代码实现:
//1.递归删除(调用delete() 只能删除文件以及空文件夹)
public static void deleteDir(File file) {
if(file.isDirectory()) {//只会递归目录
File[] fs=file.listFiles();
if(fs.length!=0) {//非空文件夹(先删除子目录,然后再删除自己)
//1.遍历删除子目录
for(File f:fs) {
if(f.isDirectory()) {
deleteDir(f);
}else {
f.delete();//是文件就直接删除
}
}
//2.子目录删除之后,自己变成空文件夹删除自己
file.delete();
}else {//空目录
file.delete();//可以直接删除
} //if 2
} //if 1
}
2.IO流
流按照数据的流向可分为输入流和输出流。输入输出是相对于内存来说的.
输入就是把某个地方(比如磁盘)的数据读到内存中
输出就是把数据从内存中写到某个地方(比如磁盘)。
IO流主要是用来处理设备之间的数据传输。
java中流的体系结构如下,由于实现类实在太多,标记颜色的为常用的类。
2.1字节流
以字节为单位读写数据
InputStream(抽象父类) :字节输入流 (子类FileInputStream) 读取文件中的数据
Outputstream(抽象父类):字节输出流 (子类FileOutputStream) 往文件中写入数据
构造方法:
new FileInputStream(String path)//文件的路径
new FileInputStream(File file) // file文件对象
注意:读写文件完成之后记得关闭流(与文件进行数据交互的一个通道)
代码实现:
public static void main(String[] args) throws IOException {
String path="file/user.txt";
readFile(path);
/*String str="我";
byte[] bs=str.getBytes();
for(int i=0;i<bs.length;i++) {
System.out.println(bs[i]);
}
String str1=new String(bs);
System.out.println(str1);
System.out.println(bs.length);*/
writeFile(path,"字节流以字节为单位进行读写\r\n");
copyFile("file/5.jpg", "//192.168.7.141/40第二周周考机试题/images/张三.jpg");
}
/**
* @description 读取指定文件中的数据
* @param path 文件的路径
* @throws IOException
* */
public static void readFile(String path) throws IOException {
InputStream ins=new FileInputStream(path);
//1.定义字节流的缓冲区
byte[] bs=new byte[1024];
StringBuilder sb=new StringBuilder();
int temp=-1;
//2.ins.read(bs) 将从文件中读取到的字节 存入bs数组中,最终返回实际读到的字节数 当读不到内容返回-1
while((temp=ins.read(bs))!=-1) {//说明读到了数据
//new String(bs,0,temp) 将字节数组bs转成字符串下标范围[0,temp)之间的字节转成字符串
sb.append(new String(bs,0,temp));//只是将实际读到的字节转成字符串
}
System.out.println(sb);
//3.关闭输入流
ins.close();
}
/***
* @description 往文件中写入数据
*
* */
public static void writeFile(String path,String talk) throws IOException{
//0.默认情况下 写入文件以覆盖方式写入
//1.如果设置第二个参数为true 逐后写入数据。。。
OutputStream out=new FileOutputStream(path,true);
//1.将字符串转成字节数组
byte[] bs=talk.getBytes();
//2.将字节数组写到文件中
out.write(bs);
//3.关闭输出流
out.close();
}
/**
* 将一个文件复制粘贴到另一个文件中 (复制粘贴)
* 将path1路径下的文件写入到 path2下面
* path1读取 path2 写入
* "//192.168.7.141/40第二周周考机试题/images/名字.jpg"
* */
public static void copyFile(String path1,String path2)throws IOException {
//1.创建读取path1下面文件的输入流
InputStream ins=new FileInputStream(path1);
//2.创建往path2下面写入数据的输出流。
OutputStream out=new FileOutputStream(path2);
//3.自定义一个字节缓冲区
byte[] bs=new byte[1024];
int temp=-1;
//4.将从path1文件中读到的数据写入到path2文件中
while((temp=ins.read(bs))!=-1) {
//5.将实际读到的数据写入到path2中
out.write(bs,0,temp);
}
//6.关闭输入输出流
ins.close();
out.close();
}
2.2字符流
以字符为单位进行读写文件,一般用于读写文档信息
Writer (抽象类) FileWriter输出流 往文件中写数据的
Reader (抽象类) FileReader输入流 往文件中读取数据的
read(char[] cs) 读取文件中的数据写入到指定文件中
write(cs,0,temp);
write(cs);
write(String str);
public static void main(String[] args) throws IOException {
String path="file/user.txt";
readFile(path);
writeFile(path,"字符流是以字符为单位进行读写数据...\r\n");
copyFile("file/作业.txt","clearFile/作业.txt");
}
//1.使用字符流读取文件
public static void readFile(String path)throws IOException {
//1.得到字符输入流对象 (读取文件)
Reader rd=new FileReader(path);
//2.定义缓冲区
char[] cs=new char[1024];
//3.定义用来记录实际读到的字符数量
int temp=-1;
StringBuilder sb=new StringBuilder();
//4.将读取到的数据保存到字符串中
while((temp=rd.read(cs))!=-1) {
sb.append(new String(cs,0,temp));
}
System.out.println(sb);
rd.close();
}
//2.使用字符流往文件中写入数据
public static void writeFile(String path,String talk)throws IOException {
//1.得到了字符输出流对象(往文件中写数据)
//2.第二个参数如果为 true 代表不覆盖逐后写入数据
//3.第二个参数为false 代表覆盖原先的数据写入
Writer wt=new FileWriter(path,true);
//2.将字符串写入文件
wt.write(talk);
//3.关闭输出流
wt.close();
}
//3.尝试自己完成复制粘贴(使用流 往文件中写入数据的时候,如果文件不存在,自动创建一个,然后将数据写入文件)
//注意:不会创建文件夹,如果文件夹不存在,需要自己创建文件夹
public static void copyFile(String path1,String path2) throws IOException{
File file=new File(path2);
//1.得到上一级的文件对象 是否存在 不存在创建
if(!file.getParentFile().exists()) {
file.getParentFile().mkdir();
}
//1.创建读写文件的字符流对象
Reader ins=new FileReader(path1);
Writer out=new FileWriter(path2);
//2.定义缓冲区(一次读1024个字符)
char[] cs=new char[1024];
int temp=-1;
//3.将读到的数据写入文件
while((temp=ins.read(cs))!=-1) {
out.write(cs,0,temp);
}
//4.关闭流
ins.close();
out.close();
}
2.3字符缓冲流
BufferedReader 输入流
底层是基于字符流输入流Reader实现的
读取数据readLinne() 一行一行读取文件中的数据
BufferedWriter 输出流
底层是基于字符输出流Writer实现的
自带缓冲区(缓冲区默认值是8kb 8192)
write(String str)
public static void main(String[] args) throws IOException {
readFile("file/作业.txt");
writeFile("file/test.txt","hello world(你好我的世界)!!!\r\n");
copyFile("file/test.txt", "clearFile/test.txt");
}
public static void readFile(String path)throws IOException {
//1.创建带缓冲区的字符输入流 缓冲区默认大小为 8192
BufferedReader rd=new BufferedReader(new FileReader(path));
StringBuffer sb=new StringBuffer();
String temp=null;//读到了返回读到的一行数据返回字符串类型 读不到了返回null
//2.一行一行读,将读到的字符串拼接起来。。。
while((temp=rd.readLine())!=null) {
sb.append(temp+"\r\n");
}
System.out.println(sb);
rd.close();
}
public static void writeFile(String path,String talk)throws IOException {
BufferedWriter writer=new BufferedWriter(new FileWriter(path,true));
writer.write(talk);
//writer.flush();//只会将数据写入指定文件
writer.close();//将数据写入文件,并且关闭流
}
public static void copyFile(String path1,String path2)throws IOException{
//1.创建带缓冲器的读写的字符流
BufferedReader ins=new BufferedReader(new FileReader(path1));
BufferedWriter out=new BufferedWriter(new FileWriter(path2,true));
String temp=null;
while((temp=ins.readLine())!=null) {
out.write(temp+"\r\n");
}
ins.close();//关闭的是FileReader对象
out.close();//先将缓冲区的数据写入到文件,然后再关闭的是FileWriter对象
}
2.4转换流
1.将字节流转换成字符流
获取的网络流一般是字节流,使用字符流处理文档信息效率更高,将字节流转字符流
InputStreamReader:将字节输入流转成字符输入流 Reader的子类
OutputStreamWriter:将字节输出流转成字符输出流 Writer的子类
InputStreamReader ins=new InputStreamReader(new FileInputStream("file/test.txt"));
OutputStreamWriter out=new OutputStreamWriter(new FileOutputStream("file/test1.txt"));
字节流转字符流
用法与字符流一样...
2.5序列化流和反序列化流
专门用于读写对象的(new ...)
ObjectInputStream: 对象输入流
readObject();//读取对象至文件
ObjectOutputStream:对象输出流
writeObject();//写入对象至文件
注意:
1.对象不能直接写入文件中,一般需要将对象进行序列化之后才可以将序列化之后的对象写入到文件。
序列化:将对象转成字节,字节写入到文件中(能够实现对象存入磁盘)
2.读取文件中序列化之后的对象,需要进行反序列化(将文件中的序列化之后的字节转成对象的过程)
3.实体类必须序列化之后才能使用对象流进行文件读取(实现 Serializable接口即可完成序列化)
2.5.1 对象序列化流
Person类
只能将支持 java.io.Serializable 接口的对象写入流中
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
Test类
public class Test {
public static void main(String[] args) throws IOException,
ClassNotFoundException {
/*
* 将一个对象存储到持久化(硬盘)的设备上。
*/
writeObj();//对象的序列化。
}
public static void writeObj() throws IOException {
//1,明确存储对象的文件。
FileOutputStream fos = new FileOutputStream("obj.object");
//2,给操作文件对象加入写入对象功能。
ObjectOutputStream oos = new ObjectOutputStream(fos);
//3,调用了写入对象的方法。
oos.writeObject(new Person("wangcai",20));
//关闭资源。
oos.close();
}
}
2.5.2对象反序列化流
ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。支持 java.io.Serializable接口的对象才能从流读取。
public class Test02 {
public static void main(String[] args) throws IOException,
ClassNotFoundException {
readObj();//对象的反序列化。
}
public static void readObj() throws IOException,
ClassNotFoundException {
//1,定义流对象关联存储了对象文件。
FileInputStream fis = new FileInputStream("obj.object");
//2,建立用于读取对象的功能对象。
ObjectInputStream ois = new ObjectInputStream(fis);
Person obj = (Person)ois.readObject();
System.out.println(obj.toString());
}
}
2.5.3序列化接口
1.当一个对象要能被序列化,这个对象所属的类必须实现Serializable接口。否则会发生异常NotSerializableException异常。
2.同时当反序列化对象时,如果对象所属的class文件在序列化之后进行的修改,那么进行反序列化也会发生异常InvalidClassException。发生这个异常的原因如下:
- 该类的序列版本号与从流中读取的类描述符的版本号不匹配
- 该类包含未知数据类型
- 该类没有可访问的无参数构造方法
3.Serializable标记接口。该接口给需要序列化的类,提供了一个序列版本号。serialVersionUID. 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
public class Person implements Serializable {
//给类显示声明一个序列版本号。
private static final long serialVersionUID = 1L;
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
2.5.4瞬态关键字transient
当一个类的对象需要被序列化时,某些属性不需要被序列化,这时不需要序列化的属性可以使用关键字transient修饰。只要被transient修饰了,序列化时这个属性就不会被序列化了。
同时静态修饰也不会被序列化,因为序列化是把对象数据进行持久化存储,而静态的属于类加载时的数据,不会被序列化。
public class Person implements Serializable {
/*
* 给类显示声明一个序列版本号。
*/
private static final long serialVersionUID = 1L;
private static String name;
private transient/*瞬态*/ int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}