欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

缓冲流、转换流

程序员文章站 2022-04-03 19:35:29
...

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();
    }
}