1.对象流-ObjectInputStream和ObjectOutputStream(处理流包装流)的基本原理
看一个需求:
1.将int num=100这个int数据保存到文件中,注意不是100数字,而是int 100,并且,能够从文件中直接恢复int 100
2.将Dog dog=new Dog("小黄",3)这个dog对象保存到文件中,并且能够从文件恢复,恢复的也是dog对象。
3.上面的要求,就是能够将基本数据类型或者对象进行序列化和反序列化操作。
序列化和反序列化:
1.序列化就是在保存数据时,保存数据的值和数据类型
2.反序列化就是在恢复数据时,恢复数据的值和数据类型
3.需要让某个对象支持序列化机制,则必须让其类是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一:
Serializable //这是一个标记接口(声明性质的,没有方法)
Externalizable (该接口有方法需要实现,因此我们一般实现上面的Serializable接口
功能:提供了对基本类型或对象类型的序列化和反序列化的方法
2.ObjectOutputStream提供序列化功能
其中OutputStream是一个抽象类
3.ObjectInputStream提供反序列化功能
我们从构造器里面也可以看出来处理流和包装流的概念,我们发现.ObjectInputStream有一个.构造器InputStream,它可以接收一个InputStream。即只要是InputStream的子类都可以发生进去。即像我们之前所说的修饰器模式。
2.ObjectOutputStream的使用
2.1使用ObjectOutputStream序列化基本数据类型和一个Dog对象(name,age),并保存到a.txt文件中
我们设计的代码如下所示:
package com.rgf.outputStream_;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* 演示ObjectOutputStream的使用,完成数据的序列化
*/
public class ObjectOutStream_ {
public static void main(String[] args) throws Exception {
//序列化后,保存的文件格式,不是存文本,而是按照他的格式来保存
String filePath="E:\\a.txt";
ObjectOutputStream cos = new ObjectOutputStream(new FileOutputStream(filePath));
//序列化数据到E:\\a.txt;
cos.writeInt(100);//int ->Integer(实现了Serializable接口)
cos.writeBoolean(true); //boolean ->Boolean(实现了Serializable接口)
cos.writeChar('a');//char ->Character(实现了Serializable接口)
cos.writeDouble(9.5); //double->Double (实现了Serializable接口)
cos.writeUTF("蕾峰编程"); //String
//保存一个Dog对象
cos.writeObject(new Dog("旺财",3));
cos.close();
System.out.println("数据保存完毕(序列化形式)");
}
}
//如果需要序列化某个类的对象,必须实现Serializable接口
class Dog implements Serializable {
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
}
我们运行之后如下所示:
虽然是乱码,但是我们也可以看出来。比如Dog,序列化的文件并不会跟着文件的后缀形式而进行保存之后的呈现。而是有自己的表现方式。
2.2使用ObjectInputStream读取a.txt并反序列化恢复数据
我们的代码如下所示:
package com.rgf.fileInputStream; import java.io.FileInputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class ObjectInputStream_ { public static void main(String[] args) throws Exception{ //指定反序列化的文件 String filePath="e:\\a.txt"; ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath)); //读取 //1.读取(反序列化)的顺序需要和你保存数据(序列化)的顺序一致 //2.否则会出现异常 System.out.println(ois.readInt()); System.out.println(ois.readBoolean()); System.out.println(ois.readChar()); System.out.println(ois.readDouble()); System.out.println(ois.readUTF()); Object dog = ois.readObject(); System.out.println("运行类型="+dog.getClass()); System.out.println("dog信息="+dog); //底层 将Object会转成Dog. //关闭流,关闭外层流即可,底层会关闭 FileInputStream流 ois.close(); } }
我们运行之后如下所示:
我们发现在输出里面,我们没有输出dog的详细信息。为了出现这个详细信息,我们需要重写这个方法。我们重写toString方法如下所示:
package com.rgf.outputStream_; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; /** * 演示ObjectOutputStream的使用,完成数据的序列化 */ public class ObjectOutStream_ { public static void main(String[] args) throws Exception { //序列化后,保存的文件格式,不是存文本,而是按照他的格式来保存 String filePath="E:\\a.txt"; ObjectOutputStream cos = new ObjectOutputStream(new FileOutputStream(filePath)); //序列化数据到E:\\a.txt; cos.writeInt(100);//int ->Integer(实现了Serializable接口) cos.writeBoolean(true); //boolean ->Boolean(实现了Serializable接口) cos.writeChar('a');//char ->Character(实现了Serializable接口) cos.writeDouble(9.5); //double->Double (实现了Serializable接口) cos.writeUTF("蕾峰编程"); //String //保存一个Dog对象 cos.writeObject(new Dog("旺财",3)); cos.close(); System.out.println("数据保存完毕(序列化形式)"); } } //如果需要序列化某个类的对象,必须实现Serializable接口 class Dog implements Serializable { private String name; private int age; public Dog(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Dog{"+ "name='" +name + '\''+ ",age="+ age+ '}'; } }
我们重新执行序列化代码,再执行反序列化代码,如下所示:
我们发现可以拿到了dog的详细信息,所以我们在反序列化的时候,我们要对这个类进行处理并且拿到。
我们发现如下原因:
我们在dog里面创建get和set方法:
package com.rgf.outputStream_; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; /** * 演示ObjectOutputStream的使用,完成数据的序列化 */ public class ObjectOutStream_ { public static void main(String[] args) throws Exception { //序列化后,保存的文件格式,不是存文本,而是按照他的格式来保存 String filePath="E:\\a.txt"; ObjectOutputStream cos = new ObjectOutputStream(new FileOutputStream(filePath)); //序列化数据到E:\\a.txt; cos.writeInt(100);//int ->Integer(实现了Serializable接口) cos.writeBoolean(true); //boolean ->Boolean(实现了Serializable接口) cos.writeChar('a');//char ->Character(实现了Serializable接口) cos.writeDouble(9.5); //double->Double (实现了Serializable接口) cos.writeUTF("蕾峰编程"); //String //保存一个Dog对象 cos.writeObject(new Dog("旺财",3)); cos.close(); System.out.println("数据保存完毕(序列化形式)"); } } //如果需要序列化某个类的对象,必须实现Serializable接口 class Dog implements Serializable { private String name; private int age; public Dog(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 "Dog{"+ "name='" +name + '\''+ ",age="+ age+ '}'; } }
我们发现在反序列化的时候是无法进行调用dog的get方法的。
我们将Dog类公共化,我们进行修改代码如下所示
package com.rgf.outputStream_; import java.io.Serializable; public class Dog implements Serializable { private String name; private int age; public Dog(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 "Dog{"+ "name='" +name + '\''+ ",age="+ age+ '}'; } }
我们修改代码如下所示:
package com.rgf.fileInputStream; import com.rgf.outputStream_.Dog; import java.io.FileInputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class ObjectInputStream_ { public static void main(String[] args) throws Exception{ //指定反序列化的文件 String filePath="e:\\a.txt"; ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath)); //读取 //1.读取(反序列化)的顺序需要和你保存数据(序列化)的顺序一致 //2.否则会出现异常 System.out.println(ois.readInt()); System.out.println(ois.readBoolean()); System.out.println(ois.readChar()); System.out.println(ois.readDouble()); System.out.println(ois.readUTF()); //dog的编译类型是 Object,dog的运行类型是 Dog Object dog = ois.readObject(); System.out.println("运行类型="+dog.getClass()); System.out.println("dog信息="+dog); //底层 将Object会转成Dog. //这里是特别重要的细节: //1.如果我们希望调用Dog的方法,需要向下转型 //2.需要我们将Dog类的定义,拷贝到可以引用的位置 Dog dog2=(Dog)dog; System.out.println(dog2.getName()); //旺财 //关闭流,关闭外层流即可,底层会关闭 FileInputStream流 ois.close(); } }
我们运行之后如下所示:
节点流和处理流
2.3节点流和处理流的注意事项
(1)读写顺序要一致
当我们改变顺序的时候,我们的代码如下所示:
package com.rgf.fileInputStream; import com.rgf.outputStream_.Dog; import java.io.FileInputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class ObjectInputStream_ { public static void main(String[] args) throws Exception{ //指定反序列化的文件 String filePath="e:\\a.txt"; ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath)); //读取 //1.读取(反序列化)的顺序需要和你保存数据(序列化)的顺序一致 //2.否则会出现异常 System.out.println(ois.readUTF()); System.out.println(ois.readInt()); System.out.println(ois.readBoolean()); System.out.println(ois.readChar()); System.out.println(ois.readDouble()); //dog的编译类型是 Object,dog的运行类型是 Dog Object dog = ois.readObject(); System.out.println("运行类型="+dog.getClass()); System.out.println("dog信息="+dog); //底层 将Object会转成Dog. //这里是特别重要的细节: //1.如果我们希望调用Dog的方法,需要向下转型 //2.需要我们将Dog类的定义,拷贝到可以引用的位置 Dog dog2=(Dog)dog; System.out.println(dog2.getName()); //旺财 //关闭流,关闭外层流即可,底层会关闭 FileInputStream流 ois.close(); } }
我们运行之后会出现如下错误:
(2)要求序列化或反序列化对象,需要实现Serializable
(3)序列化的类中建议添加SerialVersionUID,为了提高版本的兼容性
我们可以在我们设置的dog类进行演示:
package com.rgf.outputStream_; import java.io.Serializable; public class Dog implements Serializable { private String name; private int age; //增加序列化的版本号后,我们增加新的东西只会认为是一个版本的修改,而不会认为是一个新的类。 private String hobby; //serialVersionUID 序列化的版本号,可以提高兼容性 private static final long serialVersionUID=1L; public Dog(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 "Dog{"+ "name='" +name + '\''+ ",age="+ age+ '}'; } }
(4)序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员
我们进行示例如下所示:
我们的dog类如下所示:
package com.rgf.outputStream_; import java.io.Serializable; public class Dog implements Serializable { private String name; private int age; //(4)序列化对象时,默认将里面所有属性都进行序列化,但除了static或transient修饰的成员 private static String nation; private transient String color; public Dog(String name, int age,String nation,String color) { this.name = name; this.age = age; this.color=color; this.nation=nation; } 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 "Dog{" + "name='" + name + '\'' + ", age=" + age + ", color='" + color + '\'' + '}' +nation ; } }
我们增加了dog类的属性,同时用static和 transient进行修饰,我们进行写入如下所示:
package com.rgf.outputStream_; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; /** * 演示ObjectOutputStream的使用,完成数据的序列化 */ public class ObjectOutStream_ { public static void main(String[] args) throws Exception { //序列化后,保存的文件格式,不是存文本,而是按照他的格式来保存 String filePath="E:\\a.txt"; ObjectOutputStream cos = new ObjectOutputStream(new FileOutputStream(filePath)); //序列化数据到E:\\a.txt; cos.writeInt(100);//int ->Integer(实现了Serializable接口) cos.writeBoolean(true); //boolean ->Boolean(实现了Serializable接口) cos.writeChar('a');//char ->Character(实现了Serializable接口) cos.writeDouble(9.5); //double->Double (实现了Serializable接口) cos.writeUTF("蕾峰编程"); //String //保存一个Dog对象 cos.writeObject(new Dog("旺财",3,"中国","白色")); cos.close(); System.out.println("数据保存完毕(序列化形式)"); } }
我们进行输出后如下所示:
我们打开文件可以查看到序列化里面只有name和age,而没有color和nation。
(5)序列化对象时,要求里面属性的类型也需要实现序列化接口
我们创建Master类如下所示:
package com.rgf.outputStream_; public class Master { }
我们之后在dog类里面引用这个类:
//序列化对象时,要求里面属性的类型也需要实现序列化接口 private Master master = new Master();
我们没有实现接口进行序列化的时候,我们发现出现了报错:
我们修改master来实现seeializable接口:
package com.rgf.outputStream_; import java.io.Serializable; public class Master implements Serializable { }
我们修改我们的tostring,如下所示:
public String toString() { return "Dog{" + "name='" + name + '\'' + ", age=" + age + ", color='" + color + '\'' + '}' +nation+ ""+master ; } }
我们运行之后如下所示:
(6)序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化
3.标准输入输出流
类型 默认设备 System.in 标准输入 (System里面的一个属性) InputStream 键盘 System.out 标准输出 (System里面的一个属性) PrintStream 显示器 我们进行演示如下所示:
package com.rgf.standard; public class InputAndOutput { public static void main(String[] args) { //public final static InputStream in = null; System.in } }
我们查看源码如下所示:
我们查看他们的类型如下所示:
package com.rgf.standard; public class InputAndOutput { public static void main(String[] args) { //System 类 的 public final static InputStream in = null; //System.in 编译类型 InputStream //System.in 运行类型 BufferedInputStream System.out.println(System.in.getClass()); } }
我们运行之后如下所示:
我们查看System.out的类型:
package com.rgf.standard; public class InputAndOutput { public static void main(String[] args) { //System 类 的 public final static InputStream in = null; //System.in 编译类型 InputStream //System.in 运行类型 BufferedInputStream(字节流、处理流) //表示标准输入 键盘 System.out.println(System.in.getClass()); //System 类 的 public final static PrintStream out = null; //System.out 编译类型 PrintStream //System.out 运行类型 PrintStream //表示标准输出 显示器 System.out.println(System.out.getClass()); } }
我们运行之后如下所示:
我们的案例如下所示:
package com.rgf.standard; import java.util.Scanner; public class InputAndOutput { public static void main(String[] args) { //System 类 的 public final static InputStream in = null; //System.in 编译类型 InputStream //System.in 运行类型 BufferedInputStream(字节流、处理流) System.out.println(System.in.getClass()); //System 类 的 public final static PrintStream out = null; //System.out 编译类型 PrintStream //System.out 运行类型 PrintStream System.out.println(System.out.getClass()); System.out.println("hello,蕾峰编程"); Scanner scanner = new Scanner(System.in); //BufferedInputStream. System.out.println("请输入内容"); String next = scanner.next(); System.out.println("next="+next); } }
我们运行之后如下所示:
4.转换流 InputStreamReader和OutputStreamWriter
我们设计的代码如下所示:
package com.rgf.transformation; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; /** * 看一个中文乱码问题 * */ public class CodeQuestion { public static void main(String[] args) throws IOException { //读取e:\\b.txt文件到程序 //思路 //1.创建字符输入流 BufferedReader (处理流,处理流里面可以放一个节点流Buffered) //2.使用BufferedReader对象读取a.txt //3.默认情况下,读取文件是按照UTF-8编码 String filePath="e:\\b.txt"; BufferedReader br = new BufferedReader(new FileReader(filePath)); String s = br.readLine(); System.out.println("读取到的内容为:"+ s); br.close(); } }
我们运行之后如下所示:
我们打开该文件,可以了解到该文件的编码为UTF-8:
当我们转换成不是UTF-8编码的时候,我们发现如下所示:
ANSI为国标码,为gbk码。我们进行保存而且替换掉它。
我们继续运行之后如下所示:
我们发现出现乱码了。我们没有指定我们所要读取的文件格式。而字节流是可以指定的。我们通过转换流来解决该问题。
介绍:
1.InputStreamReader:Reader的子类,可以将InputStream(字节流)包装成Reader(字符流)
我们发现里面有一个方法InputStreamReader(InputStream,Charset),InputStream是字节流的抽象父类,而Charset指定他的编码。可以传入一个InputStream对象,而且可以指定处理的编码。
2.OutputStreamWriter:Writer的子类,实现将OutputStream(字节流)包装(转换)成Writer(字符流)
我们发现里面有一个方法OutputStreamWriter(OutputStream,Charset),OutputStream是字节流的抽象父类,而Charset指定他的编码。可以传入一个OutputStream对象,而且可以指定处理的编码。
3.当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流
4.可以在使用时指定编码格式(比如utf-8,gbk,gb2312,ISO8859-1等)
我们利用如下实例来进行熟练:
(1)字节流FileInputStream包装成(转换成)字符流InputStreamReader,对文件进行读取(按照utf-8/gbk格式),进而在包装成BufferedReader我们设计的代码如下所示:
package com.rgf.transformation; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; /** * 演示使用InputStreamReader转换流解决中文乱码问题 * 将字节流FileInputStream转换成字符流InputStreamReader,指定编码gbk/utf-8 */ public class InputStreamReader_ { public static void main(String[] args) throws IOException { String filePath="e:\\b.txt"; //解读 //1.把 FileInputStream(filePath)转成 InputStreamReader //2.指定编码 InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath),"gbk"); //3.把InputStreamReader传入 BufferedReader BufferedReader br = new BufferedReader(isr); //4.读取 String s = br.readLine(); System.out.println("读取内容="+s); //5.关闭外层流 br.close(); } }
我们运行之后如下所示:
我们也可以将代码进行简洁如下所示:
package com.rgf.transformation; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; /** * 演示使用InputStreamReader转换流解决中文乱码问题 * 将字节流FileInputStream转换成字符流InputStreamReader,指定编码gbk/utf-8 */ public class InputStreamReader_ { public static void main(String[] args) throws IOException { String filePath="e:\\b.txt"; //解读 //1.把 FileInputStream(filePath)转成 InputStreamReader //2.指定编码 //InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath),"gbk"); //3.把InputStreamReader传入 BufferedReader //BufferedReader br = new BufferedReader(isr); //将2和3合在一起写 BufferedReader br= new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "gbk")); //4.读取 String s = br.readLine(); System.out.println("读取内容="+s); //5.关闭外层流 br.close(); } }
(2)字节流FileOutputStream包装成(转换成)字符流OutputStreamWriter,对文件进行写入(按照utf-8/gbk格式),进而在包装成BufferedWriter。
我们设计的代码如下所示:
package com.rgf.transformation; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; /** * 演示OutputStreamWriter 使用 * 把FileOutputStream 字节流,转成字符流 * 指定处理的编码 gdk/utf-8/utf8 */ public class OutputStreamWriter_ { public static void main(String[] args) throws IOException { String filePath="e:\\b.txt"; String charSet="gbk"; OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath), charSet); osw.write("hello,雷锋编程教育"); osw.close(); System.out.println("按照"+charSet+"保存文件成功"); } }
我们运行之后如下所示:
我们打开文件如下所示:
我们查看到此时的编码为ANSI编码,即为对应的gbk码。
我们将编码修改如下所示:
package com.rgf.transformation; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; /** * 演示OutputStreamWriter 使用 * 把FileOutputStream 字节流,转成字符流 * 指定处理的编码 gdk/utf-8/utf8 */ public class OutputStreamWriter_ { public static void main(String[] args) throws IOException { String filePath="e:\\b.txt"; String charSet="utf-8"; OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath), charSet); osw.write("hello,雷锋编程教育"); osw.close(); System.out.println("按照"+charSet+"保存文件成功"); } }
我们运行之后如下所示:
我们打开文件进行查看:
我们发现此时为utf-8了。
5. 打印流-PrintStream(字节流)和PrintWriter(字符流)
打印流只有输出流没有输入流
PrintStream:
我们查看他的类图如下所示:
我们进行查看他的构造方法:
我们来发现PrintStream的用法:
我们发现system.out就是PrintStream
我们编写如下代码进行查看:
package com.rgf.printStream; import java.io.IOException; import java.io.PrintStream; /** * 演示PrintStream(字节打印流/输出流 ) */ public class PrintStream_ { public static void main(String[] args) throws IOException{ PrintStream out=System.out; //在默认情况下,打印流PrintStream输出数据的位置是标准输出,即显示器 out.print("hello,we"); out.write("woaini".getBytes()); //因为print底层使用的是write方法,所以我们可以直接调用write方法进行打印/输出。 /* out.print的源码。 public void print(String s) { if (s == null) { s = "null"; } write(s); }*/ /* 我们发现真正的打印输出的是write方法: private void write(String s) { try { synchronized (this) { ensureOpen(); textOut.write(s); textOut.flushBuffer(); charOut.flushBuffer(); if (autoFlush && (s.indexOf('\n') >= 0)) out.flush(); } } catch (InterruptedIOException x) { Thread.currentThread().interrupt(); } catch (IOException x) { trouble = true; } } */ out.close(); //我们也可以去修改打印流输出的位置/设备 //setOut可以去修改PrintStream,默认为向显示器打印的。 //1.输出修改成到e:\\a.txt //2.“hello,woren”就会输出到e:\\a.txt。 System.setOut(new PrintStream("e:\\a.txt")); //此时文件已经切换到PrintStream里面,然后输出就会输出到里面。 /* setOut的底层代码: public static void setOut(PrintStream out) { checkIO(); setOut0(out); setOut0(out)的底层代码: private static native void setIn0(InputStream in); private static native void setOut0(PrintStream out); //native方法,修改了out。 private static native void setErr0(PrintStream err); setOut0(out)是一个native方法。 } */ System.out.println("hello,woren"); } }
我们运行之后如下所示:
我们打开文件进行查看,我们发现成功的输送出来了。
我们通过setOut方法修改了输出的位置。
我们查看PrintWriter
我们查看他的构造方法
我们设计如下代码:
package com.rgf.printStream; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; /** * 演示PrintWriter使用方式 */ public class PrintWriter_ { public static void main(String[] args) throws IOException { PrintWriter printWriter = new PrintWriter(System.out); // PrintWriter printWriter = new PrintWriter(new FileWriter("e:\\a.txt")); printWriter.print("hi,北京你好"); printWriter.close(); //printWriter.close( );没有colse就不会刷新。 } }
我们运行之后如下所示:
我们发现输送在控制台上面了。
为了输送到文件里面,我们创建如下所示:
package com.rgf.printStream; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; /** * 演示PrintWriter使用方式 */ public class PrintWriter_ { public static void main(String[] args) throws IOException { //PrintWriter printWriter = new PrintWriter(System.out); PrintWriter printWriter = new PrintWriter(new FileWriter("e:\\a.txt")); printWriter.print("hi,北京你好"); printWriter.close(); //flush+关闭流,才会将数据写入到文件。。 //printWriter.close( );没有colse就不会刷新。 } }
我们运行之后如下所示:
我们发现这里没有,我们打开文件如下所示:
如果里面的close没有关闭,里面是不会刷新的。我们示例如下所示:
package com.rgf.printStream; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; /** * 演示PrintWriter使用方式 */ public class PrintWriter_ { public static void main(String[] args) throws IOException { PrintWriter printWriter = new PrintWriter(System.out); // PrintWriter printWriter = new PrintWriter(new FileWriter("e:\\a.txt")); printWriter.print("hi,北京你好"); printWriter.close(); //printWriter.close( );没有colse就不会刷新。flush+关闭流,才会将数据写入到文件。 } }
我们运行之后如下苏所示:
我们发现仍然是:
因为我们没有close,没有close就不会刷新。我们在创建文件的时候,会首先把光标定在文件的最前面,会首先把文件截断再输出,结果我们没有close,就不会刷新。
我们分析源码来判断出close的作用:
我们debug如下所示:
PrintStream主要操作byte流,而PrintWriter用来操作字符流,读取文本文件时一般用后者。
6.Properties类
如果我们将所需要的配置文件写死,改起来会非常困难,所以我们可以将我们需要的文件写为properties文件,然后程序进行读取修改。
看一个需求:
如下一个配置文件mysql.properties:
ip=192.168.0.13
user=root
pwd=12345
请问编程读取ip、user和pwd的值是多少
分析:
1.传统的方法
2.使用Properties类可以方便实现
我们传统的方法为:
传统的方法比较麻烦。
我们创建配置文件如下所示:
我们设置的代码如下所示:
package com.rgf.properties_;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class Properties01 {
public static void main(String[] args) throws IOException {
//读取mysql.properties文件,并得到ip,user和pwd
BufferedReader br = new BufferedReader(new FileReader("com\\mysql.properties"));
String line="";
while ((line=br.readLine())!=null){ //循环读取
System.out.println(line);
}
br.close();
}
}
我们运行之后如下所示:
我们修改代码如下所示:
package com.rgf.properties_;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class Properties01 {
public static void main(String[] args) throws IOException {
//读取mysql.properties文件,并得到ip,user和pwd
BufferedReader br = new BufferedReader(new FileReader("com\\mysql.properties"));
String line="";
while ((line=br.readLine())!=null){ //循环读取
String[] split = line.split("=");
System.out.println(split[0]+"值是:"+split[1]);
}
br.close();
}
}
我们运行之后如下所示:
如果只打印ip,我们将代码修改如下所示:
package com.rgf.properties_; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; public class Properties01 { public static void main(String[] args) throws IOException { //读取mysql.properties文件,并得到ip,user和pwd BufferedReader br = new BufferedReader(new FileReader("com\\mysql.properties")); String line=""; while ((line=br.readLine())!=null){ //循环读取 String[] split = line.split("="); //如果我们要求指定得到ip值 if("ip".equals(split[0])) { System.out.println(split[0] + "值是:" + split[1]); } } br.close(); } }
我们运行之后如下所示:
我们对Properties类的基本介绍:
(1)专门用于读写配置文件的集合类:
配置文件的格式:
键=值
键=值
(2)注意:键值对不需要有空格,值不需要用引号-起来。默认类型是String.
(3)Properties的常见方法
load:加载配置文件的键值对到Properties对象
list:将数据显示到指定设备/流对象
getProperty(key):根据键获取值
setProperty(key,value):设置键值对到Properties对象
store:将Properties中的键值对存储到配置文件,在idea中,保存信息到配置文件,如果含有中文,会存储为unicode码(创建文件)
我们通过案例来进行了解:
1.使用Properties类完成对mysql.properties的读取。
我们设置的代码如下所示:
package com.rgf.properties_; import java.io.FileReader; import java.io.IOException; import java.util.Properties; public class Properties02 { public static void main(String[] args) throws IOException { //使用Properties类完成对mysql.properties的读取 //1.创建Properties对象 Properties properties = new Properties(); //2.加载指定的配置文件 properties.load(new FileReader("com\\mysql.properties")); //3.把k-v显示到控制台 properties.list(System.out); //4.根据key获取对应的值 String user = properties.getProperty("user"); String pwd = properties.getProperty("pwd"); System.out.println("用户名="+user); System.out.println("密码是="+pwd); } }
运行之后如下所示:
2.使用Properties类添加key-val到新文件mysql2.properties中
我们设计的代码如下所示:
package com.rgf.properties_; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.util.Properties; public class Properties03 { public static void main(String[] args) throws IOException { //使用Properties类来创建配置文件,修改配置文件内容 //1.创建Properties对象 Properties properties = new Properties(); //2.创建配置文件 properties.setProperty("charset","utf8"); properties.setProperty("user","汤姆"); //之一保存时,是中文的unicode码值 properties.setProperty("pwd","abc111"); //3.将k-v存储到文件中即可。 properties.store(new FileOutputStream("com\\mysql2.properties"),null); System.out.println("保存配置文件成功"); //4.加载指定的配置文件 properties.load(new FileReader("com\\mysql2.properties")); //5.把k-v显示到控制台 properties.list(System.out); } }
我们运行之后如下所示:
我们查看里面的mysql2.properties:
我们发现将k-v存储到文件里面的时候,后面的为null,Null的含义为:注释
我们追加进去查看源码,如下所示:
public void store(OutputStream out, String comments) throws IOException { store0(new BufferedWriter(new OutputStreamWriter(out, "8859_1")), comments, true); }
我们将null换成"hello mysql",
我们发现里面的user为unicode码。我们通过查询看这个unicode码是否为汤姆。
http://tool.chinaz.com/tools/unicode.aspx
我们发现转换后如下所示:
3.使用Properties类完成对mysql.properties的读取,并修改某个key-val。
我们设计代码如下所示:
package com.rgf.properties_; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.util.Properties; public class Properties03 { public static void main(String[] args) throws IOException { //使用Properties类来创建配置文件,修改配置文件内容 //1.创建Properties对象 Properties properties = new Properties(); //2.创建配置文件 //如果该文件没有key,就是创建, //如果该文件有key,就是修改。 properties.setProperty("charset","utf8"); properties.setProperty("user","汤姆"); //之一保存时,是中文的unicode码值 properties.setProperty("pwd","8888888"); //3.将k-v存储到文件中即可。 properties.store(new FileOutputStream("com\\mysql2.properties"),"hello world"); System.out.println("保存配置文件成功"); //4.加载指定的配置文件 properties.load(new FileReader("com\\mysql2.properties")); //5.把k-v显示到控制台 properties.list(System.out); } }
我们运行之后如下所示:
我们发现成功的修改过来了。
我们查看setProperty的源码:
public synchronized Object setProperty(String key, String value) { return put(key, value); }
//hashtable不允许value为null,否则会抛异常。 public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; //如果key存在,就替换 return old; } } addEntry(hash, key, value, index); //如果是新的key,就addEntry return null; }
Properties父类是Hashtable,底层就是Hashtable核心方法,如果hash值相同,则为替换,否则就是增加新的值。