缓冲流、转换流

1.1 概述

1.1 缓冲流的分类

  • 缓冲流概述
  1. 缓冲流又称为高效流
  • 缓冲流的分类
  1. 字节缓冲输出流:BufferedOutputStream
  2. 字节缓冲输入流:BufferedInputStream
  3. 字符缓冲输出流:BufferedWriter
  4. 字符缓冲输入流:BufferedReader
  • 缓冲流的使用方式和非缓冲流使用方式是一样的,区别在于构造方法不一样

1.1.2 缓冲流的原理

  •  利用缓冲区数组临时存储多个数据,等 缓冲区数组满了或调用了close方法时一次调用或减少底层资源的调用次数从而提高读写速度。

1.2 字节缓冲流

1.2.1 字节输出缓冲流: 

  • BufferedOutputStream类概述
  1. 继承OutputStream,是字节缓冲输出流,可以往任意类型的文件输出数据。
  • BufferedOutputStream类构造方法
  • BufferedOutputStream(OutputStream   out)
  1. 根据字节输出流创建字节缓冲输出流对象。
  2. 目前可以传递的子类有:FileOutputStream
  3. 传递谁就提高谁的效率。
  • BufferedOutputStream类成员方法
  1. write:输出一个字节,一个字节数组
  • BufferedOutputStream类使用注意事项:
  1. 调用缓冲流的write方法输出数据不是直接输出到目标文件中,而是先输出到缓冲区数组中,等缓冲区数组满了或调用了flush或close方法才会将缓冲区数组的数据通过FileOutputStream输出到目标文件中。
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;

public class BufferedOutputStreamDemo01 {
    public static void main(String[] args) throws Exception {
        //创建字节输出流对象并关联目标文件
        FileOutputStream fos = new FileOutputStream("H:/aa.txt",true);
        //创建字节缓冲输出流对象
        BufferedOutputStream bos = new BufferedOutputStream(fos);

        //输出一个字节
        bos.write(98);

        //输出一个字节数组
        bos.write("我爱java\r\n".getBytes());

        //关闭流释放资源
        bos.close();
    }
}

 效率测试

import java.io.FileInputStream;
import java.io.FileOutputStream;
/*
    普通流:(复制400M的文件)
 */
public class BufferDemo {
    public static void main(String[] args) {
        //记录开始时间
        long start = System.currentTimeMillis();

         try(//创建输入流对象并关联源文件
             FileInputStream fis = new FileInputStream("H:/jre-9.0.1.zip");
             //创建输出流对象并关联目标文件
             FileOutputStream fos = new FileOutputStream("H:/copy.zip");
             ) {
             //读写数据
             int b;
             while ((b=fis.read())!=-1){
                 fos.write(b);
             }

         }catch (Exception e){
             e.printStackTrace();
         }
         //记录结束的时间
        long end = System.currentTimeMillis();
        System.out.println("普通流复制时间:"+(end-start));
    }
}
/*
    十几分钟过去了...
 */
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
/*
    缓冲流:(复制400M的文件)
 */
public class BufferDemo {
    public static void main(String[] args) {
        //记录开始时间
        long start = System.currentTimeMillis();

         try(
                 //创建输入流对象并关联源文件
             FileInputStream fis = new FileInputStream("H:/jre-9.0.1.zip");
             BufferedInputStream bis = new BufferedInputStream(fis);
             //创建输出流对象并关联目标文件
             FileOutputStream fos = new FileOutputStream("H:/copy.zip");
             BufferedOutputStream bos = new BufferedOutputStream(fos);
             ) {
             //读写数据
             int b;
             while ((b=fis.read())!=-1){
                 fos.write(b);
             }

         }catch (Exception e){
             e.printStackTrace();
         }
         //记录结束的时间
        long end = System.currentTimeMillis();
        System.out.println("普通流复制时间:"+(end-start));
    }
}
/*
    8044秒
 */

