第一章 缓冲流
上一次我们学习了IO流得基本的流,这次我们要学习更强大的流。
比如: 能够高效读写的缓冲流,能够转换编码的转换流,能够持久化储存对象的序列化流等等,这些功能强大的流,都是在基本的流对象基础之上创建而来的,就像穿上铠甲的武士一样,相当于是对基本流对象的一种增强。
1.1 概述
缓冲流,也叫高效流,是对4个基本的FileXXX
流的增强,所以也是4个流,按照数据类型分类
- 字节缓冲流:
BufferedInputStream
,BufferedOutputStream
- 字符缓冲流
BufferedReader
,BufferedWriter
1.2 字节缓冲流
构造方法
public BufferedInputStream(InputStream in)
: 创建一个新的缓冲输入流.public BufferedOutputStream(OutStream out,int size)
: 创建一个新的缓冲输出流,后面是可选参数,表示指定了缓冲区大小的数据之后,写入指定的底层输出流(不指定就默认)
他们继承自FileXXXStream,所以,父类有的方法他们都有
字节缓冲流示例代码:
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
FileOutputStream fo = new FileOutputStream("C:\\Users\\CUMT_CJY\\Desktop\\Java\\images\\[Java] 缓冲流,转换流,序列化流,转换流\\你爹我.txt");
//创建一个缓冲流对象
BufferedOutputStream bos = new BufferedOutputStream(fo);
//使用write方法
bos.write("我是你爹".getBytes());
//注意,这里一定要使用fulsh或close方法进行刷新,这样数据才能够写进文件中
bos.close();
}
}
字节输入流代码示例
package demo07;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\CUMT_CJY\\Desktop\\Java\\images\\[Java] 缓冲流,转换流,序列化流,转换流\\你爹我.txt");
BufferedInputStream bis = new BufferedInputStream(fileInputStream);
byte[] b = new byte[1024];
int len = 0;
while ((len = bis.read(b)) != -1){
System.out.println(new String(b,0,len));
}
//资源释放直接关闭缓冲流就可以了,前面的文件也会被关闭
bis.close();
}
}
效率测试
package demo07;
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
long a = System.currentTimeMillis();
File f1 = new File("C:\\Users\\CUMT_CJY\\Desktop\\Java\\images\\[Java] 缓冲流,转换流,序列化流,转换流\\你爹我.txt");
File f2 = new File("C:\\Users\\CUMT_CJY\\Desktop\\Java\\images\\[Java] 缓冲流,转换流,序列化流,转换流\\你爹我1.txt");
FileInputStream fis = new FileInputStream(f1);
FileOutputStream fos = new FileOutputStream(f2);
int len;
while ((len = fis.read())!= -1){
fos.write(len);
}
long b = System.currentTimeMillis();
System.out.println(b - a);//427毫秒
long a1 = System.currentTimeMillis();
File f11 = new File("C:\\Users\\CUMT_CJY\\Desktop\\Java\\images\\[Java] 缓冲流,转换流,序列化流,转换流\\你爹我.txt");
File f21 = new File("C:\\Users\\CUMT_CJY\\Desktop\\Java\\images\\[Java] 缓冲流,转换流,序列化流,转换流\\你爹我1.txt");
FileInputStream fis1 = new FileInputStream(f11);
FileOutputStream fos1 = new FileOutputStream(f21);
BufferedInputStream fis11 = new BufferedInputStream(fis1);
BufferedOutputStream fos11 = new BufferedOutputStream(fos1);
int len1;
while ((len1 = fis11.read())!= -1){
fos.write(len1);
}
fos11.close();
fis11.close();
long b1 = System.currentTimeMillis();
System.out.println(b1 - a1);
}
}
//这样写比较正规
//效率结果
//46258
//35160
1.3 字符缓冲流
构造方法
public BufferedReader(Reader in)
: 创建一个新的缓冲输入流public BufferedWriter(Writer out)
: 创建一个新的缓冲输出流
特有方法
BufferedReader
String readLine()
读取一个文本行返回到字符串中。 (不会包含换行符号,如果读取到最后一行之后,就会返回null)
BufferedWriter
void newLine()
写入一个行分隔符。 (会自动识别当前系统,添加正确的行分隔符)
1.4 练习: 文本排序
根据序号,将文章排序
3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必能裨补阙漏,有所广益。
8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。
4.将军向宠,性行淑均,晓畅军事,试用于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。
2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。
1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。
9.今当远离,临表涕零,不知所言。
6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。
7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐托付不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。
5.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。
实现代码:
package demo07;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("C:\\Users\\CUMT_CJY\\Desktop\\Java\\images\\[Java] 缓冲流,转换流,序列化流,转换流\\出师表(乱).txt");
BufferedReader fis = new BufferedReader(fr);
Map<Character, String> goal = new HashMap<>();
for (char i = '1'; i <= '9'; i++) {
goal.put(i, "");
}
String value = "";
char a;
char b = 0;
while ((value = fis.readLine()) != null) {
a = value.charAt(0);
switch (a) {
case '1':
value = value.substring(2);
goal.put('1', value);
b = a;
break;
case '2':
value = value.substring(2);
goal.put('2', value);
b = a;
break;
case '3':
value = value.substring(2);
goal.put('3', value);
b = a;
break;
case '4':
value = value.substring(2);
goal.put('4', value);
b = a;
break;
case '5':
value = value.substring(2);
goal.put('5', value);
b = a;
break;
case '6':
value = value.substring(2);
goal.put('6', value);
b = a;
break;
case '7':
value = value.substring(2);
goal.put('7', value);
b = a;
break;
case '8':
value = value.substring(2);
goal.put('8', value);
b = a;
break;
case '9':
value = value.substring(2);
goal.put('9', value);
b = a;
break;
default:
if (goal.containsKey(b)) {
value = goal.get(b) + value;
goal.put(b, value);
}
}
}
//value的值已经将前面的符号去掉了,这样写是为了处理换行的问题
System.out.println(goal);
FileWriter fw = new FileWriter("C:\\Users\\CUMT_CJY\\Desktop\\Java\\images\\[Java] 缓冲流,转换流,序列化流,转换流\\出师表.txt");
BufferedWriter bw = new BufferedWriter(fw);
for (char i = '1'; i <= '9'; i++) {
value = goal.get(i);
bw.write(value);
bw.newLine();
}
bw.close();
fis.close();
}
}
第二章 转换流
2.1 字符编码和字符集
字符编码
计算机存储的信息都是用二进制数表示的,而我们在屏幕上看到的数字,英文,标点符号,汉字等字符是二进制转换之后的结果. 按照某走规则,将字符存储到计算机中,称为编码. 反之,将储存在计算机中的二进制数按照某种规则解析显示出来,称为解码.
要是编码存储和解码的方式不一样,那么就会导致,显示的是乱码.
- 字符解码
Character Encoding
: 就是一套自然语言的字符与二进制数之间的对应规则
字符集
- 字符集
charSet
也叫编码表, 是一个系统所支持的所有字符的集合,包括了各国文字,数字,标点,数字,等
计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码. 常见字符集有ASCII字符集,GBK字符集,Unicode字符集等等.
可见,指定了编码,它所对应的字符集自然就确定了,所以编码才是我们最终要关心的.
-
ASCII字符集
- ASCII是基于拉丁字母的一套电脑编码系统,用于显示现代英语(老美用的),共128个,7bits表示一个字符,ASCII的拓展字符集使用8bits表示一个字符,共256个字符,支持欧洲常用的字符
-
ISO-8859-1字符集
- 拉丁码表,别名Latin-1 , 用于显示欧洲使用的语言
- ISO-5559-1使用单字节编码,兼容ASCII编码
-
GBxxx字符集
- GB就是国标的意思,是为了显示中文而设计的
- GB2312: 简体中文码表. 一个小于127的字符的意义与原来相同,但是大于127的字符连载一起的时候,就可以表示成一个汉字,它大概包括了7000多个汉字,此外,希腊字母,日文的假名们都编进去了, 127以上的叫全角字符, 以下的就叫做半角字符
- GBK; 最常用的中文码表. 是上一个的基础上的拓展规范,收录了21003个汉字,完全兼容上一个码表,同时还收录了繁体中文,日韩文汉字等
- GBK18030:" 最新的中文码表. 收录了70244个, 采用多字节编码,每个字可由1,2,4个字节组成,支持少数民族的文字,同时有繁体中文汉字以及日韩汉字等.
-
Unicode字符集
- 你懂的,是为了表达任意语言而设计的,又称为标准万国码,统一码
- 它最多使用4个字节的数字来表达每个字母,符号,或者文字.有三种编码方案
:UTF-8,UTF-16,UTF-32,最为常用的是8 - UTF-8编码
- 128个US-ASCII字符,只需要一个字节编码
- 拉丁文等字符,需要两个字节编码
- 大部分常用字(包括中文),使用三个字节编码
- 极少数使用的Unicode辅助字符,使用四字节编码
2.2 编码引出的问题
在IDEA中,使用FileReader
读取项目中的文本文件,由于IDEA的设置,默认是UTF-8的编码,所以没有任何问题,但是,当我们读取Windows系统中创建的文本文件的时候,由于Windows创建的默认是GBK编码,就会出现乱码
package demo07;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) throws IOException {
//这个文件是ANSI编码(里面包括了GB)
FileReader fr = new FileReader("C:\\Users\\CUMT_CJY\\Desktop\\Java\\images\\[Java] 缓冲流,转换流,序列化流,转换流\\出师表.txt");
BufferedReader fis = new BufferedReader(fr);
//随便读取一行试试
String s = fis.readLine();
System.out.println(s);
//运行结果
//�ȵ۴�ҵδ����е����㣬���������֣�����ƣ�ף��˳等等
}
}
//总之就是乱码
2.3 InputStreamReader类
转换流java.io.InputStreamReader
,是Reader的子类,是从字节流到字符流的桥梁.它读取字节,并使用指定的字符集将其解码为字符.他的字符集可以由名称指定,也可以接受平台默认字符集
构造方法
InputStreamReader(InputStream in)
创建一个使用默认字符集的 InputStreamReader。
InputStreamReader(InputStream in, Charset cs)
创建使用给定字符集的 InputStreamReader。
InputStreamReader(InputStream in, CharsetDecoder dec)
创建使用给定字符集解码器的 InputStreamReader。
InputStreamReader(InputStream in, String charsetName)
创建使用指定字符集的 InputStreamReader。
注意: 构造方法的第一个参数的意思是,他的所有子类可以作为参数
指定代码读取
package demo07;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) throws IOException {
FileInputStream fi = new FileInputStream("C:\\Users\\CUMT_CJY\\Desktop\\Java\\images\\[Java] 缓冲流,转换流,序列化流,转换流\\出师表.txt");
InputStreamReader fis = new InputStreamReader(fi, "GBK");
int s;
while ((s = fis.read()) != -1){
System.out.println((char) s);//我们会发现输出结果正常了
}
}
}
2.4 转换流原理
先通过FileInputStream进行解码,然后通过InputStreamReader可以查询执行的码表
InputStreamWriter可以使用指定的码表对内存中的文字进行编码,然后通过FileOutputStream解码到文件中
2.5 OutputStreamWriter类
OutputStreamWriter
是字符流通向字节流的桥梁:可使用指定的 charset
将要写入流中的字符编码成字节。它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。
构造方法
OutputStreamWriter(OutputStream out)
创建使用默认字符编码的 OutputStreamWriter。
OutputStreamWriter(OutputStream out, Charset cs)
创建使用给定字符集的 OutputStreamWriter。
OutputStreamWriter(OutputStream out, CharsetEncoder enc)
创建使用给定字符集编码器的 OutputStreamWriter。
OutputStreamWriter(OutputStream out, String charsetName)
创建使用指定字符集的 OutputStreamWriter。
注意: 其实FileWriter就是默认编码的OutputStreamWriter
示例代码:
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
FileOutputStream fs = new FileOutputStream("C:\\Users\\CUMT_CJY\\Desktop\\Java\\images\\[Java] 缓冲流,转换流,序列化流,转换流\\出师表1.txt");
OutputStreamWriter osw = new OutputStreamWriter(fs,"GBK");
osw.write("我是你爹");
osw.close();
}
}
2.6 练习: 转换文件编码
将出师表从GBK编码转换为UTF-8编码
实例代码1(这个代码可以用来转换同一个文件的格式,因为有个字符型数组保存了里面的内容读取和写入是分开的):
package demo07;
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
FileInputStream fi = new FileInputStream("C:\\Users\\CUMT_CJY\\Desktop\\Java\\images\\[Java] 缓冲流,转换流,序列化流,转换流\\出师表.txt");
InputStreamReader fis = new InputStreamReader(fi, "GBK");
char[] aaa = new char[5000];
fis.read(aaa);
FileOutputStream fo = new FileOutputStream("C:\\Users\\CUMT_CJY\\Desktop\\Java\\images\\[Java] 缓冲流,转换流,序列化流,转换流\\出师表.txt");
OutputStreamWriter osw = new OutputStreamWriter(fo,"UTF-8");
osw.write(aaa);
osw.close();
fis.close();
}
}
实例代码2(这个不可以用于转换一个文件的编码,因为,不能对一个文件边读取边保存,所以这里是读取一个文件,然后创建一个新的文件,将里面的内容换一个编码放进里面):
package demo07;
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
FileInputStream fi = new FileInputStream("C:\\Users\\CUMT_CJY\\Desktop\\Java\\images\\[Java] 缓冲流,转换流,序列化流,转换流\\出师表1.txt");
InputStreamReader fis = new InputStreamReader(fi, "GBK");
FileOutputStream fo = new FileOutputStream("C:\\Users\\CUMT_CJY\\Desktop\\Java\\images\\[Java] 缓冲流,转换流,序列化流,转换流\\出师表.txt");
OutputStreamWriter osw = new OutputStreamWriter(fo,"UTF-8");
int len ;
while ((len = fis.read()) != -1){
osw.write(len);
}
osw.close();
fis.close();
}
}
第三章 序列化
3.1 概述
Java提供了一种对象序列化的机制。 用一个字节序列表示一个对象,该字节包含该对象的数据
,对象的类型
和对象中存储的属性
等信息. 字节序列写出到文件之后,就相当于文件中永久保存了一个对象的信息.
反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SzdxOUzQ-1617504821515)(C:\Users\CUMT_CJY\Desktop\Java\images[Java] 缓冲流,转换流,序列化流,转换流\4.png)]
3.2 ObjectOutputStream类
java.io.ObjectOutputStream
类,将Java对象的原始数据类型写出到文件,实现对象的持久存储.
构造方法
protected ObjectOutputStream()
为完全重新实现 ObjectOutputStream 的子类提供一种方法,让它不必分配仅由 ObjectOutputStream 的实现使用的私有数据。
ObjectOutputStream(OutputStream out)
创建写入指定 OutputStream 的 ObjectOutputStream(参数可以是其子类)。
特有的成员方法
void writeObject(Object obj)
将指定的对象写入 ObjectOutputStream。
序列化操作
- 一个对象想要序列化,必须满足两个条件
- 该类必须实现
java.io.Serializable
接口,这个接口是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出NotSerializableException
- 该类的所有属性必须是可序列化的,如果有一个属性不需要可序列化,那么该属性必须标明是瞬态的,使用
transient
关键字修饰,读出来的值是初始值,除此之外和其他的变量没有任何不同- static也是不能被序列化的,读出来的数据永远是初始值
- 该类必须实现
//Person类
package demo07;
import java.io.Serializable;
public class Person implements Serializable {
public String name;
public int age;
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 Person(String name, int age) {
this.name = name;
this.age = age;
}
}
//main函数
package demo07;
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\CUMT_CJY\\Desktop\\Java\\images\\[Java] 缓冲流,转换流,序列化流,转换流\\你爹.txt"));
oos.writeObject(new Person("美女",18));
oos.close();
}
}
3.3 ObjectInputStream类
反序列化流,将之前使用的ObjectOutputStream序列化的原始数据恢复为对象.
构造方法
protected ObjectInputStream()
为完全重新实现 ObjectInputStream 的子类提供一种方式,让它不必分配仅由 ObjectInputStream 的实现使用的私有数据。
ObjectInputStream(InputStream in)
创建从指定 InputStream 读取的 ObjectInputStream。
特有方法
Object readObject()
从 ObjectInputStream 读取对象。
反序列化操作
如果能够找到一个对象的class文件,我们就可以进行反序列化操作
用上面那个特有方法
package demo07;
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream oos = new ObjectInputStream(new FileInputStream("C:\\Users\\CUMT_CJY\\Desktop\\Java\\images\\[Java] 缓冲流,转换流,序列化流,转换流\\你爹.txt"));
Person yourFather =(Person) oos.readObject();
oos.close();
System.out.println(yourFather.getName());
System.out.println(yourFather.getAge());
}
}
//运行结果
//美女
//18
注意,readObject方法要抛出ClassNotFoundException异常
注意
序列化与反序列化的原理如下图所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZFWtgnBN-1617504821516)(C:\Users\CUMT_CJY\Desktop\Java\images[Java] 缓冲流,转换流,序列化流,转换流\5.png)]
我们可发现,序列化与反序列化期间有个序列号比对的过程,如果遇到版本迭代或者修改源文件类的定义,那么序列号就会对不上,就会产生InvaildClassException
解决方案
手动给类添加一个序列号,继承了serializable
接口的类可以用声明serialVersonUID的方法来固定自己序列号保持不变
static final long serialVersionUID = XX;
3.4 练习: 序列化集合
- 将存有多个自定义对象的集合序列化操作,保存到
list.txt
文件中 - 反序列化
list.txt
,并遍历集合,打印对象信息.
package demo07;
import java.io.*;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\CUMT_CJY\\Desktop\\Java\\images\\[Java] 缓冲流,转换流,序列化流,转换流\\你爹.txt",true));
oos.writeObject(new Person("大美女",20));
oos.writeObject(new Person("中美女",20));
oos.writeObject(new Person("小美女",20));
oos.writeObject(new Person("美女",20));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\CUMT_CJY\\Desktop\\Java\\images\\[Java] 缓冲流,转换流,序列化流,转换流\\你爹.txt"));
ArrayList<Person> goal = new ArrayList<>();
Person temp = null;
while((temp = (Person) ois.readObject()) != null){
System.out.println(temp.getName());
System.out.println(temp.getAge());
}
ois.close();
oos.close();
}
}
注意: 这里其实writeObject方法可以用一个ArrayList作为参数传进去(数组里面放了很多对象),这是一个很好的思路
第四章 打印流
4.1 概述
平时我们使用的print和println方法都是来自于java.io.PrintStream
类,该类能够方便的打印各种数据,是一种边界的输出方式
4.2 PrintStream类
构造方法
PrintStream(File file)
创建具有指定文件且不带自动行刷新的新打印流。
PrintStream(File file, String csn)
创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
PrintStream(OutputStream out)
创建新的打印流。
PrintStream(OutputStream out, boolean autoFlush)
创建新的打印流。
PrintStream(OutputStream out, boolean autoFlush, String encoding)
创建新的打印流。
PrintStream(String fileName)
创建具有指定文件名称且不带自动行刷新的新打印流。
PrintStream(String fileName, String csn)
创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
4.2 改变打印流的流向
ois.close();
oos.close();
}
}
> 注意: 这里其实writeObject方法可以用一个ArrayList作为参数传进去(数组里面放了很多对象),这是一个很好的思路
# 第四章 打印流
## 4.1 概述
平时我们使用的print和println方法都是来自于`java.io.PrintStream`类,该类能够方便的打印各种数据,是一种边界的输出方式
## 4.2 PrintStream类
### 构造方法
```java
PrintStream(File file)
创建具有指定文件且不带自动行刷新的新打印流。
PrintStream(File file, String csn)
创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
PrintStream(OutputStream out)
创建新的打印流。
PrintStream(OutputStream out, boolean autoFlush)
创建新的打印流。
PrintStream(OutputStream out, boolean autoFlush, String encoding)
创建新的打印流。
PrintStream(String fileName)
创建具有指定文件名称且不带自动行刷新的新打印流。
PrintStream(String fileName, String csn)
创建具有指定文件名称和字符集且不带自动行刷新的新打印流。
4.2 改变打印流的流向
使用System.setOut(Object)
方法可以改变打印流的流向,使打印流输出到不一样的位置