本文基于马士兵JAVA教学视频https://www.bilibili.com/video/BV1Ff4y147WP?p=257&spm_id_from=pageDriver学习撰写
目录
一、File类的引入
二、File对文件的操作
二、File对目录的操作
三、I/O流的引入
四、FileReader读取文件内容
五、利用try-catch-finally处理异常
六、FileInputStream读取文件中的内容
八、缓冲字节流处理器
九、转换流-InputStreamReader,OutputStreamWriter
十、练习:键盘录入内容输出到文本文件
十一、数据流-DataInputStream,DataOutputStream
十二、对象流-序列化和反序列化
在java中操纵文件/目录?怎么办?
JAVA中最典型的特点就是面向对象,JAVA最擅长的也是操作对象,所以我们要把磁盘上的文件和文件夹封装成对象,对象属于File类的对象,有了这个对象,我们的程序就能直接操作文件了,还能对文件进行创建和删除。
public static void main(String[] args) throws IOException { //将文件封装为一个File类的对象 File f = new File("src\\main\\resources\\test.txt"); //跨平台建议使用File.separator,获取拼接符 File f2 = new File("C:" + File.separator + "webapp\\test\\src\\main\\resources\\test.txt"); //常用方法 System.out.println("文件是否可读" + f.canRead()); System.out.println("文件是否可写" + f.canWrite()); System.out.println("文件名字" + f.getName()); System.out.println("文件上级目录" + f.getParent()); System.out.println("是否是一个目录" + f.isFile()); System.out.println("是否是一个文件" + f.isDirectory()); System.out.println("是否隐藏" + f.isHidden()); System.out.println("文件大小" + f.length()); System.out.println("是否存在 " + f.exists()); if (f.exists()) { // f.delete(); } else { f.createNewFile(); } System.out.println(f == f2); System.out.println(f.equals(f2)); //比较文件的路径 }
public static void main(String[] args) { File f = new File("C:\\webapp\\test\\src\\main\\resources"); System.out.println("文件是否可读" + f.canRead()); System.out.println("文件是否可写" + f.canWrite()); System.out.println("文件名字" + f.getName()); System.out.println("文件上级目录" + f.getParent()); System.out.println("是否是一个目录" + f.isFile()); System.out.println("是否是一个文件" + f.isDirectory()); System.out.println("是否隐藏" + f.isHidden()); System.out.println("文件大小" + f.length()); System.out.println("是否存在 " + f.exists()); //针对目录的方法 File f2 = new File("C:\\webapp\\test\\src\\main\\resources\\a\\b\\c"); //创建目录 //f2.mkdirs(); //删除,删除目录只会删一层,而且要保证没东西 //f2.delete(); //查询, String[] list = f.list(); //文件夹下目录/文件对应名字的数组 for(String s:list){ System.out.println(s); } File[] files = f.listFiles();//作用更加广泛 for(File file:files){ System.out.println(file.getName()+","+file.getAbsolutePath()); } }
I/O用于处理设备直接数据的传输
形象的理解:I/O流当作一根“管子”:
I/O流体系结构(橙色是重点):
案例:通过JAVA完成文件的复制
功能分解1:文件-》程序:FileReader
一个字符一个字符的将文件中的内容读取到程序中了:
public static void main(String[] args) throws IOException { //文件-》程序: //1.有一个文件 --》创建一个File类 File f = new File("src\\main\\resources\\test.txt"); //内容:abc老师 //2.利用FileReader流,这个“管子”怼到源文件上面去 --》》创建一个FileReader的流对象 FileReader fr = new FileReader(f); //3.进行操作“吸”的动作 --》读取文件 /*下面的代码验证了,如果到了文件的结尾处,读取的内容为-1 int n1 = fr.read(); int n2 = fr.read(); int n3 = fr.read(); int n4 = fr.read(); int n5 = fr.read(); int n6 = fr.read(); System.out.println(n1); System.out.println(n2); System.out.println(n3); System.out.println(n4); System.out.println(n5); System.out.println(n6);*/ //方式1: /*int n = fr.read(); while (n!=-1){ System.out.println(n); n = fr.read(); }*/ //方式2: int n; while ((n=fr.read())!=-1){ System.out.print((char) n); } //4.“管”不用了,就要关闭--》》关闭流 //流,数据库,网络资源,靠JVM本身无法帮我们关闭,此时必须靠程序员手动关闭 fr.close(); }
想一次读五个字符,不够的话下次再读五个字符
public static void main(String[] args) throws IOException { //文件-》程序: //1.有一个文件 --》创建一个File类 File f = new File("src\\main\\resources\\test.txt"); //内容:abc老师 //2.利用FileReader流,这个“管子”怼到源文件上面去 --》》创建一个FileReader的流对象 FileReader fr = new FileReader(f); //3.进行操作“吸”的动作 --》读取文件 //引入一个"快递员的小车",这个“小车”一次拉5个快递 char[] ch = new char[5]; //缓冲数组 int len = fr.read(ch);//一次读5个:返回值是数组中的有效长度 while (len!=-1){ //System.out.println(len); /* 方式1: for(int i=0;i<len;i++) System.out.print(ch[i]);*/ //方式2: String str = new String(ch,0,len); System.out.print(str); len = fr.read(ch); } //4.“管”不用了,就要关闭--》》关闭流 fr.close(); }
功能分解2:程序-》文件:FileReader
一个字符一个字符的向外输出:
public static void main(String[] args) throws IOException { //1.有一个目标文件 File f = new File("src\\main\\resources\\demo.txt"); //2.利用FileWriter流,这个“管子”怼到源文件上面去 FileWriter fw = new FileWriter(f); //3.开始动作:输出动作 //一个字符一个字符往外输出 String str = "hello你好"; for(int i=0;i<str.length();i++){ fw.write(str.charAt(i)); } //4.关闭流 fw.close(); }
发现:如果目标文件不存在的话,那么会自动创建此文件。
如果目标文件存在,new FileWriter(f)相当于对源文件进行覆盖操作与new FileWriter(f,false)相同,而new FileWriter(f,true)表示追加在后面,而不是覆盖。
利用缓冲数组向外输出:
public static void main(String[] args) throws IOException { //1.有一个目标文件 File f = new File("src\\main\\resources\\demo.txt"); //2.利用FileWriter流,这个“管子”怼到源文件上面去 FileWriter fw = new FileWriter(f,true); //3.开始动作:输出动作 //一个字符一个字符往外输出 String str = ",你好中国"; char[] chars = str.toCharArray(); fw.write(chars); //4.关闭流 fw.close(); }
功能分解3:利用FileReader,FileWriter文件复制
public static void main(String[] args) throws IOException { //1.有一个源文件 File f1 = new File("src\\main\\resources\\test.txt"); //2.有一个目标文件 File f2 = new File("src\\main\\resources\\demo.txt"); //3.搞一个输入的管,怼到源文件上: FileReader fr = new FileReader(f1); //4.搞一个输出的管,怼到目标文件上去 FileWriter fw = new FileWriter(f2); //5.开始动作 //方式1:一个字符一个字符的复制: /*int n = fr.read(); while (n!=-1){ fw.write(n); n = fr.read(); }*/ //方式2:利用缓冲字符数组: /* char[] ch = new char[5]; int len = fr.read(ch); while (len!=-1){ fw.write(ch,0,len); len = fr.read(ch); }*/ //方式3:将缓冲数组转为字符串写出 char[] ch = new char[5]; int len = fr.read(ch); while (len!=-1){ String s = new String(ch,0,len); fw.write(s); len = fr.read(ch); } //4.关闭流(关闭流的时候遵循倒着关闭,后用先关) fw.close(); fr.close(); }
!!!警告,不要用字符流去操作非文本文件
文本文件:.txt .java .c .cpp ---》建议使用字符流操作
非文本文件:.jpg .mp3 .mp4 .doc .ppt ---》建议使用字节流操作
public static void main(String[] args) { //1.有一个源文件 File f1 = new File("src\\main\\resources\\test.txt"); //2.有一个目标文件 File f2 = new File("src\\main\\resources\\demo.txt"); //3.管怼到源文件上: FileReader fr = null; FileWriter fw = null; try { fr = new FileReader(f1); fw = new FileWriter(f2); //4.开始动作 char[] ch = new char[5]; int len = fr.read(ch); while (len!=-1){ String s = new String(ch,0,len); fw.write(s); len = fr.read(ch); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { //5.关闭流 try { if(fw!=null) //防止空指针异常 fw.close(); } catch (IOException e) { e.printStackTrace(); } try { if(fr!=null) fr.close(); } catch (IOException e) { e.printStackTrace(); } } }
1、读取文本文件:
public static void main(String[] args) throws IOException { //1.有一个源文件 File f = new File("src\\main\\resources\\test.txt"); //2.将一个字节流管怼到源文件上 FileInputStream fis = new FileInputStream(f); //3.开始读文件 /* * 细节1: * 文件是utf-8进行存储的,所以英文字符底层实际占用1个字节 * 但是中文字符,底层实际占用3个字节。 * 细节2: * 如果文件是文本文件,那么就不要使用字节流读取了,建议使用字符流 * * 细节3:read()读取一个字节,但是你有没有发现返回值是Int类型,而不是byte类型? * read方法底层做了处理,让返回的数据都是正数 * 就是魏立避免如果字节返回的是-1的话,那到底是读入的字节还是文件结尾呢? * */ int n = fis.read(); while (n!=-1){ System.out.println(n); n = fis.read(); } //4.关闭流 fis.close(); }
2、利用字节流读取非文本文件:(以图片为案例:) --》一个字节一个字节的读取,效率低
public static void main(String[] args) throws IOException { //1.有一个源文件 File f = new File("src\\main\\resources\\Red dead.png"); //2.将一个字节流管怼到源文件上 FileInputStream fis = new FileInputStream(f); //3.开始读文件 int count = 0; //计数器,计读入的字节个数 int n = fis.read(); while (n!=-1){ count++; n = fis.read(); } System.out.println(count); //4.关闭流 fis.close(); }
3、利用字节类型的缓冲数组:
public static void main(String[] args) throws IOException { //1.有一个源文件 File f = new File("src\\main\\resources\\Red dead.png"); //2.将一个字节流管怼到源文件上 FileInputStream fis = new FileInputStream(f); //3.开始读文件 //利用缓冲数组:(快递员的小车) int count = 0; byte[] b = new byte[1024*6]; int len = fis.read(b); //len指读取的有效长度 while (len!=-1){ // System.out.println(len); count += len; len = fis.read(b); } System.out.println(count); //4.关闭流 fis.close(); }
七、FileInputStream,FileOutput完成非文本复制
1.读入一个字节写出一个字节
public static void main(String[] args) throws IOException { //1.有一个源文件 File f1 = new File("src\\main\\resources\\Red dead.png"); //2.有一个目标文件 File f2 = new File("src\\main\\resources\\Red dead1.png"); //3.将一个字节流管怼到源文件上 FileInputStream fis = new FileInputStream(f1); //4.有一个输出管道,怼到木匾文件上 FileOutputStream fos = new FileOutputStream(f2); //5.开始复制:(边读边写) int n = -1; while ((n=fis.read())!=-1){ fos.write(n); } //4.关闭流 fos.close(); fis.close(); }
耗时:2147ms
2.利用缓冲数组(快很多)
public static void main(String[] args) throws IOException { //1.有一个源文件 File f1 = new File("src\\main\\resources\\Red dead.png"); //2.有一个目标文件 File f2 = new File("src\\main\\resources\\Red dead1.png"); //3.将一个字节流管怼到源文件上 FileInputStream fis = new FileInputStream(f1); //4.有一个输出管道,怼到木匾文件上 FileOutputStream fos = new FileOutputStream(f2); //5.开始复制:(边读边写) //利用缓冲数组 byte[] b = new byte[1024*8]; int len = fis.read(b); while (len!=-1){ fos.write(b,0,len); len = fis.read(b); } //4.关闭流 fos.close(); fis.close(); }
耗时:2ms
想要完成上面的效果,单纯的靠FileInputStream,FileOutputStream是不可以完成的,这个时候就需要功能的加强,这个加强就需要引入新的流(在FileInputStream,FileOutputStream外面再套一层流):BufferedInputStream,BufferedOutputStream,------>处理流
public static void main(String[] args) throws IOException { //1.有一个源文件 File f1 = new File("src\\main\\resources\\Red dead.png"); //2.有一个目标文件 File f2 = new File("src\\main\\resources\\Red dead1.png"); //3.将一个字节流管怼到源文件上 FileInputStream fis = new FileInputStream(f1); //4.有一个输出管道,怼到木匾文件上 FileOutputStream fos = new FileOutputStream(f2); //5.功能加强,在FileInputStream外面套一个管:BufferedInputStream; BufferedInputStream bis = new BufferedInputStream(fis); //6.功能加强,在FileOutputStream外面再套一个管:BufferedOutputStream; BufferedOutputStream bos = new BufferedOutputStream(fos); //7,开始动作 byte[] b = new byte[1024*6]; int len = bis.read(b); while (len!=-1){ bos.write(b,0,len); len = bis.read(b); } //8.关闭流 //如果处理流包裹着节点流的话,那么只要关闭高级流,里面的字节流也会随之被关闭 bos.close(); bis.close(); }
耗时:1ms
作用:将字节流和字符流进行转换。
属于字节流还是字符流?属于字符流
InputStreamReader:将字节的输入流--》字符的输入流
OutputStreamWriter:将字符的输出流--》字节的输出流
1.将输入的字节流转换为输入的字符流,然后完成文件--》程序:
public static void main(String[] args) throws IOException { //1.有一个源文件 File f = new File("src\\main\\resources\\test.txt"); //2.需要一个输入的字节流接触文件: FileInputStream fis = new FileInputStream(f); //3.加入一个转化流,将字节流转换为字符流:(转换流属于一个处理流) //将字节转换为字符的时候,需要指定一个编码,这个编码跟文件本身的编码格式统一 //如果编码格式不统一的话,那么控制台上展示的效果就会出现乱码 //InputStreamReader isr = new InputStreamReader(fis,"utf-8"); //获取程序本身的编码--》utf-8 InputStreamReader isr = new InputStreamReader(fis); //4.开始动作,将文件中内容显示在控制台: char[] ch = new char[20]; int len = isr.read(ch); while (len!=-1){ System.out.print(new String(ch,0,len)); len = isr.read(ch); } //关闭流 isr.close(); }
2.转换流,实现文本文件的复制
public static void main(String[] args) throws IOException { //1.有一个源文件 File f1 = new File("src\\main\\resources\\test.txt"); //2.有一个目标文件 File f2 = new File("src\\main\\resources\\test1.txt"); //3.输入方向 FileInputStream fis = new FileInputStream(f1); InputStreamReader isr = new InputStreamReader(fis,"utf-8"); //4.输出方向 FileOutputStream fos = new FileOutputStream(f2); OutputStreamWriter osw = new OutputStreamWriter(fos,"gbk"); //5.开始动作 char[] ch = new char[20]; int len = isr.read(ch); while (len!=-1){ osw.write(ch,0,len); len = isr.read(ch); } //6.关闭流 osw.close(); isr.close(); }
public static void main(String[] args) throws IOException { //1.准备输入方向,键盘录入 InputStream in = System.in; //字节流-》字符流 InputStreamReader isr = new InputStreamReader(in); //在isr外面套一个缓冲流 BufferedReader br = new BufferedReader(isr); //2.再准备输出方向: //准备目标文件 File f = new File("src\\main\\resources\\test2.txt"); FileWriter fw = new FileWriter(f); BufferedWriter bw = new BufferedWriter(fw); //3.开始动作 String s = br.readLine(); //等待键盘录入,是一个阻塞方法 while (!s.equals("exit")){ bw.write(s); bw.newLine(); s = br.readLine(); } //4.关闭流 bw.close(); br.close(); }
数据流:用来操作基本数据类型和字符串
DataInputStream:将文件中存储的基本数据类型和字符串写入内存变量中
DataOutputStream:将内存中的基本数据类型和字符串的变量写入文件中
代码:利用DataOutputStream向外写出变量
public static void main(String[] args) throws IOException { DataOutputStream dos = new DataOutputStream(new FileOutputStream(new File("src\\main\\resources\\test3.txt"))); dos.writeUTF("你好"); dos.writeBoolean(false); dos.writeDouble(6.9); dos.writeInt(82); dos.close(); }
结果
程序读入
DataInputStream dis = new DataInputStream(new FileInputStream(new File("src\\main\\resources\\test3.txt"))); System.out.println(dis.readUTF()); System.out.println(dis.readBoolean()); System.out.println(dis.readDouble()); System.out.println(dis.readInt()); //关闭流 dis.close();
输出结果:
验证:文件我们看不懂,但是程序看得懂
要求:写出类型跟读入的类型必须要匹配!且读出顺序要一致。
对象流:用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把JAVA中的对象写入到数据源中,也能把对象从数据源中还原回来。
序列化和反序列化:
ObjectOutputStream类:把内存中的JAVA对象转换成平台无关的二进制数据,从而允许把这种二进制数据持久的保存在磁盘上,或通过网络将这种二进制数据传输到另一个网络节点。---》序列化
用ObjectInputStream类:当其他程序获取了这种二进制数据,就可以恢复成原来的Java对象。---》反序列化
代码:操作字符串对象
首先将一个字符串对象写到文件中去
public static void main(String[] args) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("src\\main\\resources\\test4.txt"))); oos.writeObject("你好"); oos.close(); }
查看文件:
我们看不懂文件的内容,但是程序是可以看懂的,所以可以写一个程序读文件中的内容:
//将文件中保存的字符串读入到内存 ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("src\\main\\resources\\test4.txt"))); //读取 String s = (String) ois.readObject(); System.out.println(s); //关闭流 ois.close();
输出结果:
代码:操作自定义类对象
自定义的Person类:
public class Person { public String getName() { return name; } public int getAge() { return age; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } }
测试类:
public static void main(String[] args) throws IOException, ClassNotFoundException { //序列化:将内存中对象--》文件 //有一个对象: Person p = new Person("lili",19); //有对象流: ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("src\\main\\resources\\test5.txt"))); //向外写: oos.writeObject(p); //关闭流 oos.close(); }
运行时异常
出现异常的原因:
想要序列化的对象对应的类必须要实现接口
接口内部什么都没有,这种接口叫标识接口,起标识作用,标识什么呢?只有实习这个接口的类的对象才能够序列化。
Person类修改为(实现接口)
public class Person implements Serializable { public String getName() { return name; } public int getAge() { return age; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } }
测试:发现序列化成功,Person具备了序列化的能力。
这个二进制数据我们看不懂,但是程序可以看懂,所以我们可以用程序实现反序列化操作,将这个对象恢复到内存中。
测试:
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("src\\main\\resources\\test5.txt"))); //读入内存: Person person = (Person) ois.readObject(); System.out.println(person.getName()); //关闭流 ois.close();
打印结果:
证明了反序列化成功,将二进制数据--》内存
serialVersionUID:
凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态常量
:
private static final long serialVersionUID;
serialVersionUID用来表明类的不同版本间的兼容性,简而远之,其目的是以序列化对象进行版本控制,有关各版本反序列化时是否兼容。
如果类没有显示定义这个静态变量,它的值时JAVA运行时环境根据类的内部细节自动生成的,若类的实例变量做了修改,serialVersionUID可能发生变化。故建议,显示声明。
简单来说,JAVA的序列化机制时通过在运行时判断类的serialVersionUID来验证版本的一致性的,在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。
我现在在person类中加入toString方法:
public class Person implements Serializable { public String getName() { return name; } public int getAge() { return age; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
再次运行的结果:Exception in thread "main" java.io.InvalidClassException: Person; local class incompatible: stream classdesc serialVersionUID = -873572521303181536, local class serialVersionUID = -7418736053376735269
序列化时不带toString方法,现在的这个类加了toString,出现不匹配问题。
解决:给类加入序列号
加入序列号后重新生成不带toString方法的版本,再添加toString方法,执行下列代码:
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("src\\main\\resources\\test5.txt"))); //读入内存: Person person = (Person) ois.readObject(); System.out.println(person.toString()); //关闭流 ois.close();
输出:
IDEA中自动配置序列号
勾选图中选定选项。在Person类上,Alt+回车即可生成。
细节:
1.被序列会的类的内部的所有属性,必须是可序列化的(基本数据类型都是可序列化的,但是自定义对象必须实现Serializable接口)
2.static,transient修饰的属性不可以被序列化。