1 IO流
1.1 概念
input:输入(读取)-----> 流:数据(字节/字符) -----> output:输出(写入)
输入:把硬盘中的数据,读取到内存中使用
输出:把内存中的数据,写入到硬盘中保存
内存:临时存储
硬盘:永久存储
1个字符 = 2个字节
1个字节=8个二进制位
顶层父类
输入流 | 输出流 | |
---|---|---|
字节流 | 字节输入流 InputStream | 字节输出流 OutputStream |
字符流 | 字符输入流 Reader | 字符输出流 Writer |
2 字节流
2.1 字节输出流OutputStream
它是一个抽象类
共性成员方法:
- public void close() :关闭此输出流并释放与此流相关联的任何系统资源。当完成流的操作时,必须调用此方法,释放系统资源。
- public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
- public void write(byte[] b) :将 b.length字节从指定的字节数组写入此输出流。
- public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
- public abstract void write(int b) :将指定的字节输出流。1次写1个字节。
2.1.1 文件字节输出流FileOutputStream
,extends OutputStream,把内存中的数据写入硬盘的文件中。
构造方法:创建一个 FileOutputStream 对象。根据参数传递的文件/文件路径,创建一个空文件。把 FileOutputStream 对象指向创建好的文件。
- FileOutputStream(String name):文件路径
- FileOutputStream(File file):文件
写入数据的原理(内存 —> 硬盘)
Java程序 —> JVM —>OS —> OS调用写数据的方法 —>把数据写入文件中
字节输出流的使用步骤
- 创建一个 FileOutputStream 对象,构造方法在传递写入数据的目的地
- 抵用 FileOutputStream 对象的write方法,把数据写入文件中
- 释放资源,清空流占用的内存,提高程序效率
把a写入a.txt文件
public abstract void write(int b)
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("a.txt");
fos.write(97);
fos.close();
}
原理:写数据时会把十进制97转为二进制1100001,硬盘中存储的数据都是字节,1个字节等于8个比特位。文本编辑器在打开文件时会查询编码表,把字节转为字符表示,97—>a。
一次写入多个字节
public void write(byte[] b)
public void write(byte[] b, int off, int len) :把数组的一部分写入文件
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream(new File("b.txt"));
byte[] a = {65,66,67,68,69};//ABCDE
byte[] b = {-65,-66,-67,68,69};//烤紻E
//如果写的第一个字节是正数 显示会查ascii码表
//如果是负数,第一个字节和第二个字节会组成一个中文显示 查询GBK
fos.write(b);
fos.write(a,1,2);//BC
fos.close();
}
使用String类的方法把字符串转换为字节数组。
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream(new File("b.txt"));
byte[] b = "哈哈哈".getBytes();
System.out.println(Arrays.toString(b));
fos.write(b);
fos.close();
}
GBK:两个字节是一个中文
UTF8:三个字节是一个中文
数据的追加写和换行写
追加写的构造方法 append=true
FileOutputStream(String name, boolean append)
FileOutputStream(File file, boolean append)
换行:win【\r\n】mac【/n】linux【/r】
public static void main(String[] args) throws IOException {
//追加写
FileOutputStream fos = new FileOutputStream(new File("b.txt"),true);
for (int i = 0; i < 10; i++) {
fos.write("哈哈哈".getBytes());
//换行写
fos.write("\r\n".getBytes());
}
fos.close();
}
2.2 字节输入流InputStream
它是一个抽象类,是所有字节输入流类的超类
共性方法
- int read()
- int read(byte[] b)
- void close
2.2.1 文件字节输入流FileInputStream
作用:把硬盘中的数据,读取到内存中使用
构造方法
- FileInputStream(String name)
- FileInputStream(File file)
构造方法的作用:
- 创建一个FileInputStream对象
- 把FileInputStream对象指向指定构造方法中要读取的文件
读取数据原理(硬盘—>内存)
Java程序 —> JVM —>OS —>OS调用读取数据的方法 —> 读取文件
字节输出流的使用步骤
- 创建FileInputStream对象,构造方法中绑定要读取的数据源
- 调用FileInputStream对象的read方法,读取文件
- 释放资源
注意:read每调用一次指针都会后移一位,如果while循环括号中不保存读取的值,输出的就是跳位读取的值。
一次读取一个字节
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("a.txt");
int len = 0;
while((len = fis.read())!=-1){
//读取一个字节并返回 文件末尾返回-1
System.out.print((char) len);
}
fis.close();
}
一次读取多个字节
int read(byte[] b):
注意:byte[]的作用是起缓冲作用,存储每次读取到的多个字节。数组长度一般定义为1024(1kb)或者1024的整数值。int返回值是每次读取的有效字节个数。
原理:创建一个byte数组,数组元素的初始值为0。开始读取,把读取出的数据存入byte数组,指针移动,b.length是一次读取的字节个数。new String()可以把byte数组转换为字符串输出。read方法的返回值是有效读取字节个数。
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("a.txt");
byte[] bytes = new byte[1024];
int len = 0;
while((len = fis.read(bytes))!=-1){
System.out.print(new String(bytes,0,len));//打印byte中存的有效位
}
}
2.3 字节流练习-文件复制
原理
创建一个输入流的对象,再创建一个输出流的对象,然后输入流对象读取文件内容,写入输出流指向的对象处。
注意:应该先关闭输出流(写),再关闭输入流(读)。因为写完了一定读完了,但读完了不一定写完了。应该先开启输入流,再开启输出流,先读后写。
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("D:\\1.png");
FileOutputStream fos = new FileOutputStream("E:\\1.png");
byte[] bytes = new byte[1024];
int len = 0;
while((len=fis.read(bytes))!=-1){
fos.write(bytes,0,len);
}
fos.close();
fis.close();
}
2.4 字节流读文件存在的问题
在读取中文字符的时候,可能不会显示完整的字符,因为一个中文可能占用多个字节。按字节流读取,每次只能读取字符的一部分。所以文本文件一般用字符流进行读写。
GBK:1个中文2个字节
UTF8:1个中文3个字节
3 字符流
3.1 字符输入流Reader
字符输入流类最顶层的父类,是一个抽象类。
共性方法
- int read()
- int read(char[] c)
- void close()
3.1.1 文件字符输入流FileReader
作用:把硬盘文件中的数据以字符的方式读取到内存中
构造方法:创建一个FileReader对象,把FileReader对象指向要读取的文件/文件路径
- FileReader(String name)
- FileReader(File file)
使用步骤
- 创建FileReader对象,构造方法中绑定要读取的数据源
- FileReader对象调用read方法
- 关闭,释放资源
一次读取一个字符
public static void main(String[] args) throws IOException {
FileReader fd = new FileReader("a.txt");
int len = 0;
while((len = fd.read())!=-1){
System.out.print((char) len);
}
fd.close();
}
一次读取多个字符
字符数组 —> 字符串 new String(char[], int off, int len)构造方法
public static void main(String[] args) throws IOException {
FileReader fd = new FileReader("a.txt");
char[] c = new char[1024];
int len = 0;
while((len = fd.read(c))!=-1){
System.out.print(new String(c,0,len));
}
fd.close();
}
3.2 字符输出流Writer
共性方法
- void write(int c) 写入单个字符。
- void write(char[] cbuf) 写入字符数组。
- abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分,off数组的开始索引,len写的字符个数。
- void write(String str) 写入字符串。
- void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
- void flush() 刷新该流的缓冲。
- void close() 关闭此流,但要先刷新它。
3.2.1 文件字符输出流FileWriter
作用:内存中的字符数据写入文件中
构造方法
- FileWriter(String name)
- FileWriter(File file)
作用:创建FileWriter对象,根据构造方法中传递的文件/文件路径创建文件,会把FileWriter对象指向创建好的文件中。
使用步骤
- 创建FileWriter对象,构造方法中绑定写入数据的目的地。
- 使用FileWriter中的方法write,把数据写入到内存缓冲区中。(字符转换为字节的过程)
- 使用FileWriter中的方法flush,把内存缓冲区的数据,刷新到文件中。
- 释放资源(会先把内存缓冲区的数据刷新到文件中)
写入单个字符
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("a.txt");//绑定写入位置
fw.write(97);//写入缓冲区 字符-->字节
fw.flush();//写入文件
fw.close();//释放资源
}
flush和close方法的区别
close在关闭之前会先把内存缓冲区的数据刷新到文件中,但close之后不能继续使用write方法。flush同样也是刷新操作,但flush之后可以继续使用write方法。
其他写入方法
- void write(char[] c):写入字符数组
- abstract void write(char[] c, int off, int len):写入字符数组的某一部分
- void write(String str):写入字符串
- void write(String str, int off, int len):写入字符串的某一部分
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("a.txt");
char[] c = {'a','b','c','d','e','f'};
fw.write(c);
fw.write('\n');
fw.write(c,3,3);
String str = "嘻嘻哈哈呵呵";
fw.write(str);
fw.write('\n');
fw.write(str,1,3);
fw.close();
}
续写和换行写
同字节流输出
public static void main(String[] args) throws IOException {
FileWriter fw = new FileWriter("c.txt",true);
for (int i = 0; i < 10; i++) {
fw.write("哈哈哈"+i);
fw.write("\r\n");
}
fw.close();
}
4 IO流的异常处理
在JDK1.7之前可以使用try-catch-finally处理流中的一次
public static void main(String[] args) {
FileWriter fw = null;
try{
fw = new FileWriter("F:\\c.txt",true);
for (int i = 0; i < 10; i++) {
fw.write("哈哈哈"+i);
fw.write("\r\n");
}
}catch (IOException e){
e.printStackTrace();
}finally {
if(fw != null){
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
JDK7的新特性:可以在try后增加一个 ,括号中可以定义流对象,那么这个流对象的作用域就在try中有效,try执行完后,流对象自动释放,不用写finally【常用】
public static void main(String[] args) {
try(FileWriter fw = new FileWriter("F:\\c.txt",true)){
for (int i = 0; i < 10; i++) {
fw.write("哈哈哈"+i);
fw.write("\r\n");
}
}catch (IOException e){
e.printStackTrace();
}
System.out.println("哈哈哈哈哈");
}
JDK9的新特性,try前面可以定义对象,在try后边的()中可以引入流对象的名称。try执行完毕后,流对象可以释放掉,不需要写finally。【不常用】
public static void main(String[] args) {
FileReader fr = new FileReader("c.txt");
FileWriter fw = new FileWriter("d.txt");
try(fr;fw){
char[] c = new char[1024];
int len = 0;
while((len = fr.read(c))!=-1){
fw.write(c);
}
fw.flush();
}catch (IOException e){
e.printStackTrace();
}
System.out.println("哈哈哈哈哈");
}
5 属性集
java.util.Properties 继承于Hashtable,来表示一个持久的属性集。是唯一一个和IO流相结合的集合。
它使用键值结构存储数据,每个键及其对应值都是一个字符串。该类也被许多Java类使用,比如获取系统属性时, System.getProperties 方法就是返回一个Properties 对象。
使用Properties存储数据并遍历取出
操作字符串的特有方法
- Object setProperty(String key, String value):相当于map.put(k, v)
- String getProperty(String key):相当于map.get(k)
- Set <String> stringPropertyNames():相当于map.keySet()
public static void main(String[] args) {
Properties prop = new Properties();
prop.setProperty("张三","20");
prop.setProperty("李四","25");
prop.setProperty("王五","30");
Set<String> set = prop.stringPropertyNames();
for(String key: set){
String value = prop.getProperty(key);
System.out.println(key+" "+value);
}
}
- 可以使用store方法把集合中的临时数据,持久化写入硬盘中存储
- void store(OutputStream out, String comments):字节输出流,不能写中文。
- void store(Writer writer, String comments):字符输出流,可以写中文。
comments是注释,用来解释保存的文件是做什么的,不可以使用中文,默认是Unicode编码,一般使用空字符串
使用步骤
- 创建一个Proprorities对象,添加数据
- 创建字节输出流/字符输出流对象,构造方法绑定输出位置
- 使用store方法把集合中的临时数据持久化写入硬盘中。
- 释放输出流对象
public static void main(String[] args) throws IOException {
Properties prop = new Properties();
prop.setProperty("张三","20");
prop.setProperty("李四","25");
prop.setProperty("王五","30");
FileWriter fw = new FileWriter("e.txt");
prop.store(fw, "");
fw.close();
}
- 可以使用load方法,把硬盘中保存的文件,读取到集合中使用
- void load(InputStream in):字节输入流,不能读取含有中文的键值对
- void load(Reader r):字符输入流,可以读取含有中文的键值对
使用步骤
- 创建一个Properties对象,load堆区保存键值对的文件
- 遍历Properties集合
注意
- 键值对文件中,键和值默认的连接符可以使用=、空格
- 可以使用#来注释,被注释的键值对不会再读取
- 键和值默认都是字符串,不用再加引号
public static void main(String[] args) throws IOException {
Properties prop = new Properties();
prop.load(new FileReader("f.txt"));
Set<String> set = prop.stringPropertyNames();
for(String key: set){
System.out.println(key+" "+prop.getProperty(key));
}
}
6 缓冲流
缓冲流,也叫高效流,是对4个基本的FileXxx 流的增强,所以也是4个流,按照数据类型分类:
字节缓冲流: BufferedInputStream , BufferedOutputStream
字符缓冲流: BufferedReader , BufferedWriter
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
6.1 字节缓冲流
6.1.1 字节缓冲输出流BfferedOutputStream
继承自OutputStream,可使用父类共性方法(见2.1)
构造方法:
- BufferedOutputStream(OutputStream out):创建一个新的缓冲输出流,以数据写入指定的底层输出流。
- BufferedOutputStream(OutputSteam out, int size):创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流。
使用步骤
- 创建FileOutputStream对象,构造方法中绑定输出位置。
- 创建BufferedOutputStream对象,构造方法中传递fos对象,提高fos对象效率。
- 使用bos对象的write方法,把数据写入到内部缓冲区中。
- 使用bos对象的flush方法,把缓冲区的数据刷新到文件中。
- 释放资源,close会先调用flush刷新数据再关闭(所以第四步可省略)
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("b.txt");
BufferedOutputStream bos = new BufferedOutputStream(fos);
bos.write("写入内部缓冲区".getBytes());//字节输出流 用字节
bos.flush();
bos.close();
}
6.1.2 字节缓冲输入流BfferedInputStream
继承自InputStream,可使用父类共性方法(见2.2)
构造方法
- BufferedInputStream(InputStream in):创建一个新的缓冲输入流,保存参数输入流in。
- BufferedInputStream(InputSteam in, int size):创建一个具有指定缓冲区大小的BufferedInputStream保存其参数,即输入流。
使用步骤
- 创建FileInputStream对象,构造方法中绑定读取位置。
- 创建BufferedInputStream对象,构造方法中传递fis对象,提高fis对象的读取效率。
- 使用bis对象的read方法,把数据写入到内部缓冲区中。
- 释放资源。
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("a.txt");
BufferedInputStream bis = new BufferedInputStream(fis);
/*
int len = 0;
while((len = bis.read()) != -1){
System.out.print((char)len);
}
bis.close();
*/
byte[] bytes = new byte[1024];
int len = 0;
while((len = bis.read(bytes))!=-1){
System.out.println(new String(bytes));
}
}
6.1.3 基本和缓冲效率比较
public static void main(String[] args) throws IOException {
long s = System.currentTimeMillis();
FileInputStream fis = new FileInputStream("D:\\1.png");
FileOutputStream fos = new FileOutputStream("E:\\1.png");
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
byte[] bytes = new byte[1024];
int len = 0;
while((len=bis.read(bytes))!=-1){
bos.write(bytes,0,len);
}
bos.close();
bis.close();
long e = System.currentTimeMillis();
System.out.println(e-s);
}
文件复制4MB的ppt的效率比较:
基本流(一次读一个字节):17757 ms
缓冲流(一次读一个字节):121 ms
基本流 + 数组缓冲区(一次读多个字节):27 ms
缓冲流 + 数组缓冲区(一次读多个字节):9 ms
6.2 字符缓冲流
6.2.1 字符缓冲输出流BufferedWriter
继承自Writer,可使用父类共性方法(见3.2)
构造方法
- BufferedWriter(Writer out):创建一个使用默认大小输出缓冲区的缓冲字符输出流。
- BufferedWriter(Writer out, int size):创建一个使用指定大小输出缓冲区的缓冲字符输出流。
特有方法
- void newLine():写入一个行分隔符,它会根据不同的操作系统,获取不同的行分隔符。
使用步骤
- 创建字符输出流对象,构造方法中传递写入地址
- 创建字符缓冲区输出流对象,构造方法中传递字符输出流
- 调用flush,将内存缓冲区中的数据刷新到文件中
- 释放资源
public static void main(String[] args) throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
for (int i = 0; i < 10; i++) {
bw.write("哈哈哈哈");
bw.newLine();
bw.write("嘻嘻嘻嘻");
bw.newLine();
}
bw.close();
}
6.2.2 字符缓冲输入流BufferedReader
继承自Reader,可使用父类共性方法(见3.1)
构造方法
- BufferedReader(Reader in):创建一个使用默认大小输入缓冲区的缓冲字符输入流。
- BufferedReader(Reader in, int size):创建一个使用指定大小输入缓冲区的缓冲字符输入流。
特有方法
- String readLine():读取一行文本,读取一行数据,流末尾返回null,readLine不读换行符
使用步骤
- 创建字符输入流对象,构造方法中传递读取地址
- 创建字符缓冲区输入流对象,构造方法中传递字符输入流
- 调用read/readLine读取
- 释放资源
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new FileReader("b.txt"));
String line = null;
while((line = br.readLine())!=null){
System.out.println(line);
}
}
6.3 缓冲流练习-文本排序
b.txt内容:
7.77777777777777
3.张三3333333333
5.王五5555555555
1.大一1111111111
4.李四4444444444
2.小二2222222222
6.六六6666666666
public static void main(String[] args) throws IOException {
Map<String , String> map = new HashMap<>();
BufferedReader br = new BufferedReader(new FileReader("b.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("nb.txt"));
String line;
while((line = br.readLine())!=null){
String[] split = line.split("\\.");
map.put(split[0],split[1]);
}
//map会自动排序
for(String key: map.keySet()){
String value = map.get(key);
bw.write(key+"."+value);
bw.newLine();
}
bw.close();
br.close();
}
7 转换流
7.1 字符编码和字符集
字符编码
计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本f符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。
字符编码Character Encoding : 就是一套自然语言的字符与二进制数之间的对应规则。
字符集
字符集 Charset :也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等。
ASCII是最基本的编码表,GBK是中文编码表
Unicode是万国码,兼容各种语言,是应用中优先采用的编码。
7.2 编码常见问题
例如:在IDEA中,使用FileReader 读取项目中的文本文件。由于IDEA的设置,都是默认的UTF-8编码,所以没有任何问题。但是,当读取Windows系统中创建的文本文件时,由于Windows系统的默认是GBK编码,就会出现乱码。
解决方法:使用转换流
7.3 转换流相关内容
字节转换为字符(解码)
FileInputStream —> 查询编码表 —> FileReader
7.3.1 OutputStreamWriter
字符流通向字节流的桥梁
可以指定编码格式
写入你想写入的编码格式的文件中
构造方法
- OutputStreamWriter(OutputStream out):创建使用默认字符编码的OutputStreamWriter(utf8)
- OutputStreamWriter(OutputStream out, String charSet):创建使用指定字符编码的OutputStreamWriter
使用步骤
- 创建OutputStreamWriter对象,构造方法中传递字节输出流和指定编码集
- 使用osw对象中的write,把字符转换为字节存在缓冲区中
- 使用osw对象中的flush把字节刷新到文件中
- 释放资源
你好 GBK 4字节 UTF8 6字节
public static void main(String[] args) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("gbk.txt"),"gbk");
osw.write("你好");
osw.close();
}
7.3.2 InputStreamReader
字节流通向字符流的桥梁
可以指定编码格式
读取字节解码为字符 把看不懂的变成看得懂的
构造方法
- InputStreamReader(InputStream in):创建使用默认字符编码的OutputStreamWriter(utf8)
- InStreamReader(InputStream in, String charSet):创建使用指定字符编码的InputStreamReader
使用步骤
- 创建InputStreamReader对象,构造方法中传递字节输入流和指定编码集
- 使用isw对象中的read读取文
- 释放资源
注意:如果文件编码和指定编码不一致,则会发生乱码
public static void main(String[] args) throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("utf8.txt"),"utf-8");
int len = 0;
while((len = isr.read())!=-1){
System.out.print((char)len);
}
isr.close();
}
7.4 练习:转换文件编码
将GBK编码的文本文件,转换为UTF8编码的文本文件
public static void main(String[] args) throws IOException {
InputStreamReader isr = new InputStreamReader(new FileInputStream("gbk.txt"),"gbk");
OutputStreamWriter osr = new OutputStreamWriter(new FileOutputStream("gbk2utf8.txt"),"utf-8");
int len = 0;
while((len = isr.read()) != -1){
osr.write(len);
}
osr.close();
isr.close();
}
8 序列化和反序列化
8.1 概念
ObjectOutputStream对象的序列化流
把对象以流的方式写入到文件中保存,叫写对象,也叫对象的序列化。对象中包含的不仅是字符,使用字节流。
ObjectInputStream对象的反序列化流
把文件中的对象,以流的方式读取出来,叫读对象,也叫对象的反序列化。文件保存的都是字节,使用字节流。
8.2 对象的序列化流ObjectOutputStream
构造方法
- ObjectOutputStream(OutputStream out)
特有方法
- void writeObject(Object obj)
使用步骤
- 创建ObjectOutputStream对象,构造方法中传递字节输出流
- 使用writeObject方法写入对象
- 释放资源
序列化和反序列化时,会抛出NotSerializableException没有序列化异常。
类通过实现Serializable接口以启动序列化功能,没有实现此接口的类无法使其任何状态序列化或反序列化。
Serializable接口也叫标记型接口,进行序列化和反序列化,必须要实现Serializable接口,给类添加标记。在使用时,会检测是否有标记。
使用前提
Person类的实现 要加上implement Serializable
public static void main(String[] args) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
oos.writeObject(new Person("张三",15));
oos.close();
}
8.3 对象的反序列化流ObjectInputStream
构造方法
- ObjectInputStream(InputStream in)
特有方法
- void readObject(Object obj) 读取对象
使用步骤
- 创建ObjectOutputStream对象,构造方法中传递字节输入流
- 使用writeObject方法读取对象
- 释放资源
- 打印对象
readObject方法声明抛出了ClassFoundException,class文件找不到异常,当不存在对象的class文件时抛出异常
使用前提
Person类的实现 要加上implement Serializable
对象的class类文件要存在
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Person.txt"));
Object o = ois.readObject();
ois.close();
System.out.println(o);
【注意!!!】
InvalidClassException异常:序列化之后对类进行更改之后,不重新序列化,而是直接反序列化会出现异常。
因为javac编译器会把java文件编译生成class文件。类如果实现了Serializable接口,就会根据类的定义给类的class文件添加一个序列化号serialVersionUID,输出的文件也会写入这个ID,反序列化的时候,会使用class文件的序列号和输出文件的序列号比较,如果ID一致,则反序列化一致,否则会抛出InvalidClassException异常。
而修改了类的定义之后,会给class文件重新编译生成一个新的序列号,但是输出文件的序列号没有改。
解决方案:手动给类加一个序列号。无论是否修改类都不再修改序列号。
自定义类中添加成员常量。
private static final long serialVersionID = 1L;
瞬态关键字transient
static静态关键字:静态优先于非静态加载到内存中,(静态优先于对象),所以static修饰的成员变量是不能被序列化的,序列化的都是对象。
【在上例中,如果Person类的age成员变量是静态的,序列化和非序列化age将永远是初始值0,无法更改】
transient瞬态关键字:被它修饰的成员变量不能被序列化。如果想要对象中的成员变量不被序列化,可以使用瞬态关键字。
8.4 练习:序列化集合
当我们想在文件中保存多个对象的时候,可以把对象存在集合中,对集合进行序列化和反序列化。
public static void main(String[] args) throws IOException, ClassNotFoundException {
ArrayList<Person> list = new ArrayList<>();
list.add(new Person("张三",15));
list.add(new Person("李四",19));
list.add(new Person("王五",20));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("PersonList.txt"));
oos.writeObject(list);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("PersonList.txt"));
Object o = ois.readObject();
ArrayList<Person> list2 = (ArrayList<Person>) o;
for (Person p : list2){
System.out.println(p);
}
ois.close();
oos.close();
}
9 打印流
9.1 PrintSteam类
平时我们在控制台打印输出,是调用print方法和println方法完成的,这两个方法都来自于java.io.PrintStream类,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。
特点:
- 不会抛出IO异常
- 只负责输出,不负责读取
- 特有方法print、println
构造方法:
- PrintStream(File file):输出目的地是一个文件
- PrintStream(OutputStream out):输出目的地是一个字节输出流
- PrintStream(String path):输出目的地是一个文件路径
注意:
- 如果用继承父类的write方法写数据,查看数据的时候就会查询编码表,再打印
- 如果使用特有方法print和println则会原样输出
public static void main(String[] args) throws IOException, ClassNotFoundException {
PrintStream ps = new PrintStream("a.txt");
ps.write(97);
ps.write('\n');
ps.println(97);
ps.print("a1#$@哈哈哈");
ps.close();
}
可以改变输出语句的目的地,即打印流的流向。
输出语句,默认在控制台输出,使用System.setOut方向改变输出语句的目的地改为参数中传递的打印流的目的地。
public static void main(String[] args) throws IOException, ClassNotFoundException {
System.out.println("控制台输出:哈哈哈");
PrintStream ps = new PrintStream("打印流输出");
System.setOut(ps);
System.out.println("打印流输出:哈哈哈");//控制台不输出这一句 而是写在"打印流输出"文件中
}
转载:https://blog.csdn.net/xd963625627/article/details/105358210