【缓冲流、转换流、序列化流】
缓冲流
缓冲流,也叫高效流,是对4个基本的FileXxx
流的增强,所以也是4个流,按照数据类型分为:
- 字节缓冲流:
BufferedInputStream
、BufferedOutputStream
- 字符缓冲流:
BufferedReader
、BufferedWriter
缓冲流的基本原理:是在创建对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区的读写,减少系统的IO次数,从而提高读写的效率。
字节缓冲流
字节缓冲输出流
java.io.BufferedOutputStream extends OutputStream
:字节缓冲输出流。
继承来自父类的共性方法:close()
、flush()
、write(byte[] b)
、write(byte[] b,int off,int len)
、write(int b)
。
构造方法 | 含义 |
---|---|
public BufferedOutputStream(OutputStream out) | 创建一个新的字节缓冲输出流,以将数据写入指定的底层输出流 |
public BufferedOutputStream(OutputStream out,int size) | 创建一个新的字节缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流 |
使用步骤【重点】
- 创建一个字节输出流(以FileOutputStream为例),构造方法中绑定要输出的目的地;
- 创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FileOutputStream对象效率;
- 使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中;
- 使用BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中;
- 释放资源(会先调用flush方法,刷新数据,第四步可以省略)
代码实现:
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Demo01BufferedOutputStream {
public static void main(String[] args) throws IOException {
//创建一个字节输出流(以FileOutputStream为例),构造方法中绑定要输出的目的地
//创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象,提高FileOutputStream对象效率
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("C:\\Users\\Ann\\Desktop\\d.txt"));
//使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中;
bos.write("把数据写入到内部缓冲区中".getBytes());
//使用BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中
bos.flush();
//释放资源
bos.close();
}
}
字节缓冲输入流
java.io.BufferedInputStream extends InputStream
:字节缓冲输入流。
继承来自父类的共性方法:close()
、``read(byte[] b)、
read()`。
构造方法 | 含义 |
---|---|
public BufferedInputStream(InputStream in) | 创建一个新的字节缓冲输入流并保存其参数,即输入流in,以便将来使用 |
public BufferedInputStream(InputStream in,int size) | 创建一个新的具有指定缓冲区大小的字节缓冲输入流并保存其参数,即输入流in,以便将来使用 |
使用步骤【重点】
- 创建一个字节输入流(以FileInputStream为例),构造方法中绑定要读取的数据源;
- 创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象效率;
- 使用BufferedInputStream对象中的方法read,读取文件;
- 释放资源
代码实现:
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class Demo01BufferedInputStream {
public static void main(String[] args) throws IOException {
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("C:\\Users\\Ann\\Desktop\\d.txt"));
byte[] bytes = new byte[1024];
int len = 0;
while((len=bis.read(bytes)) != -1){
System.out.println(new String(bytes,0,len));
}
bis.close();
}
}
文件复制优化
import java.io.*;
public interface Demo01Test {
public static void main(String[] args) {
long start = System.currentTimeMillis();
try (FileOutputStream fos = new FileOutputStream("C:/Users/Ann/Desktop/d1.docx");
FileInputStream fis = new FileInputStream("C:/Users/Ann/Desktop/d.docx")) {
byte[] bytes = new byte[1024];
int len = 0;
while ((len = fis.read(bytes)) != -1) {
fos.write(bytes, 0, len);
}
} catch (IOException e) {
System.out.println(e);
}
long end = System.currentTimeMillis();
System.out.println("不用缓冲流复制文件耗时:" + (end - start) + "豪秒");
start = System.currentTimeMillis();
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("C:/Users/Ann/Desktop/d2.docx"));
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("C:/Users/Ann/Desktop/d.docx"))) {
byte[] bytes = new byte[1024];
int len = 0;
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
} catch (IOException e) {
System.out.println(e);
}
end = System.currentTimeMillis();
System.out.println("使用缓冲流复制文件耗时:" + (end - start) + "豪秒");
}
}
字符缓冲流
字符缓冲输出流
java.io.BufferedWriter extends Writer
:字符缓冲输出流。
继承来自父类的共性方法:close()
、flush()
、write(char[] cbuf)
、write(char[] cbuf,int off,int len)
、write(int c)
、write(String str)
、write(String str,int off,int len)
。
构造方法 | 含义 |
---|---|
BufferedWriter(Writer out) | 创建一个使用默认大小输出缓冲区的缓冲字符输出流 |
BufferedWriter(Writer out,int size) | 创建一个使用给定大小输出缓冲区的缓冲字符输出流 |
特有的成员方法:
方法 | 含义 |
---|---|
void newLine() | 写入一个行分隔符。会根据不同的操作系统,获取不同的分隔符 |
使用步骤【重点】:
- 创建字符缓冲输出流对象,构造方法中传递字符输出流;
- 调用字符缓冲输出流中得到方法write,把数据写入内存缓冲区中;
- 调用字符缓冲输出流得到方法flush。把内存缓冲区中的数据,刷新到文件中;
- 释放资源。
代码实现:
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class Demo02BufferedWriter {
public static void main(String[] args) throws IOException {
//创建字符缓冲输出流对象,构造方法中传递字符输出流;
BufferedWriter bw = new BufferedWriter(new FileWriter("C:/Users/Ann/Desktop//d.txt"));
//调用字符缓冲输出流中得到方法write,把数据写入内存缓冲区中;
for (int i = 0; i < 10; i++) {
bw.write("测试!");
bw.newLine();
}
//调用字符缓冲输出流得到方法flush。把内存缓冲区中的数据,刷新到文件中;
bw.flush();
//释放资源。
bw.close();
}
}
字符缓冲输入流
java.io.BufferedReader extends Reader
:字符缓冲输入流。
继承来自父类的共性方法:close()
、``read(char[] cbuf)、
read()`。
构造方法 | 含义 |
---|---|
BufferedReader(Reader in) | 创建一个使用默认大小输入缓冲区的缓冲字符输入流 |
BufferedReader(Reader in,int size) | 创建一个使用给定大小输入缓冲区的缓冲字符输入流 |
特有的成员方法:
方法 | 含义 |
---|---|
String readLine() | 读取一个文本行,读取一行数据 |
注意,该方法的返回值为:
包含该行内容的字符串,不包含任何行终止符,如果已达到流末尾,则返回null。
使用步骤【重点】:
- 创建字符缓冲输入流对象,构造方法中传递字符输入流;
- 使用字符缓冲输入流对象的方法read/readLine读取文本;
- 释放资源。
代码实现:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class Demo02BufferedReader {
public static void main(String[] args) throws IOException {
//创建字符缓冲输入流对象,构造方法中传递字符输入流;
BufferedReader br = new BufferedReader(new FileReader("C:/Users/Ann/Desktop/d.txt"));
//使用字符缓冲输入流对象的方法read/readLine读取文本;
String str = null;
while((str = br.readLine())!=null){
System.out.print(str);
}
//释放资源。
br.close();
}
}
练习:文本排序
请对文本信息恢复顺序:
2.千里江陵一日还。
4.轻舟已过万重山。
1.朝辞白帝彩云间;
3.两岸猿声啼不住;
文本路径为:C:\Users\Ann\Desktop\javaLearning\poem.txt
代码实现:
import java.io.*;
import java.util.HashMap;
import java.util.Set;
public class Demo02Test {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("C:\\Users\\Ann\\Desktop\\javaLearning\\poem.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("C:\\Users\\Ann\\Desktop\\javaLearning\\poem.txt", true));
HashMap<String, String> map = new HashMap<>();
String s;
while ((s = br.readLine()) != null) {
String[] split = s.split("\\.");
map.put(split[0], split[1]);
}
Set<String> keys = map.keySet();
for (String key : keys) {
bw.newLine();
bw.write(key+"."+map.get(key));
}
br.close();
bw.close();
}
}
转换流
字符编码和字符集
计算机中储存得到信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉子等字符都是二进制转换之后的结果。
**编码:**字符(能看懂的)–>字节(看不懂的)
**解码:**字节(看不懂的)–>字符(能看懂的)
- 字符编码
Character Encoding
:就是一套自然语言的字符与二进制数之间的对应规则。 - 字符集
Charset
:也叫编码表。是一个系统支持的所有字符的集合,包括个国家文字、标点符号、图形符号、数字等。
计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见的字符集有ASCII字符集、GBK字符集、Unicode字符集等。
可见,当指定了编码,它所对应的字符集就自然指定了。所以编码才是我们最终关心的。
编码引出的问题
FileReader可以读取IDE默认编码格式(UTF-8)的文件,没有问题。但是如果让FileReader读取系统默认编码(中文GBK)会产生乱码。那么如何阅读GBK编码的文件呢?这时候就需要用到转换流。
OutputStreamWriter
java.io.OutputStreamWriter extends Writer
:是字符流通向字节流的桥梁,可使用指定的字符集Charset
将要写入流中的字符编码成字节。
构造方法 | 含义 |
---|---|
OutputStreamWriter(OutputStream out) | 创建使用默认字符编码的OutputStreamWriter对象 |
OutputStreamWriter(OutputStream out,String charsetName) | 创建使用给定字符编码的OutputStreamWriter对象 |
注意:指定的编码表名称,不区分大小写,可以是utf-8/UTF-8…等等,不指定默认使用utf-8。
使用步骤:
- 创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称;
- 使用OutputStreamWriter对象中的方法write,把字符转换为字节存储到缓冲区中;
- 使用OutputStreamWriter对象中的方法flush,吧内存缓冲区中的字节刷新到文件中(使用字节输出流写出字节的过程);
- 释放资源。
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class Demo03OutputStreamWriter {
public static void main(String[] args) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("C:\\Users\\Ann\\Desktop\\c.txt"));
osw.write("你好");
osw.flush();
osw.close();
}
}
InputStreamReader
java.io.InputStreamReader extends Reader
:是字节流通向字符流的桥梁:它使用指定的charset
读取字节并将其解码为字符。
构造方法 | 含义 |
---|---|
InputStreamReader(InputStream in) | 创建一个使用默认字符集的InputStreamReader对象 |
InputStreamReader(InputStream in,String charsetName) | 创建一个使用指定字符集的InputStreamReader对象 |
使用步骤:
- 创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称;
- 使用InputStreamReader对象中的方法read读取文件;
- 释放资源。
注意事项:构造方法中指定的编码表名称要和文件的编码相同,否则会发生乱码。
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class Demo03InputStreamReader {
public static void main(String[] args) throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("C:\\Users\\Ann\\Desktop\\c.txt"),"utf-8");
int len = 0;
char[] chars = new char[1024];
while((len = isr.read(chars))!=-1){
System.out.println(new String(chars,0,len));
}
isr.close();
}
}
练习:转换文件编码
import java.io.*;
public class Demo03InputStreamReader {
public static void main(String[] args) throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("C:\\Users\\Ann\\Desktop\\c.txt"),"utf-8");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("C:\\Users\\Ann\\Desktop\\c1.txt"),"gbk");
int len = 0;
char[] chars = new char[1024];
while((len = isr.read(chars))!=-1){
osw.write(chars,0,len);
}
isr.close();
osw.close();
}
}
序列化
序列化流只指操作对象的流(读写一个对象)
序列化流: 写对象 ObjectOutputStream
反序列化流: 读对象 ObjectInputStream
ObjectOutputStream
java.io.ObjectOutputStream extends OutputStream
类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。
构造方法 | 含义 |
---|---|
ObjectOutputStream(OutputStream out) | 创键一个指定OutputStream对象的ObjectOutputStream对象 |
特有的成员方法:
方法 | 含义 |
---|---|
void writeObject(Object obj) | 将指定的对象写入ObjectOutputStream |
使用步骤:
- 创建ObjectOutputStream对象,构造方法中传递字节输出流;
- 使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中;
- 资源释放。
序列化操作
直接对一般对象进行序列化或反序列化操作时,会抛出NotSerializableException
异常。
一个对象想要完成序列化,必须满足两个条件:
- 该类必须实现
java.io.Serializable
接口。Serializable
是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
. - 该类的所有属性必须是可序列化的,如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用
transient
关键字修饰。
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Demo04ObjectOutputStream {
public static void main(String[] args) throws IOException {
//创建ObjectOutputStream对象,构造方法中传递字节输出流;
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Ann\\Desktop\\e.txt"));
//使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中;
Person person = new Person("小美",18);
oos.writeObject(person);
//资源释放。
oos.close();
}
//创建一个静态内部类Person实现Serializable接口
static class Person implements Serializable{...}
}
}
ObjectInputStream
java.io.ObjectInputStream extends InputStream
类,将文件中保存的对象,以流的方式读取出来使用。
构造方法 | 含义 |
---|---|
ObjectInputStream(InputStream in) | 创键一个指定InputStream对象的ObjectInputStream对象 |
特有的成员方法:
方法 | 含义 |
---|---|
Object readObject() | 从ObjectInputStream中读取对象 |
使用步骤:
- 创建ObjectInputStream对象,构造方法中传递字节输入流;
- 使用ObjectInputStream对象中的方法readObject,读取保存对象的文件;
- 资源释放;
- 使用读取出来的对象(打印)。
import java.io.*;
public class Demo04ObjectOutputStream {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//创建ObjectInputStream对象,构造方法中传递字节输入流;
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Ann\\Desktop\\e.txt"));
//使使用ObjectInputStream对象中的方法readObject,读取保存对象的文件;
Object object = ois.readObject();
//资源释放;
ois.close();
//使用读取出来的对象(打印)。
if (object instanceof Person){
Person person = (Person)object;
System.out.println(person.getName()+":"+person.getAge());
}
}
//创建一个静态内部类Person实现Serializable接口
static class Person implements Serializable{...}
}
注意:反序列化时,不仅该对象需要实现
Serializable
接口,调用readObject
方法时还需处理ClassNotFoundException
异常。
transient关键字_瞬态关键字
此前,我们学过static关键字(静态关键字)。静态优先于非静态加载到内存中(静态优先于对象进入内存中)。所以,被static修饰的成员变量不能被序列化,序列化的都是对象。
在程序中,如果只想让成员变量不被序列化,而变为静态成员变量,可以使用transient关键字。
InvalidClassException【了解】
序列化运行时将每个可序列化类与版本号相关联,称为serialVersionUID。
在反序列化期间使用该版本号来验证序列化对象的发送方和接收方是否已加载与该序列化兼容的该对象的类。 如果接收者已经为具有与相应发送者类别不同的serialVersionUID的对象加载了类,则反序列化将导致InvalidClassException
。
可序列化类可以通过声明名为"serialVersionUID"
必须为static
,final
和long
类型的字段来显式声明其自己的serialVersionUID:
static final long serialVersionUID = 42L;
练习:序列化集合
当我们想在文件中保存多个对象的时候,可以把多个对象存储到一个集合中,对集合进行序列化和反序列化。
分析:
- 定义一个存储Person对象的ArrayList集合;
- 往ArrayList集合中存储Person对象;
- 创建一个序列化流ObjectOutputStream对象;
- 使用ObjectOutputStream对象中的方法writeObject方法,对集合进行序列化;
- 创建一个反序列化流ObjectInputStream对象;
- 使用ObjectInputStream对象中的readObject方法,读取文件中保存的集合;
- 把Object类转化为ArrayList类型;
- 遍历ArrayList集合;
- 释放资源。
import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
public class Demo04SerializableList {
public static void main(String[] args) throws IOException, ClassNotFoundException {
write();
read();
}
public static void read() throws IOException, ClassNotFoundException {
//创建一个反序列化流ObjectInputStream对象;
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Ann\\Desktop\\e.txt"));
//使用ObjectInputStream对象中的readObject方法,读取文件中保存的集合;
Object obj = ois.readObject();
//把Object类转化为ArrayList类型
ArrayList<Person> list = (ArrayList<Person>) obj;
//遍历ArrayList集合
for (Person person : list) {
System.out.println(person.getName()+"的球衣号码是:"+person.getAge());
}
//资源释放
ois.close();
}
public static void write() throws IOException {
//定义一个存储Person对象的ArrayList集合
ArrayList<Person> list = new ArrayList<>();
//往ArrayList集合中存储Person对象
Collections.addAll(list, new Person("James", 23), new Person("Kobe", 24), new Person("Durant", 35));
//创建一个序列化流ObjectOutputStream对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Ann\\Desktop\\e.txt"));
//使用ObjectOutputStream对象中的方法writeObject方法,对集合进行序列化
oos.writeObject(list);
//释放资源
oos.close();
}
}
打印流
java.io.PrintStream extends OutputStream
:打印流。
作用:PrintStream
为其他输出流添加了功能,使它们能够方便地打印出各种数据值表示形式。
特点:
- 只负责数据的输出,不负责数据的读取;
- 与其他的输出流,
PrintStream
永远不会抛出IOException
; - 有特有的方法,
print
、println
等:void print(任意类型的值);
void println(任意类型的值并换行)
构造方法:
构造方法 | 含义 |
---|---|
PrintStream(File file) | 输出的目的地是一个文件 |
PrintStream(OutputStream out) | 输出的目的地是一个字节输出流 |
PrintStream(String fileName) | 输出的目的地是一个文件路径 |
注意:
- 如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表
- 如果使用自己特有的方法print/println写数据,写的数据原样输出
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
public class Demo04PrintStream {
public static void main(String[] args) throws IOException {
//创建一个PrintStream对象
PrintStream ps = new PrintStream(new FileOutputStream("C:\\Users\\Ann\\Desktop\\f.txt", true));
//使用继承自OutputStream的方法write写出数据
ps.write("继承自OutputStream的方法write".getBytes());
//使用自己特有的方法print/println写出数据
ps.println("自己特有的方法println");
ps.print("自己特有的方法print");
//释放资源
ps.close();
}
}
修改打印流的流向【了解】:
public class TestPrintStreamDemo02 {
public static void main(String[] args) throws Exception {
//打印流实际上我们基础班第一天就学了
System.out.println("java");
//修改打印流的方向
PrintStream ps = new PrintStream("1.txt");
//System.out = ps; //修改打印流的对象,因为out是final修饰的,已经赋值一次了,赋值打印目标是控制台
System.setOut(ps);//修改打印流的对象,因为setOut底层调用c语言
//再次打印数据
System.out.println("php");
System.out.println("python");
System.out.println("c#");
System.out.println("c++");
System.out.println("c");
}
}