目录
缓冲流
在基本的流对象基础之上创建而来的,就像穿上铠甲的武士一样,相当于是对基本流对象的一种增强。
1.1 概述
缓冲流,也叫高效流,是对4个基本的 FileXxx 流的增强,所以也是4个流,按照数据类型分类:
- 字节缓冲流: BufferedInputStream , BufferedOutputStream
- 字符缓冲流: BufferedReader , BufferedWriter
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO
次数,从而提高读写的效率。
1.2 字节缓冲流
构造方法
- public BufferedInputStream(InputStream in) :创建一个 新的缓冲输入流。
- public BufferedInputStream(InputStream in,int size) :创建一个指定大小的缓冲输入流。
- public BufferedOutputStream(OutputStream out) : 创建一个新的缓冲输出流。
- public BufferedOutputStream(OutputStream out,int size) : 创建一个指定大小的缓冲输出流。
使用步骤:
- 字节输出流使用步骤(重点)
1.创建FileOutputStream对象,构造方法中绑定要输出的目的地
2.创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象对象,提高FileOutputStream对象效率
3.使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中
4.使用BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中
5.释放资源(会先调用flush方法刷新数据,第4部可以省略)
- 字节输入流使用步骤(重点):
1.创建FileInputStream对象,构造方法中绑定要读取的数据源
2.创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
3.使用BufferedInputStream对象中的方法read,读取文件
4.释放资源
字节流缓冲流练习:文件复制
1. 使用read() 和 write() 每次读取一个字节
public static void main(String[] args) {
// 记录开始时间
long start = System.currentTimeMillis();
try(
// 1. 创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象对象,提高FileOutputStream对象效率
// 2. 创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("I://a.pdf"));
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D://a.pdf"));
)
{
// 3. 使用read()每一次读取一个字节
int len = 0;
while ((len=bis.read())!=-1){
// 4. 使用write() 每次写入一个字节
bos.write(len);
}
} catch (IOException e) {
e.printStackTrace();
}
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println(end-start);
}
所用时间:73ms
2. 使用数组的方式(自定义数组缓冲区)
public static void main(String[] args) {
// 记录开始时间
long start = System.currentTimeMillis();
try(
// 1. 创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象对象,提高FileOutputStream对象效率
// 2. 创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("I://a.pdf"));
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D://a.pdf"));
)
{
// 定义一个数组缓冲区
byte[] buff = new byte[1024];
// 3. 使用read(byte[] buff) 每次读取数组大小的字节放入数组缓冲区
int len = 0;
while ((len=bis.read(buff))!=-1){
// 4. 使用write(byte[] buff) //每次将数组大小的字节写入文件
bos.write(buff,0,len);
}
} catch (IOException e) {
e.printStackTrace();
}
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println(end-start);
}
所用时间:21ms
1.3 字符缓冲流
构造方法
- public BufferedReader(Reader in) :创建一个 新的缓冲输入流。
- public BufferedWriter(Writer out) : 创建一个新的缓冲输出流。
特有方法
字符缓冲流的基本方法与普通字符流调用方式一致,不再阐述,我们来看它们具备的特有方法。
- BufffferedReader: public String readLine() : 读一行文字。
- BufffferedWriter: public void newLine() : 写一行行分隔符,由系统属性定义符号。
readLine 方法演示,代码如下:
public static void main(String[] args) throws IOException {
// 1. 创建缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
// 2. 定义字符串,保存每次读取的一行数据
String line = null;
// 3. 使用readLine();读取一行数据
while ((line = br.readLine())!=null){
System.out.println(line);
}
// 4. 释放资源
br.close();
}
newLine 方法演示,代码如下:
public static void main(String[] args) throws IOException {
// 1. 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
// 2. 写入数据到文件,是用newLine();换行
for (int i = 0; i < 5; i++) {
bw.write("哈哈哈");
bw.newLine();
}
// 4. 释放资源
bw.close();
}
1.4 练习:文本排序
- 练习
对文本的内容进行排序,按照(1,2,3....)顺序排序
- 分析:
1.创建一个TreeMap集合对象,可以:存储每行文本的序号(1,2,3,..);value:存储每行的文本
2.创建字符缓冲输入流对象,构造方法中绑定字符输入流
3.创建字符缓冲输出流对象,构造方法中绑定字符输出流
4.使用字符缓冲输入流中的方法readline,逐行读取文本
5.对读取到的文本进行切割,获取行中的序号和文本内容
6.把切割好的序号和文本的内容存储到TreeMap集合中(key序号是有序的,会自动排序1,2,3,4..)
7.遍历TreeMap集合,获取每一个键值对
8.把每一个键值对,拼接为一个文本行
9.把拼接好的文本,使用字符缓冲输出流中的方法write,写入到文件中
10.释放资源
public static void main(String[] args) throws IOException {
// 1. 创建字符缓冲流
BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));
BufferedReader br = new BufferedReader(new FileReader("in.txt"));
// 2. 创建一个treemap 集合用来存取读取的行,key:行号,value:内容
TreeMap<String, String> map = new TreeMap<String,String>();
// 3. 读取一行内容,进行分割
String line = null;
while ((line = br.readLine())!=null ){
// 分割数据
String[] split = line.split("\\.");
// 将数据存入map 中
map.put(split[0], split[1]);
}
// 4. 遍历map 集合
for (String key:map.keySet()) {
String value = map.get(key);
// 5. 将key 和 value 进行拼接
String newLine = key+value;
// 6. 将拼接好的数据写入文件
bw.write(newLine);
bw.newLine();// 写入换行
}
// 关闭资源
bw.close();
br.close();
}
转换流
编码引出的问题
在IDEA中,使用 FileReader 读取项目中的文本文件。由于IDEA的设置,都是默认的 UTF-8 编码,所以没有任何
问题。但是,当读取Windows系统中创建的文本文件时,由于Windows系统的默认是GBK编码,就会出现乱码。
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("D://a.txt");
int len = 0;
while ((len= fr.read())!=-1){
System.out.print((char)len); // 结果:�?���ʡ�Ϸ�����˸��������ķ�
}
fr.close();
}
InputStreamReader类
转换流 java.io.InputStreamReader ,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定
的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集
构造方法
- InputStreamReader(InputStream in) : 创建一个使用默认字符集的字符流。
- InputStreamReader(InputStream in, String charsetName) : 创建一个指定字符集的字符流。
指定编码读取
/*
java.io.InputStreamReader extends Reader
InputStreamReader:是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。(解码:把看不懂的变成能看懂的)
使用步骤:
1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
2.使用InputStreamReader对象中的方法read读取文件
3.释放资源
注意事项:
构造方法中指定的编码表名称要和文件的编码相同,否则会发生乱码
*/
public class Demo2 {
public static void main(String[] args) throws IOException {
//read_gbk();
read_utf8();
}
/*
使用InputStreamReader读取GBK格式的文件
*/
private static void read_gbk() throws IOException {
//1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
InputStreamReader isr = new InputStreamReader(new FileInputStream("gbk_s.txt"), "gbk");
//2.使用InputStreamReader对象中的方法read读取文件
int len = 0;
while ((len = isr.read())!=-1){
System.out.print((char)len);
}
//3.释放资源
isr.close();
}
/*
使用InputStreamReader读取UTF-8格式的文件
*/
private static void read_utf8() throws IOException {
//1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称
InputStreamReader isr = new InputStreamReader(new FileInputStream("utf8_s.txt"), "utf-8");
//2.使用InputStreamReader对象中的方法read读取文件
int len = 0;
while ((len = isr.read())!=-1){
System.out.print((char)len);
}
//3.释放资源
isr.close();
}
}
OutputStreamWriter类
构造方法
- OutputStreamWriter(OutputStream in) : 创建一个使用默认字符集的字符流。
- OutputStreamWriter(OutputStream in, String charsetName) : 创建一个指定字符集的字符流。
指定编码写出
/*
java.io.OutputStreamWriter extends Writer
OutputStreamWriter: 是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。(编码:把能看懂的变成看不懂)
使用步骤:
1.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
2.使用OutputStreamWriter对象中的方法write,把字符转换为字节存储缓冲区中(编码)
3.使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程)
4.释放资源
*/
public class Demo3 {
public static void main(String[] args) throws IOException {
// write_gbk();
write_utf_8();
}
/*
使用转换流OutputStreamWriter 写utf-8格式的文件
* */
private static void write_gbk() throws IOException {
//1.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("gbk_s.txt"), "gbk");
//2.使用OutputStreamWriter对象中的方法write,把字符转换为字节存储缓冲区中(编码)
osw.write("我喜欢你");
// 3.使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程)
osw.flush();
// 4. 关闭资源
osw.close();
}
/*
使用转换流OutputStreamWriter 写GBK格式的文件
* */
private static void write_utf_8() throws IOException {
//1.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("utf8_s.txt"), "utf-8");
//2.使用OutputStreamWriter对象中的方法write,把字符转换为字节存储缓冲区中(编码)
osw.write("我喜欢你");
// 3.使用OutputStreamWriter对象中的方法flush,把内存缓冲区中的字节刷新到文件中(使用字节流写字节的过程)
osw.flush();
// 4. 关闭资源
osw.close();
}
}
转换流理解图解
练习:转换文件编码
将GBK编码的文本文件,转换为UTF-8编码的文本文件。
public static void main(String[] args) throws IOException {
// 1. 创建转换流
InputStreamReader isr = new InputStreamReader(new FileInputStream("gbk_s.txt"), "gbk");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("utf-8.txt"),"utf-8");
// 2. 创建数组缓冲区
char[] buff = new char[1024];
// 3. 读取文件
int len = 0;
while ((len=isr.read(buff))!=-1){
// 4. 写入文件
osw.write(buff,0,len);
}
// 5. 关闭流
osw.close();
isr.close();
}
序列化
概述
Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该 对象的数据 、 对象的
类型 和 对象中存储的属性 等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。 对象的数据 、 对象的类型 和 对象中
存储的数据 信息,都可以用来在内存中创建对象。
ObjectOutputStream类
java.io.ObjectOutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。
构造方法
- public ObjectOutputStream(OutputStream out) : 创建一个指定OutputStream的ObjectOutputStream。
特有方法
- public final void writeObject(Object obj):将指定的对象写入 ObjectOutputStream
序列化操作
1. 一个对象要想序列化,必须满足两个条件:
- 该类必须实现 java.io.Serializable 接口, Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出 NotSerializableException 。
- 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用 transient 关键字修饰。
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
private transient String addr; // 不可被序列化
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;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", addr='" + addr + '\'' +
'}';
}
}
2.写出对象方法
- public final void writeObject (Object obj) : 将指定的对象写出。
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
/**
java.io.ObjectOutputStream extends OutputStream
ObjectOutputStream:对象的序列化流
作用:把对象以流的方式写入到文件中保存
使用步骤:
1.创建ObjectOutputStream对象,构造方法中传递字节输出流
2.使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中
3.释放资源
*/
public class Demo1 {
public static void main(String[] args) throws IOException {
// 创建person对象
Person p = new Person();
p.setName("小明");
p.setAge(20);
p.setAddr("beijing");
//1.创建ObjectOutputStream对象,构造方法中传递字节输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
//2.使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中
oos.writeObject(p);
//3.释放资源
oos.close();
}
}
ObjectInputStream类
ObjectInputStream反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。
构造方法
- public ObjectInputStream(InputStream in) : 创建一个指定InputStream的ObjectInputStream。
反序列化操作1
如果能找到一个对象的class文件,我们可以进行反序列化操作,调用 ObjectInputStream 读取对象的方法:
- public final Object readObject () : 读取一个对象。
import java.io.*;
/*
java.io.ObjectInputStream extends InputStream
ObjectInputStream:对象的反序列化流
作用:把文件中保存的对象,以流的方式读取出来使用
使用步骤:
1.创建ObjectInputStream对象,构造方法中传递字节输入流
2.使用ObjectInputStream对象中的方法readObject读取保存对象的文件
3.释放资源
4.使用读取出来的对象(打印)
readObject方法声明抛出了ClassNotFoundException(class文件找不到异常)
当不存在对象的class文件时抛出此异常
反序列化的前提:
1.类必须实现Serializable
2.必须存在类对应的class文件
*/
public class Demo2 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 1.创建ObjectInputStream对象,构造方法中传递字节输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.txt"));
// 2.使用ObjectInputStream对象中的方法readObject读取保存对象的文件
Object obj = ois.readObject();
// 3.释放资源
ois.close();
// 4.使用读取出来的对象(打印)
System.out.println(obj); // Person{name='小明', age=20, addr='null'} addr 没有被序列化
}
}
反序列化操作2
另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操 作也会失败,抛出一个 InvalidClassException 异常。发生这个异常的原因如下:
- 该类的序列版本号与从流中读取的类描述符的版本号不匹配
- 该类包含未知数据类型
- 该类没有可访问的无参数构造方法
Serializable 接口给需要序列化的类,提供了一个序列版本号。 serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
public class Person implements Serializable {
// 加入序列版本号
private static final long serialVersionUID = 11L;
private String name;
public int age;
private transient String addr; // 不可被序列化
...
}
练习:序列化集合
1. 将存有多个自定义对象的集合序列化操作,保存到 list.txt 文件中。
2. 反序列化 list.txt ,并遍历集合,打印对象信息。
案例分析
1. 把若干学生对象 ,保存到集合中。
2. 把集合序列化。
3. 反序列化读取时,只需要读取一次,转换为集合类型。
4. 遍历集合,可以打印所有的学生信息
案例实现
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//1. 把若干学生对象 ,保存到集合中。
ArrayList<Person> list = new ArrayList<>();
Person p1 = new Person("lisi1",20,"beijing");
Person p2 = new Person("lisi2",40,"shanghai");
Person p3 = new Person("lisi3",30,"xian");
list.add(p1);
list.add(p2);
list.add(p3);
//2. 把集合序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("list.txt"));
oos.writeObject(list);
//3. 反序列化读取时,只需要读取一次,转换为集合类型。
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("list.txt"));
ArrayList<Person> obj = (ArrayList)ois.readObject();
//4. 遍历集合,可以打印所有的学生信息
for (Person p : obj){
System.out.println(p);
}
}
}
打印流
概述
平时我们在控制台打印输出,是调用 print 方法和 println 方法完成的,这两个方法都来自于 java.io.PrintStream 类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。
PrintStream类
构造方法
- public PrintStream(String fileName) : 使用指定的文件名创建一个新的打印流。
- public PrintStream(File file):输出的目的地是一个文件
- public PrintStream(OutputStream out):输出的目的地是一个字节输出流
特有方法
- public void print(任意类型的值)
- public void println(任意类型的值并换行)
import java.io.FileNotFoundException;
import java.io.PrintStream;
/*
java.io.PrintStream:打印流
PrintStream 为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。
PrintStream特点:
1.只负责数据的输出,不负责数据的读取
2.与其他输出流不同,PrintStream 永远不会抛出 IOException
3.有特有的方法,print,println
void print(任意类型的值)
void println(任意类型的值并换行)
注意:
如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表 97->a
如果使用自己特有的方法print/println方法写数据,写的数据原样输出 97->97
*/
public class Demo1 {
public static void main(String[] args) throws FileNotFoundException {
//创建打印流PrintStream对象,构造方法中绑定要输出的目的地
PrintStream ps = new PrintStream("ss.txt");
//如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表 97->a
ps.write(97);
//如果使用自己特有的方法print/println方法写数据,写的数据原样输出 97->97
ps.println(97);
// 释放资源
ps.close();
// 结果 : a97
}
}
改变打印流向
System.out 就是 PrintStream 类型的,只不过它的流向是系统规定的,打印在控制台上。不过,既然是流对象,
我们就可以玩一个"小把戏",改变它的流向。
public static void main(String[] args) throws FileNotFoundException {
// 调用系统的打印流,控制台直接输出97
System.out.println(97);
// 创建打印流指定文件名称
PrintStream ps = new PrintStream("ps.txt");
// 设置系统打印流的流向,输出到ps.txt
System.setOut(ps);
// 调用系统的打印流,ps.txt中输出97
System.out.println(97);
}