5.Java中的IO流、File类和序列化
一.File类
1.概述:
- java.io.File :是文件和目录路径类,用于对文件和目录的创建、查找和删除等操作
2.File类的四个静态成员变量:
- public static final char separatorChar :文件名称分隔符的字符表示
- public static final String separator :文件名称分隔符的字符串表示
- public static final char pathSeparatorChar :路径分隔符的字符表示
- public static final String pathSeparator :路径分隔符的字符串表示
1 public class Main { 2 3 public static void main(String[] args) { 4 5 String pathSeparator = File.pathSeparator; //路径分隔符:windows中是分号,Linux中是冒号 6 System.out.println(pathSeparator); 7 8 String separator = File.separator; //文件名称分隔符:windows中是反斜杠,Linux中是正斜杠 9 System.out.println(separator); 10 } 11 }
注意:操作路径不能写死(不同操作系统符号不同)
一般写法:“C:"+File.separator+"xxx"+File.separator+"xxx.xx"
补充知识:相对路径和绝对路径
-
绝对路径:是一个完整地路径,以盘符开始的路径。例如:D:\\Project\\Java\\Demo\\test.txt
-
相对路径:是一个简化的路径,指的是相对于当前项目的根目录的路径,省略了项目的根目录名称。例如文件的根目录为D:\\Project\\Java\\Demo,则简写test.txt
-
注意:
-
路径是不区分大小写的
-
路径中的文件名称分隔符windows使用反斜杠,反斜杠是转义字符,两个反斜杠代表一个普通的反斜杠。
-
3.File类的四个构造方法
- public File(String pathname) :输入路径字符串名称,创建一个新的File实例
- public File(String parent, String child) :输入父路径和子路径字符串名称,创建一个新的File实例
- public File(File parent, String child) :输入父路径为文件的对象和子路径字符串名称,创建一个新的File实例,优点:可以操作父路径文件对象
- public File(URI uri) :输入一个URI来创建一个新的File实例
1 public class demo02 { 2 3 public static void main(String[] args) { 4 File file1 = new File("C:\\Demo"); 5 System.out.println(file1); //C:\Demo 6 File file2 = new File("C:\\Demo\\", "test.txt"); 7 System.out.println(file2); //C:\Demo\test.txt 8 File file3 = new File(file2, "text.txt"); 9 System.out.println(file3); //C:\Demo\test.txt\text.txt 10 } 11 }
4.File类的普通方法
-
获取功能的方法:
- public String getAbsolutePath() :返回此File的绝对路径字符串,输出连同盘符的路径名。
- public String getPath() :将此File转换为路径名字符串,创建时对象是什么名称,输出就是什么名称。(File的toString方法内部调用此方法输出)
- public String getName() :返回由此File表示的文件或目录的名称。获取路径结尾部分的文件或文件夹名称。
- public long length() :返回由此File表示的文件的长度。也就是返回文件或文件夹大小,以字节为单位,当文件和文件夹不存在时返回0。
1 public class demo03 { 2 public static void main(String[] args) { 3 4 File file1 = new File("D:\\Demo\\Java\\test.txt"); 5 System.out.println(file1.getAbsolutePath()); //D:\Demo\Java\test.txt 6 System.out.println(file1.getPath()); //D:\Demo\Java\test.txt 7 System.out.println(file1.getName()); //test.txt 8 System.out.println(file1.length()); //0 9 10 File file2 = new File("aaa.txt"); 11 System.out.println(file2.getAbsolutePath()); //F:\Java\Test\aaa.txt 12 System.out.println(file2.getPath()); //aaa.txt 13 System.out.println(file2.getName()); //aaa.text 14 System.out.println(file2.length()); //797854 15 } 16 }
-
判断功能的方法:
- public boolean exists() :判断文件或目录是否存在
- public boolean isDirectory() :判断是否为一个目录
- public boolean isFile() :判断是否为一个文件
1 public class demo04 { 2 public static void main(String[] args) { 3 4 File file1 = new File("src"); 5 System.out.println(file1.exists()); //true 6 System.out.println(file1.isDirectory()); //true 7 System.out.println(file1.isFile()); //false 8 } 9 }
-
创建和删除功能的方法:
-
public boolean createNewFile() :当文件名称不存在时创建一个新的文件。
- public boolean delete() :删除文件或目录(只能删除一个单级文件夹,删除成功返回true,当删除的文件夹中有文件则不删除返回false)
- public boolean mkdir() :创建一个目录,只能创建单级文件夹。(文件夹存在或创建路径错误则返回false)
- public boolean mkdirs() :创建一个文件目录,能够创建多级文件夹
-
1 public class demo04 { 2 public static void main(String[] args) throws IOException { 3 4 File file1 = new File("testFile.txt"); 5 System.out.println(file1.createNewFile()); //true 6 System.out.println(file1.delete()); //true 7 8 file1 = new File("testDir"); 9 System.out.println(file1.mkdir()); //true 10 System.out.println(file1.delete()); //true 11 12 file1 = new File("testDir\\testDir1\\testDir2\\testDir3"); 13 System.out.println(file1.mkdirs()); //true 14 System.out.println(file1.delete()); //true 15 } 16 }
5.目录的遍历
- public String[] list() :返回String数组,表示目录中的所有子文件和目录(不嵌套内部目录中的文件或目录)
- public File[] listFiles() :返回File数组,表示目录中的所有子文件和目录(不嵌套内部目录中的文件或目录)
- 注意:
- 根据构造函数给出的路径遍历,当目录的路径不存在,会抛出空指针异常
- 当路径不是目录时,抛出空指针异常
1 public class demo04 { 2 public static void main(String[] args) throws IOException { 3 4 File file1 = new File("F:\\java_code"); 5 6 for (String s : file1.list()) { 7 System.out.println(s); //common_java testDir 8 } 9 10 for (File f : file1.listFiles()) { 11 System.out.println(f); //F:\java_code\common_java F:\java_code\testDir 12 } 13 } 14 }
6.案例:递归打印多级文件目录
1 public class demo04 { 2 3 public static void getAllFile(File file) { 4 if (file.isDirectory()) { 5 for (File f : file.listFiles()) { 6 if (f.isDirectory()) { 7 getAllFile(f); 8 } else { 9 System.out.println(f); 10 } 11 } 12 } 13 else { 14 System.out.println(file); 15 } 16 } 17 18 public static void main(String[] args) throws IOException { 19 20 File file1 = new File("F:\\java_code"); 21 getAllFile(file1); 22 23 } 24 }
7.文件过滤器实现过滤文件
- File类中有两个与listFiles重载的方法,参数为过滤器:
- public File[] listFiles(FilenameFilter filter) :
- public File[] listFiles(FileFilter filter :
- java.io.FileFilter 接口:用于File对象的过滤器
- 唯一的抽象方法: boolean accept(File pathname) ,参数为File对象
- java.io.FilenameFilter 接口:用于过滤文件名称
- 唯一的抽象方法:boolean accept(File dir, String name) ,参数为需要遍历的目录,获取到的遍历目录中每一个文件或文件夹的名称
- 两个过滤器没有实现类,需要我们自己实现过滤器的accept方法,来实现自定义的规则。
实现将目录中递归搜索所有以.class结尾的文件,使用两种过滤器
1 public class demo04 { 2 3 // 1.使用FileFilter来递归获得文件后缀名为.class的文件 4 public static void getAllFile1(File file) { 5 //当file目录中有文件夹或以.class结尾的文件则放入文件数组中保存 6 File[] files = file.listFiles(pathname -> pathname.isDirectory() || pathname.getName().toLowerCase().endsWith(".class")); 7 8 for (File f : files) { 9 if (f.isDirectory()) { 10 getAllFile1(f); 11 } else { 12 System.out.println(f); 13 } 14 } 15 } 16 17 // 2.使用FilenameFilter来递归获得文件后缀名为.class的文件 18 public static void getAllFile2(File file) { 19 //当file目录中有文件夹或以.class结尾的文件则放入文件数组中保存 20 File[] files = file.listFiles((dir,name) -> new File(dir,name).isDirectory() || name.toLowerCase().endsWith(".class")); 21 22 for (File f : files) { 23 if (f.isDirectory()) { 24 getAllFile1(f); 25 } else { 26 System.out.println(f); 27 } 28 } 29 } 30 31 public static void main(String[] args) throws IOException { 32 33 File file1 = new File("F:\\java_code"); 34 getAllFile1(file1); 35 System.out.println("================================"); 36 getAllFile2(file1); 37 38 } 39 }
二.IO流
1.IO的分类
根据数据的流向分为:输入流和输出流
- 输入流:把数据从其他设备上读取到内存中的流
- 输出流:把数据从内存中写到其他设备上的流
数据的类型分为:字节流和字符流
流:数据(字符或字节) 1个字符 = 2个字节 ,1个字节= 8个二进制位,中文占3个字节
输入流 | 输出流 | |
字节流 | 字节输入流InputStream | 字节输出流OutputStream |
字符流 | 字符输入流Reader | 字符输出流Writer |
2.字节流:读取中文会有乱码,请使用字符流
【1】一切皆为字节
一切文件数据都是以二进制数字的形式保存。字节流可以传输任意文件数据,我们时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据。
【2】字节输出流[OutputStream]
-
FileOutputStream
public void test1() throws IOException{ FileOutputStream fos = new FileOutputStream("src/code/a.txt"); //1.写一个字节到文件中 fos.write(97); //将十进制转换为二进制数据,97 = 'a' //2.写多个字节 //当写入多个字节时,第一个字节是正数(0~127之内),那么会查询ASCII表 fos.write(new byte[]{65,66,67,68,69}); //ABCDE //当写入多个字节时,第一个字节为负数,则第一个字节和第二个字节会组成一个中文显示,查询系统默认码表(GBK) fos.write(new byte[]{65,-66,-67,68,69}); //A窘DE //写字节数组的部分 byte[] bytes = {65,66,67,68,69}; fos.write(bytes,1,2); //将字符串变成字节写入 fos.write("你好!".getBytes()); fos.close(); }
public static void test2() throws IOException{ //追加写并换行 FileOutputStream fos = new FileOutputStream("src/code/a.txt",true); fos.write("你好!".getBytes()); fos.write("\r\n".getBytes()); //windows:\r\n linux:/n mac:/r fos.write("你好!".getBytes()); fos.close(); }
【3】InputStream
-
FileInputStream
public static void test3() throws IOException { FileInputStream fis = new FileInputStream("src/code/a.txt"); //1.读取一个字节 int i = fis.read(); System.out.println(i); //读到'a'会变为int类型为97,读到末尾返回-1 //2.循环读取到文件结尾,一次读取一个字节 int len1 = -1; while((len1 = fis.read()) != -1){ System.out.println((char) len1); } //3.一次读取多个字节 byte[] bytes = new byte[10]; int len2 = fis.read(bytes); //将读取到的放在bytes,将读取到的长度返回 System.out.println(len2); //读取到的有效字节个数 //返回数据(包含无效空格和前一次读取的脏数据 //【例如之前赋值了byte[6],这次有效数据只有3个,那么byte[6]的数据就是脏数据】) System.out.println(new String(bytes)); //循环读取有效数据 int len3 = -1; while ((len3 = fis.read(bytes)) != -1) { System.out.println(new String(bytes, 0, len3)); //将有效数据转化为String } fis.close(); }
【4】字节流练习:图片复制
public static void test4() throws IOException { long start = System.currentTimeMillis(); FileInputStream fis = new FileInputStream("src/code/1.png"); FileOutputStream fos = new FileOutputStream("src/code/图片.png"); byte[] bytes = new byte[1024]; int len = -1; while ((len = fis.read(bytes)) != -1) { fos.write(bytes, 0, len); } fis.close(); fos.close(); long end = System.currentTimeMillis(); System.out.println("消耗时间:" + (end - start) + "ms"); }
3.字符流
-
使用字节流读取中文字符时会产生错误,所以我们以字符为单位,专门处理文本文件
【1】Reader
-
FileReader
public static void test5() throws IOException { FileReader fr = new FileReader("src/code/a.txt"); //1.一个字符读取 int len; while((len = fr.read()) != -1){ System.out.println((char) len); } //2.读取多个字符 char[] chars = new char[1024]; while ((len = fr.read(chars))!=-1){ System.out.println(new String(chars,0,len)); } fr.close(); }
【2】Writer
-
FileWriter
public static void test6() throws IOException { FileWriter fw = new FileWriter("src/code/b.txt"); //1.写单个字符 fw.write(97); fw.flush(); //将内存缓冲区的数据刷新到文件中 fw.close(); //也拥有flush功能(可以省略fw.flush())但会关闭流就不能使用流了 }
public static void test7() throws IOException { FileWriter fw = new FileWriter("src/code/b.txt"); //1.写整个字符数组 char[] chars = {'a','b','c','d'}; fw.write(chars); //2.写部分字符数组 fw.write(chars,1,3); //bcd //3.写一个字符串 fw.write("你好!"); fw.close(); }
public static void test8() throws IOException { //追加写 FileWriter fw = new FileWriter("src/code/b.txt",true); //换行 fw.write("你好!\r\n"); fw.write("Hello World!"); fw.close(); }
流的异常处理:
public static void test9() { FileWriter fw = null; try { fw = new FileWriter("src/code/b.txt", true); fw.write("Hello World!"); } catch (IOException e) { System.out.println(e); } finally { //判断对象是否创建成功 if (fw != null) { try { fw.close(); } catch (IOException e) { System.out.println(e); } } } }
JDK7的版本:由于实现了AutoCloseable接口的close方法
public static void test10() { try (FileWriter fw = new FileWriter("src/code/b.txt", true)) { fw.write("Hello World!"); } catch (IOException e) { System.out.println(e); } }
4.Properties
【1】基本使用
public static void test11() { Properties prop = new Properties(); prop.setProperty("id=1","小明"); prop.setProperty("id=2","小洪"); prop.setProperty("id=3","小花"); Set<String> set = prop.stringPropertyNames(); for (String key : set) { System.out.println(key + ":" + prop.getProperty(key)); } }
【2】store方法
-
字符流可以写中文
public static void test12() throws IOException { Properties prop = new Properties(); prop.setProperty("id=1","小明"); prop.setProperty("id=2","小洪"); prop.setProperty("id=3","小花"); FileWriter fw = new FileWriter("src/code/b.txt"); prop.store(fw,"save data"); fw.close(); }
-
字节流写中文乱码
public static void test13() throws IOException { Properties prop = new Properties(); prop.setProperty("id=1","小明"); prop.setProperty("id=2","小洪"); prop.setProperty("id=3","小花"); prop.store(new FileOutputStream("src/code/b.txt"),""); }
【3】load方法
需要用字符流读取不要用字节流读取
public static void test14() throws IOException { Properties prop = new Properties(); prop.load(new FileReader("src/code/b.txt")); Set<String> set = prop.stringPropertyNames(); for (String key : set) { System.out.println(key + ":" + prop.getProperty(key)); } }
5.缓冲流
-
目的是提高读写速度
【1】BufferedOutputStream
-
关闭缓冲流会自动把输入输出流都关闭了,所以使用到的输入输出流不需要再次关闭了
public static void test15() throws IOException { BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src/code/c.txt")); bos.write("小王".getBytes()); bos.flush(); bos.close(); //释放资源之前会先调用flush方法,再释放 }
【2】BufferedInputStream
public static void test16() throws IOException { BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src/code/c.txt")); //每次读一个 // int len = 0; // while ((len = bis.read()) != -1) { // System.out.println(len); // } // 读取一批 byte[] bytes = new byte[1024]; int len = 0; while ((len = bis.read(bytes)) != -1) { System.out.println(new String(bytes, 0, len)); } bis.close(); //释放资源之前会先调用flush方法,再释放 }
【3】读写图片
public static void test17() throws IOException { long start = System.currentTimeMillis(); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src/code/3.png")); BufferedInputStream bis = new BufferedInputStream(new FileInputStream("src/code/1.png")); // 读取一批 byte[] bytes = new byte[1024]; int len = 0; while ((len = bis.read(bytes)) != -1) { bos.write(bytes, 0, len); } bis.close(); bos.close(); long end = System.currentTimeMillis(); System.out.println("消耗时间:" + (end - start) + "ms"); }
【4】BufferedWrite
public static void test18() throws IOException { BufferedWriter bw = new BufferedWriter(new FileWriter("src/code/c.txt")); bw.write("你好"); bw.newLine(); bw.write("Hello World"); bw.close(); }
【5】BufferedReader
public static void test19() throws IOException { BufferedReader br = new BufferedReader(new FileReader("src/code/c.txt")); //1.读一行,返回数据不包含换行符,读到末尾返回null // String line = br.readLine(); // System.out.println(line); //2.循环读取 String line; while ((line = br.readLine()) != null) { System.out.println(line); } br.close(); }
6.转换流
-
指定编码表,来解决编码格式不同而产生的乱码问题
【1】OutputStreamWriter
public static void test20() throws IOException{ OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("src/code/c.txt"),"utf-8"); osw.write("明明"); osw.flush(); osw.close(); }
【2】InputStreamReader
public static void test21() throws IOException{ InputStreamReader isw = new InputStreamReader(new FileInputStream("src/code/c.txt"),"utf-8"); int len = 0; while((len = isw.read()) != -1){ System.out.println((char) len); } isw.close(); }
三.序列化
import java.io.Serializable; public class Person implements Serializable { private String name; private int age; public Person() { } public Person(String name, int age) { 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 + '}'; } }
1.ObjectOutputStream
public static void test22() throws IOException{ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/code/c.txt")); oos.writeObject(new Person("小王",25)); oos.close(); }
2.ObjectInputStream
public static void test23() throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src/code/c.txt")); Object o = ois.readObject(); ois.close(); System.out.println(o); }
3.transient关键字
-
加上transient关键字的属性不能被序列化
-
static关键字同样的属性不能被序列化
4.反序列化失败的案例
-
序列号不同导致
-
手动增加一个序列号,用来防止属性修改而导致的反序列化失败
private static final long serialVersionUID = 1L;
四.打印流
1.PrintStream
public static void test24() throws FileNotFoundException { PrintStream ps = new PrintStream("src/code/c.txt"); ps.write(97); //使用write会将97会变成 ’a' ps.println(97); //使用println会原样输出并换行 ps.println(8.8); ps.println('c'); ps.println(true); ps.println("Hello World"); ps.close(); }
2.改变输出语句的目的地
-
将在控制台输出的语句输出到文本中
public static void test25() throws FileNotFoundException { System.out.println("控制台输出############"); PrintStream ps = new PrintStream("src/code/c.txt"); System.setOut(ps); //修改输出的目的地 System.out.println("修改了输出的目的地到文本中"); ps.close(); }