1. 缓冲流
IO
流中有一些更强大的流,比如能够高效读写的缓冲流,能够转换编码的转换流,能够持久化存储对象的序列化流等.这些功能更为强大的流,都是在基本的流对象基础上创建出来的,相当于是对基本流对象的一种增强
1.1 概述
缓冲流,也叫高效流,是对基本的输入输出流的增强,所以也是4个流,按照数据类型分为:
- 字节缓冲流:
BufferedInputStream
,BufferedOutputStream
- 字符缓冲流:
BufferedReader
,BufferedWriter
缓冲流的基本原理是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO
次数,从而提高读写效率
1.2 字节缓冲流
构造方法
public BufferedInputStream(InputStream in)
:创建一个新的缓冲输入流public BufferedOutputStream(OutputStream out)
:创建一个新的缓冲输出流
public class Test1 {
public static void main(String[] args) throws IOException {
// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("b.txt"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("b.txt"));
}
}
1.3 字符缓冲流
构造方法
public BufferedReader(Reader in)
:创建一个新的缓冲输入流public BufferedWriter(Writer out)
:创建一个新的缓冲输出流
public class Test1 {
public static void main(String[] args) throws IOException {
// 创建字符缓冲输入流
BufferedReader bis = new BufferedReader(new FileReader("b.txt"));
// 创建字符缓冲输出流
BufferedWriter bos = new BufferedWriter(new FileWriter("b.txt"));
}
}
特有方法
字符缓冲流的基本方法与普通字符流调用方法一致,它的特有方法有:
BufferedReader:public String readLine()
:读一行文字BufferedWriter:public void newLine()
:写一行分隔符,由系统属性定义符号
public class Test1 {
public static void main(String[] args) throws IOException {
// 创建字符缓冲输入流
BufferedReader bis = new BufferedReader(new FileReader("b.txt"));
// 定义字符串,保存读取的一行文字
String str = null;
// 循环读取,读取到最后返回null
while (((str = bis.readLine()) != null)) {
System.out.println(str);
}
// 释放资源
bis.close();
}
}
public class Test1 {
public static void main(String[] args) throws IOException {
// 创建字符缓冲输出流
BufferedWriter bos = new BufferedWriter(new FileWriter("b.txt"));
// 写出数据
bos.write("bsj");
// 写出换行
bos.newLine();
// 释放资源
bos.close();
}
}
2. 转换流
2.1 字符编码和字符集
字符编码
计算机中存储的信息都是使用二进制数表示的,而我们在屏幕上看到的数字,英文,标点符号,汉字等字符是二进制转换之后的结果.按照某种规则,将字符存储到计算机中,称为编码
,反之,将存储在计算机中的二进制数按照某种规则解析显示出来的,称为解码
.
- 字符编码Character Encoding:就是一套自然语言的字符与二进制数之间的对应规则
- 编码表:生活中文字和计算机二进制的对应规则
字符集
- 字符集 Charset:也叫编码表.是一个系统支持的所有字符的集合,包括各国家文字,标点符号,图形符号,数字等
计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码.常见的字符集有ASCII
字符集,GBK
字符集,Unicode
字符集等
可见,当指定了编码,它所对应的字符集自然就指定了,所以编码菜式我们最终要关心的
2.2编码引出的问题
在IDEA中,使用FileReader
读取项目的文本文件,由于IDEA的设置,都是默认的UTF-8
编码,所以没有任何问题.但是,当读取Windows系统中创建的文本文件时,由于Windows系统默认的时GBK编码,就会出现乱码
public class Test1 {
public static void main(String[] args) throws IOException {
FileReader fileReader = new FileReader("a.txt");
int read;
while (((read = fileReader.read()) != -1)) {
System.out.println((char) read);
}
fileReader.close();
}
}
2.3 InputStream类
转换流java.io.InputStreamReader
,是Reader
的子类,是从字节流到字符流的桥梁.它读取字节,并使用指定的字符集将期解码为字符.它的字符集可以由名称指定,也可以接受平台的默认字符集
构造方法
InputStreamReader(InputStream in)
:创建一个使用默认字符集的字节流InputStreamReader(InputStream in,String charsetName)
:创建一个指定字符集的字符流
InputStreamReader isr = new InputStreamReader(new FileInputStream("in.txt"));
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("in.txt") , "GBK");
指定编码读取
public class Test1 {
public static void main(String[] args) throws IOException {
// 定义文件路径,文件为GBK编码
String fileName = "a.txt";
// 创建流对象,默认UTF-8编码
InputStreamReader isr = new InputStreamReader(new FileInputStream(fileName));
// 创建流对象.指定GBK编码
InputStreamReader isr2 = new InputStreamReader(new FileInputStream(fileName, "GBK"));
// 定义变量,保存字符
int read;
// 使用默认编码流读取,乱码
while (((read = isr.read()) != -1)) {
System.out.println((char) read);
}
isr.close();
// 使用指定编码字符流读取,正常解析
while (((read = isr2.read()) != -1)) {
System.out.println((char) read);
}
isr2.close();
}
}
2.4 OutputStreamWriter类
转换流java.io.OutputStreamWriter
,是Writer
的子类,是从字符流到字节流的桥梁.使用指定的字符集将字符编码为字节.它的字符集可以由名称指定,也可以接受平台的默认字符集
构造方法
OutputStreamWriter(OutputStream in)
:创建一个使用默认字符集的字符流OutputStreamWriter(OutputStream in,String charsetName)
创建一个指定字符集的字符流
OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("out.txt"));
OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("out.txt") , "GBK");
### 指定编码写出
public class Test1 {
public static void main(String[] args) throws IOException {
// 定义文件路径,
String fileName = "a.txt";
// 创建流对象,默认UTF-8编码
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(fileName));
// 写出数据
osw.write("你好");// 保存为6个字节
// 创建流对象.指定GBK编码
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(fileName),"GBK"));
// 写出数据
osw.write("你好");// 保存为4个字节
osw.close();
osw2.close();
}
}
3. 序列化
3.1 概述
Java提供了一种对象序列化的机制.用一个字节序列可以表示一个对象,该字节序列 包含该对象的数据,对象的类型和对象中存储的属性等信息,字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化,对象的数据,对象的类型和对象中存储的数据信息,都可以在内存中创建对象
3.2 ObjectOutputStream类
java.io.ObjectOutputStream
类,将Java对象的原始数据类型写出到文件,实现对象的持久存储
构造方法
public ObjectOutputStream(OutputStream out)
:创建一个指定OutputStream的ObjectOutputStream
FileOutputStream fileOut = new FileOutputStream("employee.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
序列化操作
一个对象要想序列化,必须满足两个条件
- 该类必须实现
java.io.Sereiazable
接口,Serializable
是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
- 该类的所有属性必须是可序列化的,如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用
transient
关键字修饰
public class Employee implements Serializable {
public String name;
public String address;
public transient int age;// transient瞬态修饰成员,不会被序列化
public void addredssCheck(){
System.out.println("Address check:"+name+"--"+address);
}
}
写出对象方法
public final void writerObject(Object obj)
:将指定的对象写出
public class Test1 {
public static void main(String[] args) throws IOException {
Employee e = new Employee();
e.name="张三";
e.address="北京";
e.age=20;
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("a.txt"));
// 写出对象
out.writeObject(e);
// 释放资源
out.close();
System.out.println("Serialized data is saved");// 姓名,地址被序列化,年龄没有被序列化
}
}
3.3 ObjectInputStream类
ObjectInputStream
反序列化类,将之前使用ObjectOutputStream
序列化的原始数据恢复为对象
构造方法
public ObjectInputStream(InputStream in)
:创建一个指定InputStream
的ObjectInputStream
反序列化操作1
入股能找到一个对象的class文件,我们可以进行反序列化操作,调用ObjectInputStream
读取对象的方法:
public final Object readObject()
:读取一个对象
public class Test1 {
public static void main(String[] args) throws IOException {
Employee e = null;
// 创建反序列化流
FileInputStream fileIn = new FileInputStream("a.txt");
ObjectInputStream in = new ObjectInputStream(fileIn);
// 读取一个对象
try {
e = (Employee) in.readObject();
} catch (ClassNotFoundException e1) {
e1.printStackTrace();
}
in.close();
System.out.println("Name:"+e.name); // 张三
System.out.println("Address:"+e.address); // 北京
System.out.println("age"+e.age);//0
}
}
对于JVM可以反序列化对象,它必须是能够找到class文件的类.如果找不到该类的class文件,则抛出一个ClassNotFoundException
异常
返回序列化操作2
另外,当JVM
反序列化对象时.能找到class文件.但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个InvalidClassException
异常.发生这个异常的原因如下:
- 该类的序列版本号从刘忠范读取的类描述的版本号不匹配
- 该类包含未知数据类型
- 该类没有可访问的无参数构造方法
Serializable
接口给需要列化的类,序列版本号
serialVersionUID
该版本号的目的在于验证序列化的对象和对应类是否版本匹配
public class Employee implements Serializable {
// 加入序列版本号
private static final long serialVersionUID = 1L;
public String name;
public String address;
// 添加新的属性,重新编译,可以反序列化,该属性赋为默认值
public int eid;
public transient int age;// transient瞬态修饰成员,不会被序列化
public void addredssCheck(){
System.out.println("Address check:"+name+"--"+address);
}
}
4. 打印流
4.1 概述
平时我们在控制台打印输出,是调用print
方法和println
方法完成的,这两个方法都来自java.io.PrintStream
类.该类能够方便打印各种数据类型的值,是一种便捷的输出方式
4.2 PrintStream类
构造方法
public PrintStream(String fileName)
:使用指定的文件名创建一个新的打印流
PrintStream ps= new PrintStream("ps.txt");
改变打印流向
System.out
就是PrintStream
类型的,只不过它的流向是系统规定的,打印在控制台上.不过,既然是流对象,我们可以改变它的打印流向
public class Test1 {
public static void main(String[] args) throws IOException {
// 调用系统的打印流,控制台直接输出97
System.out.println(97);
// 创建打印流,指定文件名称
PrintStream ps = new PrintStream("ps.txt");
// 设置系统的打印流向,输出到ps.txt
System.setOut(ps);
//调用系统的打印流,ps.txt输出97
}
}