0x00 概述
本文涉及Java知识点有IO流,Properties集合。
0x01 流案例
1.1 集合到文件数据排序改进版
- 需求: 键盘录入5个学生信息,姓名,语文成绩,数学成绩,英语成绩,要求按照成绩总分从高到低写入文本文件
- 格式: 姓名,语文成绩,数学成绩,英语成绩
步骤分析:
1. 创建学生类
2. 创建TreeSet集合,通过比较器排序进行排序
3. 键盘录入学生数据
4. 创建学生对象,把键盘录入的数据对应赋值给学生对象的成员变量
5. 把学生对象添加到TreeSet集合
6. 创建字符缓冲输出流对象
7. 遍历集合,得到每一个学生对象
8. 把学生对象的数据拼接成指定格式的字符串
9. 调用字符缓冲输出流对象的方法写数据
10. 释放资源
示例
package Set2FileDemo; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.util.Comparator; import java.util.Scanner; import java.util.TreeSet; public class Treeset2FileDemo { public static void main(String[] args) throws IOException { // 创建TreeSet集合,通过比较器排序进行排序 TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>() { @Override public int compare(Student s1, Student s2) { // 成绩总分从高到低 int num = s2.getSum() - s1.getSum(); // 次要条件 int num2 = num == 0 ? s1.getChinese() - s2.getChinese() : num; int num3 = num2 == 0 ? s1.getMath() - s2.getMath() : num2; int num4 = num3 == 0 ? s2.getEnglish() - s1.getEnglish() : num3; return num4; } }); // 键盘录入学生数据 for (int i = 0; i < 5; i++) { Scanner sc = new Scanner(System.in); System.out.println("请录入第" + (i+1) + "个学生的信息"); System.out.println("姓名: "); String name = sc.nextLine(); System.out.println("语文成绩: "); int Chinese = sc.nextInt(); System.out.println("数学成绩: "); int Math = sc.nextInt(); System.out.println("英语成绩: "); int English = sc.nextInt(); // 创建学生对象,把键盘录入的数据对应赋值给学生对象的成员变量 Student s = new Student(); s.setName(name); s.setChinese(Chinese); s.setMath(Math); s.setEnglish(English); // 把学生对象添加到TreeSet集合 ts.add(s); } // System.out.println(ts.size()); // 创建字符缓冲流输出流对象 BufferedWriter bw = new BufferedWriter(new FileWriter( "TestFolder/ts.txt")); // 遍历集合,得到每一个学生对象 for (Student s : ts) { // 把学生对象的数据拼接成指定格式的字符串 // 格式: 姓名,语文成绩,数学成绩,英语成绩 StringBuilder sb = new StringBuilder(); sb.append(s.getName()).append(", ").append(s.getChinese()).append(", ").append(s.getMath()). append(", ").append(s.getEnglish()).append(", ").append(s.getSum()); // System.out.println(sb.toString()); // 调用字符缓冲输出流对象的方法写数据 bw.write(sb.toString()); bw.newLine(); bw.flush(); } // 释放资源 TestFolder bw.close(); } }
1.2 复制单级文件夹
- 需求:把"E:\\itcast"这个文件夹复制到模块目录下
分析步骤:
1. 创建数据源目录File对象,路径是E:\itcast
2. 获取数据源目录File对象的名称
3. 创建目的地目录File对象,路径由(模块名+第2步获取的名称)组成
4. 判断第3步创建的File是否存在,如果不存在,就创建
5. 获取数据源目录下所有文件的File数组
6. 遍历File数据,得到每一File对象,该File对象,其实就是数据源文件
7. 获取数据源文件File对象的名称
8. 创建目的地文件File对象,路径由(目的地目录+第七步获取的名称)组成
9. 复制文件,由于不清楚数据源目录下的文件都是什么类型的,所以采用字节流复制文件,采用参数为File的构造方法
示例
package CopyFolderDemo; import java.io.*; public class CopyFolderDemo { public static void main(String[] args) throws IOException { // 创建数据源目录File对象,路径是E:\\itcast File srcFolder = new File("E:\\itcast"); // 获取数据源目录File对象的名称(itcast) String srcFolderName = srcFolder.getName(); // 创建目的地目录File对象,路径名是模块名+itcast组成(myCharStream\\itcast ) File destFolder = new File("myCharStream", srcFolderName); // 判断目的地目录对应的File是否存在,如果不存在,就创建 if (!destFolder.exists()) { destFolder.mkdir(); } // 获取数据源目录下所有文件的File数组 File[] listFiles = srcFolder.listFiles(); // 遍历File数组,得到每一个File对象,该File对象,其实就是数据源文件 for (File srcFile : listFiles) { // 数据源文件 E:\\itcast\\mn.jpg // 获取数据源文件File对象的名称(mn.jpg) String srcFileName = srcFile.getName(); // 创建目的地文件File对象,路径名是目的地目录+mn.jpg组成myCharStream\\itcast\\mn.jpg File destFile = new File(destFolder, srcFileName); // 复制文件 copyFile(srcFile, destFile); } } private static void copyFile(File srcFile, File destFile) throws IOException { BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile)); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile)); byte[] bys = new byte[1024]; int len; while ((len = bis.read(bys)) != -1) { bos.write(bys, 0, len); } bos.close(); bis.close(); } }
1.3 复制多级文件夹
- 需求: 把"E:\\itcast"这个文件夹复制到F盘目录下
分析步骤:
1. 创建数据源File对象,路径是E:\\itcast
2. 创建目的地File对象,路径是F
3. 写方法实现文件夹的复制,参数为数据源File对象和目的地File对象
4. 判断数据源File是否是文件
是文件:直接复制,用字节流
不是文件:在目的地创建目录,遍历该目录下的所有文件的File数组,得到每一个File对象,回到3继续
示例
package CopyFolderDemo2; import java.io.*; public class CopyFolderDemo2 { public static void main(String[] args) { // 创建数据源File对象,路径是E:\\itcast File srcFile = new File("E:\\itcast"); // 创建目的地File对象,路径是F:\\ File destFile = new File("F:\\"); // 写方法实现文件夹的复制,参数为数据源File对象和目的地File对象 copyFolder(srcFile, destFile); } private static void copyFolder(File srcFile, File destFile) { // 判断数据源File是否是目录 if (srcFile.isDirectory()) { // 在目的地目录下创建和数据源File名称一样的目录 String srcFileName = srcFile.getName(); File newFolder = new File(destFile, srcFile); if (!newFolder.exists()) { newFolder.mkdir(); } // 获取数据源File下所有文件或者目录的File数组 File[] fileArray = srcFile.listFiles(); // 遍历File数组,得到每一个File对象 for (File file : fileArray) { // 把File作为数据源File对象,递归调用复制文件夹的方法 copyFolder(file, newFolder); } } else { // 说明是文件,直接复制,用字节流 File newFile = new File(destFile, srcFile.getName()); } } // 字节缓冲流复制文件 private static void copyFile(File srcFile, File destFile) throws IOException { BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile)); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile)); byte[] bys = new byte[1024]; int len; while ((len = bis.read(bys)) != -1) { bos.write(bys, 0, len); } bos.close(); bis.close(); } }
1.4 复制文件的异常处理
基本做法
package CopyFileDemo3; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class CopyFileDemo3 { public static void main(String[] args) { } // try---catch---finally private static void method2() { FileReader fr = null; FileWriter fw = null; try { fr = new FileReader("fr.txt"); fw = new FileWriter("fw.txt"); char[] chs = new char[1024]; int len; while ((len = fr.read(chs)) != -1) { fw.write(chs, 0, len); } } catch (Exception e) { e.printStackTrace() } finally { if (fw != null) { try { fw.close(); } catch (IOException e) { e.printStackTrace(); } } if (fr != null) { try { fr.close(); } catch (IOException e) { e.printStackTrace(); } } } } // 抛出处理 private static void method1() throws IOException { FileReader fr = new FileReader("fr.txt"); FileWriter fw = new FileWriter("fw.txt"); char[] chs = new char[1024]; int len; while ((len = fr.read(chs)) != -1) { fw.write(chs, 0, len); } fw.close(); fr.close(); } }
JDK7版本改进
package CopyFileDemo3; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class CopyFileDemo31 { public static void main(String[] args) { } // JDK7的改进方案 private static void method() throws IOException { try (FileReader fr = new FileReader("fr.txt"); FileWriter fw = new FileWriter("fw.txt"); ){ char[] chs = new char[1024]; int len; while ((len = fr.read()) != -1) { fw.write(chs, 0, len); } } catch (IOException e) { e.printStackTrace(); } } }
JDK9版本改进
package CopyFileDemo3; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; public class CopyFileDemo32 { public static void main(String[] args) { } // JDK9的改进方案 private static void method() throws IOException { FileReader fr = new FileReader("fr.txt"); FileWriter fw = new FileWriter("fw.txt"); try (fr; fw) { char[] chs = new char[1024]; int len; while ((len = fr.read()) != -1) { fw.write(chs, 0, len); } } catch (IOException e) { e.printStackTrace(); } } }
0x02 IO特殊操作流
2.1 标准输入流
System类中有两个静态的成员变量
- public static final inputStream in: 标准输入流,通常该流对应于键盘输入或由主机环境或用户指定的另一个输入流
- public static final PrintStream out: 标准输出流,通常该流对应于显示输出或由主机环境或用户指定的另一输出目标
自己实现键盘录入数据
package SystemInDemo; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.Scanner; public class SystemInDemo { public static void main(String[] args) throws IOException { // public static final InputStream in: 标准输入流 /* InputStream is = System.in; int by; while ((by = is.read()) != -1) { System.out.println((char) by) } */ // 如何把字节流转换为字符流?用转换流 InputStream is = System.in; InputStreamReader isr = new InputStreamReader(is); // 使用字符流能不能实现一次读取一行数据? 可以的 // 但是,一次读取一行数据的方法是字符花虫输入流的特有方法 // BufferedReader br = new BufferedReader(isr); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.println("请输入一个字符串"); String line = br.readLine(); System.out.println("你输入的字符串是: " + line); System.out.println("请输入一个整数:"); int i = Integer.parseInt(br.readLine()); System.out.println("你输入的整数是: " + i); // 自己实现键盘录入数据太麻烦了,所以Java就提供了一个类供我们使用 Scanner sc = new Scanner(System.in); } }
2.2 标准输出流
System类中有两个静态的成员变量
- public static final InputStream in: 标准输入流,通常该流对应于键盘输入或由主机环境或用户指定的另一个输入流
- public static final PrintStream out: 标准输出流,通常该流对应于显示输出或由主机环境或用户指定的另一输出目标
输出语句的本质:用一个标准的输出流
PrintStream ps = System.out;
PrintStream类有的方法,System.out都可以使用
示例
package SystemOutDemo; import java.io.PrintStream; public class SystemOutDemo { public static void main(String[] args) { // public static final PrintStream out: 输出流 PrintStream ps = System.out; // 能够方便打印各种数据值 ps.print("hello"); ps.print(100); ps.println("hello"); ps.println(100); // System.out的本质是一个字节输出流 System.out.println("hello"); System.out.println(100); System.out.println(); } }
2.3 字节打印流
- 打印流分类
字节打印流: PrintStream
字符打印流:PrintWriter
- 打印流的特点
只负责输出数据,不负责读取数据
永远不会抛出IOException
有自己的特有方法
- 字节打印流
PrintStream(String fileName): 使用指定的文件名创建新的打印流
使用继承父类的方法写数据,查看的时候会转码,使用自己的特有方法写数据,查看的数据原样输出
可以改变输出语句的目的地
public static void setOut(PrintStream out): 重新分配“标准”输出流
示例
package PrintStreamDemo; import java.io.FileNotFoundException; import java.io.PrintStream; public class PrintStreamDemo { public static void main(String[] args) throws FileNotFoundException { // PrintStream(String FileName): 使用指定的文件名创建新的打印流 PrintStream ps = new PrintStream("myOtherStream//ps.txt"); // 写数据 // 字节输出流的所有方法 ps.write(97); // 使用特有方法写数据 ps.print(97); ps.println(); ps.print(98); ps.println(97); ps.println(98); // 释放资源 ps.close(); } }
2.4 字符打印流
- 字符打印流构造方法
示例
package PrintWriterDemo; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; public class PrintWriterDemo { public static void main(String[] args) throws IOException { // PrintWriter(String fileName): 使用指定的文件创建一个新的PrintWriter,而不需要自动执行行行刷新 PrintWriter pw = new PrintWriter("myOtherStream//pw.txt"); pw.write("hello"); pw.write("\r\n"); pw.flush(); pw.write("world"); pw.write("\r\n"); pw.flush(); pw.println("hello"); pw.flush(); pw.println("world"); pw.flush(); // PrintWriter(Writer out, boolean autoFlush): 创建一个新的PrintWriter PrintWriter pw2 = new PrintWriter(new FileWriter("myOtherStream//pw.txt"), true); // PrintWriter pw2 = new PrintWriter(new FileWriter("myOtherStream//pw.txt"), false); pw2.println("hello"); pw.close(); } }
2.5 复制Java文件打印流改进版
- 需求:把模块目录下的PrintSteamDemo.java复制到模块目录下的Copy.java
分析步骤:
根据数据源创建字符输入流对象
根据目的地创建字符输出流对象
读写数据,复制文件
释放资源
示例
package CopyJavaDemo; import java.io.*; public class CopyJavaDemo { public static void main(String[] args) throws IOException { /* // 根据数据源创建字符输入流对象 BufferedReader br = new BufferedReader(new FileReader("myOtherStream//PrintStreamDemo.java")); // 根据目的地创建字符输出流对象 BufferedWriter bw = new BufferedWriter(new FileWriter("myOtherStream//copy.java")); // 读写数据,复制文件 String line; while ((line = br.readLine()) != null) { bw.write(line); bw.newLine(); bw.flush(); } // 释放资源 bw.close(); br.close(); */ // 根据数据源创建字符输入流对象 BufferedReader br = new BufferedReader(new FileReader("myOtherStream//PrintStreamDemo.java")); // 根据目的地创建字符输出流对象 PrintWriter pw = new PrintWriter(new FileWriter("myOtherStream//copy.java"), true); // 读写数据,复制文件 String line; while ((line = br.readLine()) != null) { pw.println(line); } // 释放资源 pw.close(); br.close(); } }
2.6 对象序列化流
- 对象序列化介绍
对象序列化:就是将对象保存到磁盘中,或者在网络中传输对象
这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型,对象的数据和对象存储的属性等信息
字节序列写到文件之后,相当于文件中吃酒了保存了一个对象的信息
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列话
- 对象序列化流:ObjectOutputStream
将Java对象的原始数据类型和属性写入OutputStream,可以使用ObjectOutputStream读取(重构)对象,可以通过使用流的文件来实现
对象的持久存储,如果流是网络套接字流,则可以在另一个主机或另一个进程中重构对象
- 构造方法
- 序列化对象的方法
示例
package ObjectOutputStreamDemo; public class Student implements Serializable{ private String name; private int age; public Student() { } public Student(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 "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
package ObjectOutputStreamDemo; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class ObjectOutputStreamDemo { public static void main(String[] args) throws IOException { // ObjectOutputStream(OutputStream out):创建一个写入指定的OutputStream的ObjectOutputStream ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("myOtherStream//oos.txt")); // 创建对象 Student s = new Student("alice", 30); // void writeObject(Object obj): 将指定的对象写入ObjectOutputStream oos.writeObject(s); // 释放资源 oos.close(); } }
注意事项:
- 一个对象要想被序列化,该对象所属的类必须实现Serializable接口
- Serialzable是一个标记接口,实现该接口,不需要重写任何方法
2.7 对象反序列化流
对象反序列化流:ObjectInputStream
ObjectInputStream反序列化先前使用ObjectOutputStream编写的原始数据和对象
- 构造方法
- 反序列化对象的方法
示例
package ObjectInputStreamDemo; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; public class ObjectInputStreamDemo { public static void main(String[] args) throws IOException, ClassNotFoundException { // ObjectInputStream(InputStream in): 创建从指定的InputStream读取的ObjectInputStream ObjectInputStream ois = new ObjectInputStream( new FileInputStream("myOtherStream//oos.txt")); // Object readObject(): ObjectInputStream读取一个对象 Object obj = ois.readObject(); Student s = (Student) obj; System.out.println(s.getName()+", "+s.getAge()); ois.close(); } }
2.8 serialVersionUID & transient
- serialVersionUID
用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会不会出问题?
会出问题,会抛出InvalidClassException异常
如果出问题了,如何解决?
重新序列化
给对象所属的类加一个serialVersionUID
private static final log serialVersionUID = 42L;
- transient
如果一个对象中的某个成员变量的值不想被序列化,又该如何实现?
给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程
示例
package ObjectInputStreamDemo; public class Student { private String name; private int age; public Student() { } public Student(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 "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
package ObjectInputStreamDemo; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; public class ObjectInputStreamDemo { public static void main(String[] args) throws IOException, ClassNotFoundException { // ObjectInputStream(InputStream in): 创建从指定的InputStream读取的ObjectInputStream ObjectInputStream ois = new ObjectInputStream( new FileInputStream("myOtherStream//oos.txt")); // Object readObject(): ObjectInputStream读取一个对象 Object obj = ois.readObject(); Student s = (Student) obj; System.out.println(s.getName()+", "+s.getAge()); ois.close(); } }
0x03 Properties集合
3.1 Properties作为Map集合的使用
- Properties介绍
是一个Map体系的集合类
Propeties可以保存到流中或者从流中加载
属性列表的每个键及其对应的值都是一个字符串
基本使用方法
package PropertiesDemo1; import java.util.Properties; import java.util.Set; public class PropertiesDemo1 { public static void main(String[] args) { // 创建集合对象 // Properties<String, String> prop = new Properties<String, String>(); 错误 Properties prop = new Properties(); // 存储元素 prop.put("001", "Alice"); prop.put("002", "Bob"); prop.put("003", "Charlie"); // 遍历集合 Set<Object> keySet = prop.keySet(); for (Object key : keySet) { Object value = prop.get(key); System.out.println(key+", "+value); } } }
3.2 Properties作为mao集合的特有方法
- 特有方法
示例
package PropertiesDemo2; import java.util.Properties; import java.util.Set; public class PropertiesDemo2 { public static void main(String[] args) { // 创建集合对象 Properties prop = new Properties(); // Object setProperty(String key, String value) : 设置集合的键和值,都 // 是String类型,底层调用Hashtable方法put prop.setProperty("001", "Alice"); /* Object setProperty(String key, String value) { return put(key, value); } Object put(Object key, Object value) { return map.put(key, value); } */ prop.setProperty("002", "Bob"); prop.setProperty("003", "Charlie"); // String getProperty(String key): 使用此属性列表中指定的键搜索属性 System.out.println(prop.getProperty("001")); System.out.println(prop.getProperty("002")); System.out.println(prop.getProperty("003")); System.out.println(prop); System.out.println("-----------------"); // Set<String> stringPropertyName():从该属性列表中返回一个不可修改的键集, // 其中键及其对应的值都是字符串 Set<String> names = prop.stringPropertyNames(); for(String key: names) { System.out.println(key); String value = prop.getProperty(key); System.out.println(key+", "+value); } } }
3.3 Properties和IO流相结合的方法
- 和IO流结合的方法
示例
package PropertiesDemo3; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.Properties; public class PropertiesDemo3 { public static void main(String[] args) throws IOException { // 把集合中的数据保存到文件 myStore(); // 把文件中的数据加载到集合 // myLoad(); } private static void myLoad() throws IOException { Properties prop = new Properties(); // void load(Reader, Reader): FileReader fr = new FileReader("TestFolder/ts1.txt"); prop.load(fr); fr.close(); System.out.println(prop); } private static void myStore() throws IOException { Properties prop = new Properties(); prop.setProperty("001", "Alice"); prop.setProperty("002", "Bob"); prop.setProperty("003", "Charlie"); // void store(Writer writer, String comments) FileWriter fw = new FileWriter("TestFolder/ts1.txt"); prop.store(fw, "This is a test"); fw.close(); } }
3.4 游戏次数案例
- 案例需求:
实现猜数字小游戏只能试玩3次,如果还想玩,提示:游戏试玩已结束,并提示充值
- 分析步骤:
1. 写一个游戏类,里面有一个猜数字的小游戏
2. 写一个测试类,测试类中有main()方法,main()方法中写如下代码:
从文件中读取数据到Properties集合,用load()方法实现
文件已经存在TestFolder/game.txt
里面有一个数据值: count=0
通过Properties集合获取到游玩的次数
判断次数是否已经超过3次
如果到了,给出提示:游戏试玩已经结束,继续的话需要充值
如果不到3次,次数+1,重写到文件,用Properties的store()方法实现玩游戏
package PropertiesTest; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.Properties; public class PropertiesTest { public static void main(String[] args) throws IOException { // 从文件中读取数据到Properties集合,用load()方法实现 Properties prop = new Properties(); FileReader fr = new FileReader("TestFolder/game.txt"); prop.load(fr); fr.close(); // System.out.println(prop); // 通过Properties集合获取到游玩的次数 String count = prop.getProperty("count"); int num = Integer.parseInt(count); // System.out.println(num); // 判断次数是否到3次 if (num >= 3) { // 如果到了,给出提示,游戏试玩结束,需要充值 System.out.println("试玩超过3次,游戏已结束,请充值"); } else { // 玩游戏 GuessNumber.start(); // 次数+1,写回文件,用Properties的store()方法实现 num += 1; prop.setProperty("count", String.valueOf(num)); FileWriter fw = new FileWriter("TestFolder/game.txt"); prop.store(fw, "writing process"); fw.close(); } } }
package PropertiesTest; import java.util.Random; import java.util.Scanner; public class GuessNumber { private GuessNumber() { } public static void start() { // 要完成猜数字游戏,首先要有一个已经确定的数字,使用随机数方法生成该数字,范围1到100 Random r = new Random(); int num = r.nextInt(100) + 1; while (true) { // 使用程序实现猜数字,输入使用键盘输入实现 Scanner sc = new Scanner(System.in); System.out.println("请输入你要猜的数字: >> "); int guessNum = sc.nextInt(); // 比较输入的数字和系统产生的数据,需要使用分支语句if...else if...else if (guessNum > num) { System.out.println("你猜的数字" + guessNum + "大了"); } else if (guessNum < num) { System.out.println("你猜的数字" + guessNum + "小了"); } else { System.out.println("猜中了"); break; } } } }