复习
1.理解IO流
IO流:不同设备间的数据的传输。
2.IO流分类
按照数据流向:
输入流:站在内存的角度看,读取数据
输出流:站在内存的角度看,写出数据
按照操作数据类型:
字节流:音频、视频、图片等二进制文件(万能)
字符流:纯文本数据
总结:
学习流?
1.分清楚要做输入还是输出操作?
2.选择字节流还是字符流?
流对象基本上都是成对出现的,有输入必然有输出
字节流: 设备名+父类名
字符流: 设备名+父类名
输入:read() 输出:write()
3.字节流
根类:
InputStream OutputStream
子类:
FileInputStream FileOutputStream
BufferedInputStream BufferedOutputStream
4.字符流
本质:字节流+编码表
根类:
Reader Writer
子类:
FileWriter
5.流异常处理
try(
流对象的创建
){
流的读或写操作
}catch(流异常类型名 变量名){
异常处理代码
}
课程
一.字符流
(一) 字符输入流
-
字符流输入流介绍:
Reader: 用于读取字符流的抽象父类
FileReader: 用于读取字符流的常用子类 -
FileReader构造方法
1)FileReader(File file): 在给定从中读取数据的 File 的情况下创建一个新 FileReader
2)FileReader(String fileName): 在给定从中读取数据的文件名的情况下创建一个新 FileReader -
读的方法:
1)int read(): 一次读一个字符数据,返回值结果是int类型, 返回的就是这个字符在编码表中对应的整数结果,如果读到-1,证明文件读取完毕
2)int read(char[] ch): 一次最多读一个字符数组数据, 将读取出的字符放置在参数ch数组中, 返回值结果int类型, 表示每次从文件中读取到的字符的个数, 如果读取到-1,证明文件读取完毕
import java.io.FileReader;
import java.io.IOException;
public class Demo1 {
public static void main(String[] args) throws IOException {
//创建字符输入流,并指定数据源
FileReader fr = new FileReader("abc.txt");
//读取数据,一个字符一个字符读取,读取到文件末尾返回-1.
/*int ch = fr.read();
while(ch != -1) {
System.out.print((char) ch);
ch = fr.read();
}*/
//读取数据,采用数组方式。返回的是读取的字符数,读取到末尾返回-1.读取的内容存储到数组中。
//数组的长度设定:1024的倍数
char[] chf = new char[1024];
int num = fr.read(chf);
while(num != -1) {
System.out.println(new String(chf,0,num));
num = fr.read(chf);
}
fr.close();
}
}
/*
* 字符输入流:
* Reader:
* 抽象类,是根类
* read():读取一个字符,读取到末尾返回-1.
* read(char[] ch) :读取的内存存储到ch中,返回读取的字符个数,读取到末尾返回-1
* read(char[] ch ,int index, int count) :读取的内存存储到ch一部分中,返回读取的字符个数,读取到末尾返回-1
* close():关闭流
* 子类:
* FileReader
* 构造方法: 必须指定数据源
* FileReader(File/String filename)
* */
import java.io.FileReader;
import java.io.IOException;
public class Demo2 {
public static void main(String[] args) throws IOException {
//需求:读取当前java文件
FileReader fr = new FileReader("E:\\0802java\\javase0802\\day18\\src\\Demo2.java");
/*int ch = 0;
while((ch = fr.read()) != -1){
System.out.print((char)ch);
}*/
int num = 0;
char[] cha = new char[512];
while((num = fr.read(cha)) != -1){
System.out.print(new String(cha,0,num));
}
fr.close();
}
}
(二) 字符流的拷贝
-
字符流复制结论:
1)字符流,可以复制纯文本文件, 但是效率不高, 因此对于文件的复制,通常采用字节流
2)字符流,不能复制非纯文本文件 -
字符流复制纯文本效率低原理:
-
字符流不能复制非纯文本文件原理:
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Demo3 {
public static void main(String[] args) throws IOException {
//复制文本文件 先读后写
FileReader fr = new FileReader("E:\\0802java\\javase0802\\day18\\src\\Demo3.java");
FileWriter fw = new FileWriter("a.txt");
/* int ch = 0;
while((ch = fr.read()) != -1){
fw.write(ch);
}*/
char[] chf = new char[1024];
int len = 0;
while((len = fr.read(chf)) != -1){
fw.write(chf,0,len);
}
//关闭流
fw.close();
fr.close();
}
}
(三) 字符缓冲流
1、字符缓冲流介绍:
(1) BufferedWriter:将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途。
(2) BufferedReader:从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。 默认值足够大,可用于大多数用途。
2、构造方法
BufferedWriter(Writer out): 创建字符缓冲输出流对象
BufferedReader(Reader in) : 创建字符缓冲输入流对象
3、高效的原因:
BufferedReader:每次调用read方法,第一次从磁盘中读取了默认数组缓冲大小个字符,存储到该类型对象的缓冲区数组中,将其中一个返回给调用者,再次调用read方法时,就不需要再访问磁盘,直接从缓冲区中拿一个出来即可,效率提升了很多。
BufferedWriter:每次调用write方法,不会直接将字符刷新到文件中,而是存储到字符数组中,等字符数组写满了或者flush刷新缓冲区,才一次性刷新到文件中,减少了和磁盘交互的次数,提升了效率
(四) 高效缓冲字符流的特有方法
1、BufferedReader:
readLine():可以从输入流中,一次读取一行数据,返回一个字符串,如果到达文件末尾,则返回null
2、BufferedWriter:
newLine():写一行 行分隔符.
import java.io.*;
public class Demo4 {
public static void main(String[] args) throws IOException {
/* FileWriter fw = new FileWriter("a.txt");
BufferedWriter bw = new BufferedWriter(fw);
bw.write("hello ");
bw.newLine();
bw.write("world");
//bw.flush();
bw.close();*/
FileReader fr = new FileReader("a.txt");
BufferedReader br = new BufferedReader(fr);
String line = br.readLine();
while(line != null) {
System.out.print(line);
line = br.readLine();
}
br.close();
}
}
/*
* 字符缓冲流:
* BufferedReader
* 高效读取 : 字符、数组、行
* 本质:从内部维护的缓冲区中读取数据
* 新增方法:
* readLine():读取一行文本,返回一行文本内容,但不包含行终止符,读取到末尾返回null
* BufferedWriter
* 高效写入 字符、字符数组、字符串数据
* 本质:也是将数据写到缓冲区,需要刷新到目的地
* 新增方法:
* newLine(): 跨平台的换行符
* */
import java.io.*;
public class Demo5 {
public static void main(String[] args) throws IOException {
//使用缓冲区,复制一个java文件
BufferedReader br = new BufferedReader(new FileReader("E:\\0802java\\javase0802\\day18\\src\\Demo5.java"));
BufferedWriter bw = new BufferedWriter(new FileWriter("1.txt"));
String line = null;
while((line = br.readLine()) != null){
bw.write(line);
bw.newLine();
}
bw.close();
br.close();
}
}
练习
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
public class Demo6 {
public static void main(String[] args) throws IOException {
/*有这样一个文件,格式如下:
张三
男
20
李四
女
30
....
读取该文件中的数据,将数据存储到Person对象中。
操作对象,将对象存储到集合中,将对象的属性信息输出打印。
*/
BufferedReader br = new BufferedReader(new FileReader("user.txt"));
//用于存储文件中的每行数据
ArrayList<String> list = new ArrayList<>();
String line = null;
while((line = br.readLine()) != null){
list.add(line.trim());
}
//用于存储person对象
ArrayList<Person> listPer = new ArrayList<>();
for (int i = 0 ;i < list.size()/3; i++){
String name = list.get(3*i+0);
char gender = list.get(3*i+1).charAt(0);
int age = Integer.parseInt(list.get(3*i+2));
Person p = new Person(name,gender,age);
listPer.add(p);
}
//遍历listPer
for (Person per : listPer)
System.out.println(per);
}
}
class Person{
private String name;
private char gender;
private int age;
public Person(String name, char gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", gender=" + gender +
", age=" + age +
'}';
}
}
二. 转换流
(一) 使用转换流的原因
针对于不同编码表的文件, 通过字符流进行读写会发生乱码问题, 为了正确文本文件读写过程, 使用转换流
案例: 将GBK编码的文本文件内容, 复制到UTF-8编码的文件中
(二) 转换流的使用
1、OutputStreamWriter:Writer的子类, 字符流到字节流的桥梁,可以指定编码形式
构造方法:OutputStreamWriter(OutputStream os, String charSetName)
创建一个转换流对象,可以把将来方法中接收到的字符,通过指定的编码表charSetName,编码成字节信息,再通过指定的字节流os,将字节信息写出
2、InputStreamReader:Reader的子类, 字节流到字符流的桥梁,可以指定编码形式
构造方法:InputStreamReader(InputStream is, String charSetName)
创建一个转换流对象,可以使用is这个指定的字节流,从磁盘中读取字节信息,通过指定的编码表charSetName,将字节信息解码成字符信息,返回给调用者
3、说明:
无论是读取的时候,还是写出的时候,都需要参考读取文件和目标文件的编码形式
读取源文件时,解码的形式必须和源文件的编码形式一致
写出到目标文件时,编码形式必须和目标文件的编码形式一致
```java
import java.io.*;
public class Demo7 {
public static void main(String[] args) throws IOException {
/* BufferedReader br = new BufferedReader( new FileReader("a.txt"));
String line = br.readLine();
System.out.println(line);
br.close();*/
//向文件中写入数据,指定编码格式为 gbk
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("b.txt"),"gbk");
osw.write("你好");
osw.close();
//读取指定编码格式的文件
InputStreamReader isr = new InputStreamReader(new FileInputStream("b.txt"),"gbk");
BufferedReader br = new BufferedReader(isr);
String line = br.readLine();
System.out.println(line);
br.close();
}
}
/*
* 转换流:
* 是字符流,是Reader和Writer的子类。
* InputStreamReader: 1.字节输入流转为字符输入流 2.可以指定编码表
* OutputStreamWriter: 1.字节输出流转为字符输出流 2.可以指定编码表
* */
- 转换流的实现原理如下图:
import java.io.*;
import java.util.Scanner;
public class Demo8 {
public static void main(String[] args) throws IOException {
//数据源:文件
//此时的数据源是:键盘
/*InputStream is = System.in;
int ch = is.read(); //读取的键盘按下的内容
System.out.println(ch);*/
//录入一行字母数据,将其转为大写形式输出
InputStream is = System.in;
//将字节流转为字符流
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = br.readLine();
System.out.println(line.toUpperCase());
System.out.println("-------------------------------------------");
/*
* Scanner:
* 扫描器,可以扫描键盘录入的数据,也可以扫描文本文件中的数据,也可以直接扫描字符串数据
* 只能扫描:基本类型和字符串类型的数据。
* */
/*Scanner sc = new Scanner(System.in);
sc.nextInt();*/
Scanner sc1 = new Scanner("hello abc 123");
System.out.println(sc1.next());
Scanner sc2 = new Scanner(new File("user.txt"));
System.out.println(sc2.next());
Scanner sc3 = new Scanner(new FileInputStream("user.txt"));
System.out.println(sc3.next());
}
}
三. 对象序列化
(一) 对象流概述
-
对象序列化介绍:
(1) 对象序列化:就是将对象保存到磁盘中,或者在网络中传输对象
这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息,字节序列写到文件之后,相当于文件中持久保存了一个对象的信息(把对象存储在文件中)
(2) 对象反序列化: 将序列化的字节序列从文件中读取回来,重构对象(把文件当中存储的对象读取出来) -
对象序列化流: ObjectOutputStream ObjectInputStream
(二)对象序列化流的使用
- ObjectOutputStream : 对象序列化流,是OutputStream的子类,将Java对象的原始数据类型和图形写入OutputStream
2.构造方法:
ObjectOutputStream (OutputStream o):
创建一个写入指定的OutputStream的ObjectOutputStream
-
常用方法:
writeObject(Object obj): 将指定的对象写入ObjectOutputStream -
注意事项:
(1) 一个对象要想被序列化,该对象所属的类必须必须实现Serializable 接口
(2) Serializable是一个标记接口,实现该接口,不需要重写任何方法
(三)对象反序列化流的使用
-
ObjectInputStream : 对象反序列化流,是InputStream的子类,就是将持久化保存的对象,按照字节顺序进行读取,ObjectInputStream反序列化前先使用ObjectOutputStream编写的原始数据和对象
-
构造方法:
ObjectInputStream(InputStream in): 创建从指定的InputStream读取的ObjectInputStream
3.常用方法:
readObject(): 从ObjectInputStream读取一个对象, 返回值类型为Object
4.EOFException : End Of File Excepttion 文件到达结束位置,不能再进行对象的获取了
- 问题: 反序列时,将对象从文件中读取,不知道文件中有几个对象, 于是获取对象次数多了, 报出EOFException, 文件到达末尾异常
优化方案: 将多个对象放置到一个集合中, 将这个集合对象写入到文件中,实现序列化过程; 读取文件的时候,就读一次, 将集合对象获取出来, 遍历集合相当于将集合中所有对象都获取到. 如此可以避免掉,不知道文件中有多少对象从而报出的异常问题
import java.io.*;
import java.util.Arrays;
import java.util.List;
public class Demo9 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 基本类型 和常用对象
int num = 123;
float f = 1.23f;
String s = "helloworld";
int[] arr = {1,2,3,4};
List<Integer> list = Arrays.asList(1,2,3);
//序列化
//注意:序列化数据的顺序决定了反序列化数据的顺序
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
oos.writeInt(num);
oos.writeFloat(f);
oos.writeUTF(s);
oos.writeObject(arr);
oos.writeObject(list);
oos.close();
//反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
int x = ois.readInt();
float fl = ois.readFloat();
String str = ois.readUTF();
int[] arrays = (int[])ois.readObject();
List<Integer> list1 = (List<Integer>)ois.readObject();
System.out.println(x);
System.out.println(fl);
System.out.println(str);
System.out.println(Arrays.toString(arrays));
System.out.println(list1);
ois.close();
}
}
/*
* 对象序列化:
* 序列化: 将对象进行持久化存储。---写
* 按照字节顺序进行存储的,这是一个字节序列(类型、属性名、属性值)
* 反序列化: 将持久化的对象获取到。---读
* 字节对象流:
* ObjectInputStream
* ObjectOutputStream
* 可以操作基本类型和对象。
* */
(四)序列号serialVersionUID和transient关键字
- serialVersionUID:
(1) 序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会不会出问题呢?会出问题,会抛出InvalidClassException异常
(2) 如果出问题了,如何解决呢?
重新序列化
给对象所属的类加一个serialVersionUID
格式为:private static final long serialVersionUID = 42L; - transient:
如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?
给成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程。
import java.io.*;
import java.util.ArrayList;
public class Demo10 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("F:\\中公教育\\基础班\\8.10以及以后\\8.25\\编程8.25\\src\\per.txt"));
oos.writeObject(new Student("zhangsan",20));
oos.writeObject(new Student("zhangsan",29));
ArrayList<Student> list = new ArrayList<>();
list.add(new Student("zhangsna",18));
list.add(new Student("lisi",18));
list.add(new Student("wangwu",16));
oos.writeObject(list);
/*
* 反序列化时,容器出现 EOFException异常,没有可反序列的对象,还要反序列化。
* 解决方案,就是将多个要序列化的对象存储到一个集合中,反序列化这个集合即可。
* */
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("F:\\中公教育\\基础班\\8.10以及以后\\8.25\\编程8.25\\src\\per.txt"));
Student stu = (Student) ois.readObject();
System.out.println(stu);
Student stu1 = (Student) ois.readObject();
System.out.println(stu1);
//Student stu2 = (Student) ois.readObject();// 多反序列化时就会Exception in thread "main" java.io.EOFException,可以用集合
//System.out.println(stu2);
ArrayList<Student> stuList = (ArrayList<Student>) ois.readObject();//序列化反序列化都要是集合类,否则无法转换
System.out.println(stuList);
ois.close();
}
}
class Student implements Serializable {
public static final long serialVersionUID = 42L;//设定类的序列版本号一致,否则有异常
private String name;
//transient int age;//加了transient 不序列化。输出初始值
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
/*
* 标记接口: 接口中没有方法和字段。
* java.io.Serializable
* 只有实现该接口的类对应的对象才可以进行序列化和反序列化。
* 对象可以序列化,其子类对象也可以序列化。
*
* 问题: 某个属性不想要进行序列化怎么办?
* 属性使用static修饰,变成类变量,不属于对象可以做实现效果,但是static修饰变量,要求变量是一个共享资源数据是才可以修饰。
* 专业方案: 使用关键字 transient 修饰属性,修饰后的属性不会进行序列化。
*
* 注意: 与系统底层有关的对象不能进行序列化,如:System
* */
补充
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
public class Demo11 {
public static void main(String[] args) throws IOException {
PrintStream ps = System.out;
ps.println("abc");
ps.write("123".getBytes()); //控制台
ps.println(new char[]{'1','2','3'});//显示数组内的值
ps.println(new int[]{4,5,6,6});//显示地址值
System.out.println("\n------------------------------------");
PrintStream ps1 = new PrintStream("ps.txt");
ps1.println("abc");
ps1.write("hahah".getBytes());
ps1.print("heihei");
System.out.println("-------------------------------");
PrintStream ps2 = new PrintStream(new FileOutputStream("p.txt"));
ps2.println("abc");
ps2.write("hahah".getBytes());
ps2.print("heihei");
}
}
/*
* PrintStream:
* 打印字节流,可以输出任意类型的数据到目的地,扩展了print/println方法
* System.out:对应的目的地默认是控制台。
* 构造方法:
* PrintStream(File/String filename)
* PrintStream(OutputStream os)
*
* println(char[] ch)
* println(Object obj)
* */
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class Demo12 {
public static void main(String[] args) throws IOException {
PrintWriter pw = new PrintWriter("pp.txt");
pw.write("abc");
pw.print("hahha");
pw.println("abcd");
pw.flush();
System.out.println("-------------------------");
//true:代表开启自动刷新
//自动刷新只有对 println printf format 有效
PrintWriter pw1 = new PrintWriter(new FileOutputStream("ppp.txt"),true);
pw1.println("abcdsfd");
pw1.print("abcdsfd");
PrintWriter pw2 = new PrintWriter(new FileWriter("ppp1.txt"),true);
pw2.println("abcdsfd");
pw2.print("abcdsfd");
}
}
/*
* PrintWriter:
* 打印字符流,该对象与PrintStream用法基本相同。
* 对象创建只能通过构造方法。
* 构造方法:
* PrintWriter(File/String filename)
* PrintWriter(Writer/OutputStream wo)
* */