1、缓冲流
缓冲流,也叫高效流,是对4个基本的 FileXxx 流的增强,所以也是4个流,按照数据类型分类:
- 字节缓冲流: BufferedInputStream , BufferedOutputStream
- 字符缓冲流: BufferedReader , BufferedWriter
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO 次数,从而提高读写的效率。
(1)字节缓冲流
java.io.BufferedOutputStream extends OutputStream
BufferedOutputStream:字节缓冲输出流
java.io.BufferedInputStream extends InputStream
BufferedInputStream:字节缓冲输入流
构造方法:
- BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
- BufferedInputStream(InputStream in) 创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。
代码实现:
public void testBufferedStream() throws IOException {
// 创建流对象
String src = "E:\\javatest文件\\src\\1.png";
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src));
String des = "E:\\javatest文件\\des\\bis.png";
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(des));
//int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。
byte[] bytes = new byte[1024];//存储每次读取的数据
int len = 0; //记录每次读取的有效字节个数
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0, len);
}
//释放资源
bos.close();
bis.close();
}
(2)字符缓冲流
java.io.BufferedWriter extends Writer
BufferedWriter:字符缓冲输出流
java.io.BufferedReader extends Reader
BufferedReader:字符缓冲输入流
构造方法:
- BufferedWriter(Writer out) 创建一个使用默认大小输出缓冲区的缓冲字符输出流。
- BufferedReader(Reader in) 创建一个使用默认大小输入缓冲区的缓冲字符输入流。
特有方法:
- BufferedWriter: void newLine() 写入一个行分隔符。会根据不同的操作系统,获取不同的行分隔符。
- BufferedReader: String readLine() 读取一个文本行。读取一行数据行的终止符号:通过换行 ('\n')、回车 ('\r') 或回车后直接跟着换行(\r\n)字符之一即可认为某行已终止。
代码实现:
/*
3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉 以咨之,然后施行,必得裨补阙漏,有所广益。
2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不 宜偏私,使内外异法也。
1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外 者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以 塞忠谏之路也。
*/
public void testBuffered() throws IOException {
//1.创建一个HashMap集合对象,可以:存储每行文本的序号(1,2,3,..);value:存储每行的文本
HashMap<String, String> map = new HashMap<>();
//2.创建字符缓冲输入流对象,构造方法中绑定字符输入流
String src = "E:\\javatest文件\\src\\in.txt";
BufferedReader br = new BufferedReader(new FileReader(src));
//3.创建字符缓冲输出流对象,构造方法中绑定字符输出流
String des = "E:\\javatest文件\\des\\out.txt";
BufferedWriter bw = new BufferedWriter(new FileWriter(des));
//4.使用字符缓冲输入流中的方法readline,逐行读取文本
String line;
while ((line = br.readLine()) != null) {
//5.对读取到的文本进行切割,获取行中的序号和文本内容
String[] arr = line.split("\\.");
//6.把切割好的序号和文本的内容存储到HashMap集合中(key序号是有序的,会自动排序1,2,3,4..)
map.put(arr[0], arr[1]);
}
//7.遍历HashMap集合,获取每一个键值对
for (String key : map.keySet()) {
String value = map.get(key);
//8.把每一个键值对,拼接为一个文本行
line = key + "." + value;
//9.把拼接好的文本,使用字符缓冲输出流中的方法write,写入到文件中
bw.write(line);
bw.newLine();//写换行
}
//10.释放资源
bw.close();
br.close();
}
2、转换流
(1)字符编码和字符集
- 字符编码 Character Encoding:就是一套自然语言的字符与二进制数之间的对应规则。
- 字符集 Charset:也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符 号、数字等。
常见字符 集有ASCII字符集、GBK字符集、Unicode字符集等。
- ASCII码表:0~ 9(48~ 57)、A~ Z(65~ 90)、a~ z(97~ 122)
- GBK:最常用的中文码表。是在GB2312(简体中文码表)标准基础上的扩展规范,使用了双字节编码方案。
- UTF-8:它使用一至四个字节为每个字符编码。
编码规则:①128个US-ASCII字符,只需一个字节编码。 ②拉丁文等字符,需要二个字节编码。 ③大部分常用字(含中文),使用三个字节编码。④其他极少使用的Unicode辅助字符,使用四字节编码。
(2)InputStreamReader和OutputStreamWriter
java.io.OutputStreamWriter extends Writer
OutputStreamWriter:是字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。(编码:把能看懂的变成看不懂)
java.io.InputStreamReader extends Reader
InputStreamReader:是字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。(解码:把看不懂的变成能看懂的)
构造方法:
- OutputStreamWriter(OutputStream out):创建使用默认字符编码的 OutputStreamWriter。
- OutputStreamWriter(OutputStream out, String charsetName): 创建使用指定字符集的 OutputStreamWriter。
- InputStreamReader(InputStream in) 创建一个使用默认字符集的 InputStreamReader。
- InputStreamReader(InputStream in, String charsetName) 创建使用指定字符集的 InputStreamReader。
小贴士:String charsetName表示指定的编码表名称,不区分大小写,可以是utf-8/UTF-8、gbk/GBK、...不指定默认使用UTF-8。
代码实现:
/*将GBK编码的文本文件,转换为UTF-8编码的文本文件。*/
public void testReverseStream() throws IOException {
//1.创建InputStreamReader对象,构造方法中传递字节输入流和指定的编码表名称GBK
String src = "E:\\javatest文件\\src\\gbk.txt";
InputStreamReader isr = new InputStreamReader(new FileInputStream(src), "GBK");
//2.创建OutputStreamWriter对象,构造方法中传递字节输出流和指定的编码表名称UTF-8
String des = "E:\\javatest文件\\des\\utf8.txt";
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(des), "UTF-8");
//3.使用InputStreamReader对象中的方法read读取文件
char[] cs = new char[1024];//存储读取到的多个字符
int len = 0;//记录的是每次读取的有效字符个数
while ((len = isr.read(cs)) != -1) {
//4.使用OutputStreamWriter对象中的方法write,把读取的数据写入到文件中
osw.write(cs, 0, len);
}
//5.释放资源
osw.close();
isr.close();
}
3、序列化流
(1)ObjectOutputStream和ObjectInputStream
java.io.ObjectOutputStream extends OutputStream
ObjectOutputStream(对象的序列化流):把对象以流的方式写入到文件中保存。
java.io.ObjectInputStream extends InputStream
ObjectInputStream(对象的反序列化流):把文件中保存的对象,以流的方式读取出来使用。
构造方法:
- ObjectOutputStream(OutputStream out) 创建写入指定 OutputStream 的 ObjectOutputStream。
- ObjectInputStream(InputStream in) 创建从指定 InputStream 读取的 ObjectInputStream。
特有方法:
- ObjectOutputStream:void writeObject(Object obj) 将指定的对象写入ObjectOutputStream。
- ObjectInputStream:Object readObject() 从ObjectInputStream读取对象。
序列化操作:
一个对象要想序列化,必须满足两个条件:
- 该类必须实现 java.io.Serializable 接口, Serializable是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出 NotSerializableException。
- 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用 transient 关键字修饰。
反序列化操作:
对于JVM可以反序列化对象,它必须是能够找到class文件的类。
如果找不到该类的class文件,则抛出一个 ClassNotFoundException 异常。
如果能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操 作也会失败,抛出一个 InvalidClassException 异常。
发生这个异常的原因如下:
- 该类的序列版本号与从流中读取的类描述符的版本号不匹配
- 该类包含未知数据类型
- 该类没有可访问的无参数构造方法
小贴士:
Serializable 接口给需要序列化的类,提供了一个序列版本号。 serialVersionUID 该版本号的目的在于验证序列化的对象和对应类是否版本匹配。
被static修饰的成员变量不能被序列化的,序列化的都是对象。
被transient修饰成员变量,不能被序列化。
代码实现:
/*将GBK编码的文本文件,转换为UTF-8编码的文本文件。*/
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
//被transient修饰成员变量,不能被序列化
private transient String sex;
private int age;
public Person() {
}
public Person(String name, String sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
//省略toString方法和get、set方法
}
public void testObjectStream() throws IOException, ClassNotFoundException {
//1.定义一个存储Person对象的ArrayList集合
ArrayList<Person> list = new ArrayList<>();
//2.往ArrayList集合中存储Person对象
list.add(new Person("张三", "男", 18));
list.add(new Person("李四", "男", 19));
list.add(new Person("王五", "男", 20));
//3.创建一个序列化流ObjectOutputStream对象
String pathName = "E:\\javatest文件\\obj.txt";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(pathName));
//4.使用ObjectOutputStream对象中的方法writeObject,对集合进行序列化
oos.writeObject(list);
//5.创建一个反序列化ObjectInputStream对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(pathName));
//6.使用ObjectInputStream对象中的方法readObject读取文件中保存的集合
Object o = ois.readObject();
//7.把Object类型的集合转换为ArrayList类型
ArrayList<Person> list2 = (ArrayList<Person>) o;
//8.遍历ArrayList集合
for (Person p : list2) {
System.out.println(p);
//打印输出:
//Person{name='张三', sex='null', age=18}
//Person{name='李四', sex='null', age=19}
//Person{name='王五', sex='null', age=20}
}
//9.释放资源
ois.close();
oos.close();
}
4、打印流
(1)PrintStream
java.io.PrintStream extends OutputStream
PrintStream(打印流):为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。
构造方法:
- PrintStream(File file):输出的目的地是一个文件
- PrintStream(OutputStream out):输出的目的地是一个字节输出流。
- InputStreamReader(InputStream in) 创建一个使用默认字符集的 InputStreamReader。
- PrintStream(String fileName):输出的目的地是一个文件路径。
改变打印流向:
System.out 就是 PrintStream 类型的,只不过它的流向是系统规定的,打印在控制台上。既然是流对象, 我们就可以改变它的流向。
使用System.setOut方法改变输出语句的目的地改为参数中传递的打印流的目的地:static void setOut(PrintStream out)重新分配“标准”输出流。
注意:
如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表 97->a。
如果使用自己特有的方法print/println方法写数据,写的数据原样输出 97->97。
代码实现:
public void testPrintStream() throws IOException {
//创建打印流PrintStream对象,构造方法中绑定要输出的目的地
String pathName = "E:\\javatest文件\\print.txt";
PrintStream ps = new PrintStream(pathName);
//如果使用继承自父类的write方法写数据,那么查看数据的时候会查询编码表 97->a
ps.write(97);
ps.write("\r\n".getBytes());
//如果使用自己特有的方法print/println方法写数据,写的数据原样输出 97->97
ps.println(97);
ps.println(8.8);
ps.println('a');
ps.println("HelloWorld");
ps.println(true);
System.out.println("在控制台输出");
System.setOut(ps);//把输出语句的目的地改变为打印流的目的地
System.out.println("在打印流的目的地中输出");
//释放资源
ps.close();
}