1.2.2 字节输入缓冲流

  • BufferedInputStream类概述
  1. 继承InputStream,字节缓冲输入流,可以读取任意类型文件的数据。
  • BufferedInputStream类构造方法
  • BufferedInputStream(InputStream in)
  1. 根据字节输入流创建字节缓冲输入流对象
  2. 目前可以传递的字节输入流对象:FileInputStream
  3. 传递谁就提高谁的效率
  • BufferedInputStream类成员方法
  1. read:读取一个字节,一个字节数组
  • BufferedInputStream类使用注意事项
  1. 利用字节缓冲输入流读取数据时不是直接从目标文件中读取,而是从缓冲区数组中读取,如果缓冲区数组中没有数据可读了,则会利用FileInputStream流从目标文件中读取数据到缓冲区数组中,一次从目标文件中读取8192字节到数组中。
import java.io.BufferedInputStream;
import java.io.FileInputStream;

public class BufferedInputStreamDemo01 {
    public static void main(String[] args) {
        try (
                //创建字节缓冲输入流对象
                BufferedInputStream bis = new BufferedInputStream(new FileInputStream("H:/a.txt"));
                ) {
            //使用循环改进
            //定义整型变量记录获取到的字符
            int len = -1;
            while ((len=bis.read())!=-1){
                System.out.println((char)len);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

1.3 字符缓冲流

1.3.1 字符缓冲输出流

  • BufferedWrite类概述
  1. 继承Writer,是字符缓冲输出流,只能往文本中输出数据。
  • BufferedWrite类构造方法
  • BufferedWrite(Writer writer)
  1. 目前可以传递的字符输出流:FileWriter
  2. 传递谁就提高谁的效率
  • BufferedWrite类成员方法
  1. write:输出一个字符,一个字符数组,一个字符串
  • BufferedWrite类特有的方法
  1. void newLine();输出一个换行符
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class BufferWriterDemo {
    public static void main(String[] args) throws IOException {
        //创建字符缓冲输出流对象
        BufferedWriter bw = new BufferedWriter(new FileWriter("H:/out.txt"));

        //输出一个字符
        bw.write(20320);

        //输出一个字符串
        bw.write("我爱Java");

        bw.newLine();

        char[] chars = {'我','爱','你'};
        //输出一个字符数组
        bw.write(chars);

        //输出换行符
        bw.newLine();

        //输出字符数组的一部分
        bw.write(chars,1,1);

        //关闭流
        bw.close();
    }
}

1.3.2 字符缓冲输入流

  • BufferedReader类概述:
  1. 继承Reader,字符缓冲输入流,只能读取文本文件的内容
  • BufferedReader类构造方法:
  • BufferedReader(Reader r)
  1. 目前可以传递的字符输入流对象:FileReader
  2. 传递谁就提高谁的效率
  • BufferedReader类成员方法:
  1. read:读取一个字符,一个字符数组
  • BufferedReader类特有方法:
  1. String readLine():读取一行数据,读取到文件末尾返回null
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class BufferedReaderDemo01 {
    public static void main(String[] args) throws IOException {
        //创建字符缓冲输入流对象
        BufferedReader  br = new BufferedReader(new FileReader("c.txt"));

        //定义字符串接收读取到的行数据
        String line = null;
        while ((line=br.readLine())!=null){
            System.out.println(line);
        }
        //关门流释放资源
        br.close();
    }
}

3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉
以咨之,然后施行,必得裨补阙漏,有所广益。
8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其
咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。
4.将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,
必能使行阵和睦,优劣得所。
2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不
宜偏私,使内外异法也。
1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外
者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以
塞忠谏之路也。
9.今当远离,临表涕零,不知所言。
6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣
以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。
7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐付托不效,以伤先帝之明,故五月渡泸,深入不毛。
今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛
下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。
5.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息
痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。 

 需求:要求将a.txt文件的内容复制到b.txt文件中并恢复行号的顺序。

public class Test {
    public static void main(String[] args){
        // 创建Map集合
        Map<Integer,String> map = new HashMap<>();
        try(
                // 创建字符缓冲输入流对象
                BufferedReader br = new BufferedReader(new FileReader("a.txt"));
                // 创建字符缓冲输出流对象
                BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"));
        ){

            // 定义变量接收读取到的行数据
            String line = null;
            // 循环读取数据
            while((line = br.readLine()) != null) {
                if (line.trim().length() == 0) continue;
                // 获得行号数字
                Integer key = Integer.parseInt(line.substring(0,1));
                System.out.println(key);
                // 存储数据到map集合
                map.put(key,line);
            }
            System.out.println("------------");
            // 获得键值对个数
            int size = map.size();
            System.out.println(size);
            for (int key = 1; key <= size ; key++) {
                // 根据键获得对应的行数据
                String value = map.get(key);
                System.out.println(value);
                // 利用输出流将value输出到目标文件中
                bw.write(value);
                // 输出换行符
                bw.newLine();
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

转换流:

2.1 编码表概述

  • 在计算机中无论任何数据的传输、存储、持久化,都是以二进制的形式体现的。
  • 那么当我存一个字符的时候,计算机需要持久化到硬盘,或者保存到内存中。这个时候保存在内存、硬盘的数据显然也是二进制的。那么当我需要从硬盘、内存中取出这些字符,再显示的时候,为什么二进制会变成了字符了呢?
  • 这就是码表存在的意义。
  • 码表其实就是一个字符和其对应的二进制相互映射的一张表。
  • 这张表中规定了字符和二进制的映射关系。

计算机存储字符时将字符查询码表,然后存储对应的二进制。
计算机取出字符时将二进制查询码表,然后转换成对应的字符显示。

不同的码表所容纳的字符映射也是不同的。

你 ==> GBK ==> -20-30
你 ==> UTF8 ==> -60-43-34

可以这样理解。
在有些码表中一个字符占用1个字节,1个字节能表示的范围是-128到127,总共为256。所以能容纳256个字符映射。
而有些码表中一个字符占用2个字节,甚至3个字节,因此能容纳的字符映射也更多。

下面按照自己的理解详细讲述一下不同的码表。
常见的码表:
ASCII:
    * 美国码表,码表中只有英文大小写字母、数字、美式标点符号等。每个字符占用1个字节,所有字符映射的二进制都为正数,因此有128个字符映射关系。

GB2312:
    * 兼容ASCII码表,并加入了中文字符,码表中英文大小写字母、数字、美式标点符号占一个字节,中文占两个字节,中文映射的二进制都是负数,因此有128× 128 = 16384个字符映射关系。
    
你 ==> -20-43

GBK/GB18030:
    * 兼容GB2312码表,英文大小写字母、数字、美式标点符号,占一个字节。中文占两个字节,第一个字节为负数,第二个字节为正数和负数,因为有128× 256 = 32768个字符映射关系。    
你 ==> -20 43

Unicode码表:
    * 国际码表,包含各国大多数常用字符,每个字符都占2个字节,因此有65536个字符映射关系。Java语言使用的就是Unicode码表。
    * Java中的char类型用的就是这个码表。char c = 'a';占两个字节。

UTF-8码表:
    * 是基于Unicode码表的,但更智能,会根据字符的内容选择使用多个字节存储。英文占一个字节,中文占3个字节。

你 ==> -20-54-65

乱码的原因
    * 因为文本在存储时使用的码表和读取时使用的码表不一致造成的。

2.2 编码引出的问题

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

/*
    FileReader存在问题:只能使用默认码表读取文件数据
 */
public class test {
    public static void main(String[] args) {
        //创建字符输入流并关联目标文件
        try(FileReader fr = new FileReader("H:/d.txt")){
            //创建字符数组
            char[] chars = new char[3];
            //读取一个字符数组
            int read = fr.read(chars);
            System.out.println(read);
            System.out.println(new String( chars,0,read));

        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

那么,如何读取GBK编码的文件呢?

2.3 InputStreamReader类

  • InputStreamReader类概述
  1. 继承Reader,字符转换输入流,只能读取文本文件的内容。
  2. 字节流通向字符流的桥梁(字节转字符)。
  • InputStreamReader类构造方法
  • InputStreamReader(InputStream in)
  1. 根据字节输入流创建字符转换输入流,默认的编码表:utf8
  • InputStreamReader(InputStream in,String charsetName)
  1. 根据字节输入流创建字符转换输入流
  2. charsetName:用来指定编码的名称,常用的编码表名:gbk和utf8
  3. 目前可以传递的字节输入流有:FileInputStream 和BufferedInputStream
  • 字符转换输入流的转换流程
  1. 先由字节输入流从目标文件中读取数据,读出来的是一堆二进制数据
  2. 然后将读取到的二进制数据交给字符转换流,由字符转换流查询指定的码表将二进制数据转换为对应的字符。

 指定编码读取

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;

public class ReaderDemo02 {
    public static void main(String[] args) throws Exception{
        //定义文件路径,文件为gbk编码
        String fileName = "H:/file_gbk.txt";

        //创建流对象,默认使用utf8编码
        InputStreamReader isr = new InputStreamReader(new FileInputStream(fileName));
        //创建流对象,指定GBK编码
        InputStreamReader isr1 = new InputStreamReader(new FileInputStream(fileName),"GBK");

        //定义变量,保存字符
        int read;
        //使用默认编码字符流读取,乱码
        while ((read=isr.read())!=-1){
            System.out.println((char)read);
        }
        //关闭流释放资源
        isr.close();

        //使用指定编码字符流读取,正常解析
        while ((read= isr1.read())!=-1){
            System.out.println((char)read);
        }
        //关闭流释放资源
        isr.close();
    }
}

 2.4 OutputStreamWriter类

  • OutputStreamWriter类概述
  1. 继承Writer,字符转换输出流,只能往文本文件中输出数据。
  2. 是字符流通向字节流的桥梁。(字符转换字节)
  • OutputStreamWriter类构造方法
  • OutputStreamWriter(OutputStream out)默认的编码表:utf8
  • OutputStreamWriter (OutputStream out,String charsetName)
  1. 根据字节输出流创建字符转换输出流对象 
  2. charsetName:用来指定编码的名称,常用的编码表名为:gbk和utf8
  3. 目前可以使用字节输出流有:FileOutputStream和BufferedOutputStream
  • OutputStreamWriter类成员方法
  1. write:输出字符,字符数组,字符串
  • 字符转换输出流的转换流程
  1. 先由字符串转换输出流查询指定的码表将字符转换为二进制数据
  2. 然后将二进制数据交给字节输出流输出到目标文件中。
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;

public class OutputStreamWriterDemo01 {
    public static void main(String[] args) throws Exception {
        //创建字符转换输出流对象
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("H:/f.txt"));

        //输出字符
        osw.write('你');
        //输出字符串
        osw.write("好吗");
        //关闭流
        osw.close();
    }
}

⾼效字符流和集合的综合使⽤:

import java.io.*;
import java.util.ArrayList;
import java.util.Scanner;

public class BufferedReadDemo {
    public static void main(String[] args) throws Exception {
        //键盘录入3个字符串并写入项目根路径下的data.txt文件中
        writeString2File();
        //验证码验证
        verifyCode();
    }
    /*
       键盘录入一个需要被校验的验证码,如果输入的验证码在data.txt中存在:在控制台提示验证码成功,如果
       不存在控制台提示验证失败。
     */
    public static void verifyCode () throws Exception{
       //创建ArrayList集合,用于存储文件中的3个验证码
        ArrayList<String> list = new ArrayList<String>();

        //创建高效字符缓冲输入流对象,并和data.txt文件关联
        BufferedReader br = new BufferedReader(new FileReader("H:/data.txt"));

        String line = null;
        //循环读取每一行
        while ((line=br.readLine())!=null){
            //将读取到的每一行信息存入到list集合中
            list.add(line);
        }
        //关闭流对象
         br.close();

        //创建键盘录入对象
        Scanner sc = new Scanner(System.in);
        String line1 = sc.nextLine();
        System.out.println("请输入一个验证码:");
        String code =sc.nextLine();
        if(list.contains(code)){
            System.out.println("验证成功");
        }else {
            System.out.println("验证失败");
        }
    }



    /*
        键盘录入3个字符串并写入项目根路径下的data.txt文件中
     */
    public static  void writeString2File() throws IOException {
        //创建高效字符缓冲输出流对象并和data.txt文件关联
        BufferedWriter bw = new BufferedWriter(new FileWriter("H:/data.txt"));

        String line = null;
        //创建键盘录入对象
        Scanner sc = new Scanner(System.in);

        for (int i = 1; i <= 3; i++) {
            System.out.println("请输入第"+i+"个数据:");
            //读取用户键盘录入的一行验证码信息
            line = sc.nextLine();
            bw.write(line);
            //写入换行符
            bw.newLine();
        }
        //关闭流释放资源
        bw.close();
    }
}

猜你喜欢

转载自blog.csdn.net/Huangyuhua068/article/details/81590345