之前已经学习了基本的一些流,作为IO流的入门,今天我们要见识一些更强大的流。比如能够高效读写的缓冲流,能够转换编码的转换流,能够持久化存储对象的序列化流等等。这些功能更为强大的流,都是在基本的流对象基础之上创建而来的,就像穿上铠甲的武士一样,相当于是对基本流对象的一种增强。
本章内容:

1.IO概述
2.字节流
3.字符流
4.IO异常的处理

1.缓冲流

        缓冲流,也叫高效流,是对4个基本的FileXxx 流的增强,所以也是4个流,按照数据类型分类:

  • 字节缓冲流:BufferedInputStream,BufferedOutputStream
  • 字符缓冲流:BufferedReader,BufferedWrite

缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。

构造方法:

  • public BufferedInputStream(InputStream in) :创建一个 新的缓冲输入流。
  • public BufferedOutputStream(OutputStream out): 创建一个新的缓冲输出流。

构造举例,代码如下:

1
2
3
4
// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));

BufferedOutputStream:字节缓冲输出流

  1. 继承自父类的共性成员方法:
  • 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. 构造方法:

        BufferedOutputStream(OutputStream out)  创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
        BufferedOutputStream(OutputStream out, int size)  创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流。
    
     参数:
    OutputStream out:字节输出流
         我们可以传递FileOutputStream,缓冲流会给FileOutputStream增加一个缓冲区,提高FileOutputStream的写入效率
    int size:指定缓冲流内部缓冲区的大小,不指定默认
    
  2. 使用步骤(重点)

1.创建FileOutputStream对象,构造方法中绑定要输出的目的地
2.创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象对象,提高FileOutputStream对象效率
3.使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中
4.使用BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中
5.释放资源(会先调用flush方法刷新数据,第4部可以省略)

BufferedInputStream:字节缓冲输入流

  1. 继承自父类的成员方法:
    int read()从输入流中读取数据的下一个字节。
    int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。
    void close() 关闭此输入流并释放与该流关联的所有系统资源。

  2. 构造方法:

     BufferedInputStream(InputStream in) 创建一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。
     BufferedInputStream(InputStream in, int size) 创建具有指定缓冲区大小的 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用。
     参数:
         InputStream in:字节输入流
             我们可以传递FileInputStream,缓冲流会给FileInputStream增加一个缓冲区,提高FileInputStream的读取效率
         int size:指定缓冲流内部缓冲区的大小,不指定默认
     
  3. 使用步骤(重点):
    1.创建FileInputStream对象,构造方法中绑定要读取的数据源
    2.创建BufferedInputStream对象,构造方法中传递FileInputStream对象,提高FileInputStream对象的读取效率
    3.使用BufferedInputStream对象中的方法read,读取文件
    4.释放资源

普通流与缓存流效率测试:

