打印流
- 打印流分类:字节打印流:PrintStream,字符打印流:PrintWriter。
- PrintStream构造方法选择采用String的方法
- 打印流的特点:只负责输出护具,不负责读取数据。他有自己的特有方法
- PrintStream(String fileName):使用指定得文件名创建新的打印流。`
PrintStream ps = new PrintStream("/home/ding/IdeaProjects/Study/src/com/itheima_04/ps.txt");
ps.write(97);
ps.close();
- 使用特有方法数据,print方法他是不会进行转码操作的
ps.print(97);
- 如何实现换行呢?
ps.print(97);
ps.println();
ps.print(98);
- 字节打印流PrintStream(String fileName) 使用指定的文件名创建新的打印流
- 使用继承父类的方法写数据,查看的时候会转码;使用自己特有方法写数据,查看的数据原样输出。
字符打印流
- printWriter方法如下,他有自己特有的print、println方法。
- 构造方法使用这两个
方法名 | 说明 |
---|---|
PrintWriter(String fileName) | 使用指定的文件名创建一个新的PrintWriter,而不需要自动执行刷新 |
PrintWriter(Writer out,boolean autoFlush) | 创建一个新的PrintWriter,out:字符输出流,autoFlush:一个布尔值,如果为真则println,printf,或format方法讲刷新输出缓冲区 |
PrintWriter pw = new PrintWriter("/home/ding/IdeaProjects/Study/src/com/itheima_04/pw.txt");
pw.write("hello");
- 执行完后发现文件里面没有东西,因为PrintWriter是一个字符打印流,字符流数据是不能直接到文件的。他需要一个动作pw.flush();
PrintWriter pw = new PrintWriter("/home/ding/IdeaProjects/Study/src/com/itheima_04/pw.txt");
pw.write("hello");
pw.flush();
pw.write("world");
pw.flush();
- println同理,也需要flush才能写入文件。
pw.println("hello");
pw.flush();
pw.println("world");
pw.flush();
- 能否让程序自动flush呢?PrintWriter的第二个构造方法由两个参数,Writer out 和 boolean autoFlush 。
PrintWriter pw = new PrintWriter(new FileWriter("/home/ding/IdeaProjects/Study/src/com/itheima_04/pw.txt"),true);
pw.println("hello");
复制java文件(打印流改进版)
- 把PrintStreamDemo.java复制到Copy.java
- 思路:
- 根据数据源创建字符输入流对象
- 根据目的地创建字符输出流对象
- 读写数据,复制文件
- 释放资源
import java.io.*;
public class CopyJavaDemo {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("/home/ding/IdeaProjects/Study/src/com/itheima_04/PrintStreamDemo.java"));
BufferedWriter bw = new BufferedWriter(new FileWriter("/home/ding/itcast/Copy.java"));
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
br.close();
bw.close();
}
}
- 接下来使用打印流来改进。
BufferedReader br = new BufferedReader(new FileReader("/home/ding/IdeaProjects/Study/src/com/itheima_04/PrintStreamDemo.java"));
PrintWriter pw = new PrintWriter(new FileWriter("/home/ding/itcast/Copy.java"));
String line;
while((line=br.readLine())!=null){
pw.println(line);
}
pw.close();
br.close();
对象序列化流
- 对象序列化:就是将对象保存到磁盘中,或者在网络中传输对象。
- 这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息。
- 字节序列写到文件之后,相当于文件中持久保存了一个对象的信息。
- 反之,该字节序列还可以从文件中读取回来,重构对象,对他进行反序列化。
- 对象序列化流:ObjectOutputStream,对象反序列化流:ObjectInputStream。
- ObjectOutputStream,他讲java对象的原始数据和图形写入字节输出流。可以使用ObjectInputStream读取(重构)对象。可以通过使用流的文件来实现文件到持久存储,如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象。
- 构造方法
- 通过这个方法实现对象序列化
- 新建一个学生类,name和age两个属性
- 主程序
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class ObjectOutputStreamDemo {
public static void main(String[] args) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("/home/ding/IdeaProjects/Study/src/com/itheima_04/oos.txt"));
Student s = new Student("林青霞",30);
oos.writeObject(s);
oos.close();
}
}
- 报错,该异常:抛出一个实例需要一个Seriallizable接口,序列化运行时或hi里的类可能会抛出此异常。参数应该是类的名称。类的序列化由实现java.io.Serializable接口的类启用。不实现此接口的类将不会使用任何状态序列化或反序列化。
- 学生类,实现接口
import java.io.Serializable;
public class Student implements Serializable {
- 再执行的时候就能成功,因为他是把整个字节序列写进来的,所以有些地方他是读不懂的。
- 一个对象想要被序列化,该对象所属的类必须实现Serializable接口
- Serializable是一个标记接口,实现该接口,不需要重写任何方法
对象反序列化流
- ObjectInputStream。他是一个字节输入流,ObjectInputStream反序列化先前使用ObjectOutputStream编写的原始数据和对象。
- 构造方法
- 使用readObject方法
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 ois = new ObjectInputStream(new FileInputStream("/home/ding/IdeaProjects/Study/src/com/itheima_04/oos.txt"));
Object obj = ois.readObject();
Student s = (Student) obj;
System.out.println(s.getName() + "," + s.getAge());
ois.close();
}
对象序列化流的三个问题
- 用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会不会出问题呢?
- 如果出问题了,如何解决呢?
- 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
import java.io.*;
public class ObjectStreamDemo {
public static void main(String[] args) throws IOException,ClassNotFoundException{
write();
//read();
}
//反序列化
public static void read() throws IOException,ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/home/ding/IdeaProjects/Study/src/com/itheima_04/oos.txt"));
Object obj = ois.readObject();
Student s = (Student) obj;
System.out.println(s.getName() + "," + s.getAge());
ois.close();
}
//序列化
private static void write() throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("/home/ding/IdeaProjects/Study/src/com/itheima_04/oos.txt"));
Student s = new Student("林青霞",30);
oos.writeObject(s);
oos.close();
}
}
- 通过反序列化读这个文件
- 用对象序列化了一个对对象后,假如我们修改了对象所属的类文件Student,往里面加上一个toString方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
- 再来读的时候就出问题了。当序列化运行时检测到类中的以下问题时抛出:累的串行版本与从流中的类描述符的类型不匹配、该类包含未知的数据类型、该类没有可访问的无参构造函数。
- 现在的问题是类的版本与从流中读取到类描述的类型不匹配。
serialVersionUID = -418256905501089388, local class serialVersionUID = -3547371408601222020
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
-
- 序列化运行时与每个可序列化的类关联一个版本号,称为serialVersionUID,他在反序列化的过程中使用,以验证序列化对象的发送者和接收者是否加载了与序列化兼容的对象的类。如果接受者已经为具有与对应发件人类型的不同的serialVersionUID对象加载了一个类,则反序列化将导致一个InvalidClassException。
- 一开始我们没往Student类里面添加toString方法之前,因为他实现了序列化接口,所以本身产生一个序列化id。write()的时候在文件里面保存了一个序列化id,一开始没添加方法之前读取是没有问题的,一旦在里面添加了方法,修改了类的文件,所以他被重新产生一个序列化id。那么再读的时候他不匹配所以会出问题。
- 一个可序列化的类可以通过声明一个名为“serialVersionUID"的字段来显式地声明他自己的serialVersionUID,该字段必须是static,final,long类型。
- 如果可序列化类没有显示声明serialVersionUID,则序列化运行时将根据Java™对象序列化规范中所述的类的各方面计算该类的默认serialVersionUID值。然而,强烈建议所有可序列化的类显式声明serialVersionUID值,因为默认的serialVersionUID计算对类细节非常敏感,这些细节可能因编译器实现而异,因此可能在反序列化期间导致意外的InvalidClassException。
- 因此,为了保证不同Java编译器实现之间的一致serialVersionUID值,一个可序列化的类必须声明一个显式的serialVersionUID值。而且这个serialVersionUID使用private 修饰符。
- 然后加了这个值之后,我每次序列化id都是他,他不会根据默认的规则计算
private static final long serialVersionUID = 42L;
- 然后我们重新执行write方法,再read。现在是不会有问题的
- 接着我们把toString方法给注释掉,而这次读取就是不会有问题的
- 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?我不想林青霞的年龄被别人知道怎么办呢?
- 我们给age成员变量修饰,被transient修饰的成员变量是不参与序列化的。
private transient int age;
- 修饰之后,成员变量默认值为0。
Properties
- 他的父类Hashtable实现了Map接口,所以他就是个map集合
- Properties类表示一组持久的属性。Properties可以保存到流中或从流中加载。
- 练习:Properties作为Map集合的使用
- Properties创建对象时报错,不能够有泛型
Properties prop = new Properties();
//存储元素
prop.put("itheima001", "林青霞");
prop.put("itheima002", "张曼玉");
prop.put("itheima003", "王祖贤");
//遍历集合
Set<Object> keySet = prop.keySet();
for (Object key : keySet) {
Object value = prop.get(key);
System.out.println(key + "," + value);
}
Prperties方法
- Properties作为集合的特有方法
方法名 | 说明 |
---|---|
String getProperty(String key) | 使用此属性列表中指定的键搜索属性 |
ObjectProperty(String key, String value) | 设置集合的键和值,都是String类型,底层调用Hashtable方法 put |
Set< String> stringPropertyNames() | 从该属性列表中返回一个不可改变的键集,其中键及其对应的值 是字符串 |
- setProperty他底层调用的其实是一个put方法,而put方法他的参数是Object类型的,虽然是Object的,但实际上传过来的事String类型的。
- 现在集合中就有三个键值对了
- 调用getProperty方法
Set<String> names = prop.stringPropertyNames();
for(String key : names){
System.out.println(key);
}
// System.out.println(prop.getProperty("itheima0011"));
Set<String> names = prop.stringPropertyNames();
for(String key : names){
// System.out.println(key);
String value = prop.getProperty(key);
System.out.println(key + "," + value);
}