查询API,缓冲流读写方法与基本的流是一致的,我们通过复制大文件(400MB),测试它的效率

  1. 基本的io流,考虑到文件最好选择二进制的字节流来读写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BufferedDemo {
public static void main(String[] args) throws FileNotFoundException {
// 记录开始时间
long start = System.currentTimeMillis();
// 创建流对象
try (
FileInputStream fis = new FileInputStream("jdk9.exe");
FileOutputStream fos = new FileOutputStream("copy.exe")
){
// 读写数据
int b;
while ((b = fis.read()) != -1) {
fos.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println("普通流复制时间:"+(end - start)+" 毫秒");
}
}

十几分钟过去了...
  1. 缓冲流,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BufferedDemo {
public static void main(String[] args) throws FileNotFoundException {
// 记录开始时间
long start = System.currentTimeMillis();
// 创建流对象
try (
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("jdk9.exe"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.exe"));
){
// 读写数据
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println("缓冲流复制时间:"+(end - start)+" 毫秒");
}
}

缓冲流复制时间:8016 毫秒
  1. 如何更快呢?使用数组的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BufferedDemo {
public static void main(String[] args) throws FileNotFoundException {
// 记录开始时间
long start = System.currentTimeMillis();
// 创建流对象
try (
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("jdk9.exe"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.exe"));
){
// 读写数据
int len;
byte[] bytes = new byte[8*1024];
while ((len = bis.read(bytes)) != -1) {
bos.write(bytes, 0 , len);
}
} catch (IOException e) {
e.printStackTrace();
}
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println("缓冲流使用数组复制时间:"+(end - start)+" 毫秒");
}
}
缓冲流使用数组复制时间:666 毫秒

BufferedWriter:字符缓冲输出流

  1. 继承自父类的共性成员方法:

    • 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() 关闭此流,但要先刷新它。
  2. 构造方法:

     BufferedWriter(Writer out) 创建一个使用默认大小输出缓冲区的缓冲字符输出流。
     BufferedWriter(Writer out, int sz) 创建一个使用给定大小输出缓冲区的新缓冲字符输出流。
     参数:
         Writer out:字符输出流
             我们可以传递FileWriter,缓冲流会给FileWriter增加一个缓冲区,提高FileWriter的写入效率
         int sz:指定缓冲区的大小,不写默认大小
    
  1. 特有的成员方法:
    void newLine() 写入一个行分隔符。会根据不同的操作系统,获取不同的行分隔符
    换行:换行符号
    windows:\r\n
    linux:/n
    mac:/r

  2. 使用步骤:
    1.创建字符缓冲输出流对象,构造方法中传递字符输出流
    2.调用字符缓冲输出流中的方法write,把数据写入到内存缓冲区中
    3.调用字符缓冲输出流中的方法flush,把内存缓冲区中的数据,刷新到文件中
    4.释放资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Demo03BufferedWriter {
public static void main(String[] args) throws IOException {
//System.out.println();
//1.创建字符缓冲输出流对象,构造方法中传递字符输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("10_IO\\c.txt"));
//2.调用字符缓冲输出流中的方法write,把数据写入到内存缓冲区中
for (int i = 0; i <10 ; i++) {
bw.write("传智播客");
//bw.write("\r\n");
bw.newLine();
}
//3.调用字符缓冲输出流中的方法flush,把内存缓冲区中的数据,刷新到文件中
bw.flush();
//4.释放资源
bw.close();
}
}

BufferedReader:字符缓冲输入流

  1. 继承自父类的共性成员方法:
    int read() 读取单个字符并返回。
    int read(char[] cbuf)一次读取多个字符,将字符读入数组。
    void close() 关闭该流并释放与之关联的所有资源。

  2. 构造方法:

     BufferedReader(Reader in)  创建一个使用默认大小输入缓冲区的缓冲字符输入流。
     BufferedReader(Reader in, int sz)     创建一个使用指定大小输入缓冲区的缓冲字符输入流。
     参数:
         Reader in:字符输入流
             我们可以传递FileReader,缓冲流会给FileReader增加一个缓冲区,提高FileReader的读取效率
    
  3. 特有的成员方法:
    String readLine() 读取一个文本行。读取一行数据

    行的终止符号:通过下列字符之一即可认为某行已终止:换行 ('\n')、回车 ('\r') 或回车后直接跟着换行(\r\n)。

    返回值:

    **包含该行内容的字符串,不包含任何行终止符,如果已到达流末尾,则返回 null**
  4. 使用步骤:
    1.创建字符缓冲输入流对象,构造方法中传递字符输入流
    2.使用字符缓冲输入流对象中的方法read/readLine读取文本
    3.释放资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Demo04BufferedReader {
public static void main(String[] args) throws IOException {
//1.创建字符缓冲输入流对象,构造方法中传递字符输入流
BufferedReader br = new BufferedReader(new FileReader("10_IO\\c.txt"));

//2.使用字符缓冲输入流对象中的方法read/readLine读取文本
/*String line = br.readLine();
System.out.println(line);

line = br.readLine();
System.out.println(line);

line = br.readLine();
System.out.println(line);

line = br.readLine();
System.out.println(line);*/

/*
发现以上读取是一个重复的过程,所以可以使用循环优化
不知道文件中有多少行数据,所以使用while循环
while的结束条件,读取到null结束
*/
String line;
while((line = br.readLine())!=null){
System.out.println(line);
}

//3.释放资源
br.close();
}
}

2.转换流

        计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字、英文、标点符号、汉字等字符是二进制数转换之后的结果。按照某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码 。比如说,按照A规则存储,同样按照A规则解析,那么就能显示正确的文本符号。反之,按照A规则存储,再按照B规则解析,就会导致乱码现象。

编码:字符(能看懂的)–字节(看不懂的)

解码:字节(看不懂的)–>字符(能看懂的)

字符集,也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符号、数字等。

计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符集有ASCII字符集、GBK字符集、Unicode字符集等

  • ASCII码:基本的ASCII字符集,使用7位(bits)表示一个字符,共128字符。主要包括控制字符(回车键、退格、换行键等)和可显示字符(英文大小写字符、阿拉伯数字和西文符号)
  • GBK字符集:GB就是国标的意思,是为了显示中文而设计的一套字符集。
  • Unicode:Unicode编码系统为表达任意语言的任意字符而设计,是业界的一种标准,也称为统一码、标准万国码。它最多使用4个字节的数字来表达每个字母、符号,或者文字。有三种编码方案,UTF-8、UTF-16和UTF-32。最为常用的UTF-8编码。

编码引出的问题

        在IDEA中,使用 FileReader 读取项目中的文本文件。由于IDEA的设置,都是默认的 UTF-8 编码,所以没有任何问题。但是,当读取Windows系统中创建的文本文件时,由于Windows系统的默认是GBK编码,就会出现乱码。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ReaderDemo {
public static void main(String[] args) throws IOException {
// 文件file_gbk.txt是GBK编码,而idea默认是utf-8编码,所以会出现乱码
FileReader fileReader = new FileReader("E:\\File_GBK.txt");
int read;
while ((read = fileReader.read()) != -1) {
System.out.print((char)read);
}
fileReader.close();
}
}
输出结果:
���

InputStreamReader类

        转换流 java.io.InputStreamReader,是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。

构造方法

  • InputStreamReader(InputStream in): 创建一个使用默认字符集的字符流。
  • InputStreamReader(InputStream in, String charsetName): 创建一个指定字符集的字符流。

举例说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class InputStreamReaderDemo {
public static void main(String[] args) {
// 定义文件路径(你好)
String fileName = "html.txt";
// 创建转换流对象,默认为UTF-8编码
InputStreamReader isr = null;
try{
// isr = new InputStreamReader(new FileInputStream(fileName));
// 创建转换流对象,设置为GBK编码
isr = new InputStreamReader(new FileInputStream(fileName),"GBk");
int len;
// 使用默认编码字符流读取,乱码
while((len = isr.read())!= -1){
System.out.print((char)len); // GBK:浣犲ソ UTF-8:你好
}
}catch (IOException e){
e.printStackTrace();
}
finally {
try{
if (isr != null){
isr.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
}
}

OutputStreamWriter类

        转换流java.io.OutputStreamWriter ,是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。

构造方法

  • OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流。
  • OutputStreamWriter(OutputStream in, String charsetName): 创建一个指定字符集的字符流。

构造样例,代码如下:

1
2
OutputStreamWriter isr = new OutputStreamWriter(new FileOutputStream("out.txt"));
OutputStreamWriter isr2 = new OutputStreamWriter(new FileOutputStream("out.txt") , "GBK");

指定编码方式,举例说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class OutputDemo {
public static void main(String[] args) throws IOException {
// 定义文件路径
String FileName = "E:\\out.txt";
// 创建流对象,默认UTF8编码
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(FileName));
// 写出数据
osw.write("你好"); // 保存为6个字节
osw.close();

// 定义文件路径
String FileName2 = "E:\\out2.txt";
// 创建流对象,指定GBK编码
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream(FileName2),"GBK");
// 写出数据
osw2.write("你好");// 保存为4个字节
osw2.close();
}
}

 评论

联系我 | Contact with me

Copyright © 2019-2020 谁知你知我,我知你知深。此恨经年深,比情度日久

博客内容遵循 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议