javajavase

缓冲流

引入目的

  • 作用:提升流的读取、写入的速度
  • 提高读写速度的原因:内部提供了一个缓冲区,默认情况下是8kb

缓冲流、转换流 - 图1
处理流与节点流的对比图示
缓冲流、转换流 - 图2
缓冲流、转换流 - 图3
缓冲流涉及到的类
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter

使用说明

  • 当读取数据时,数据按块读入缓冲区,其后的读操作则直接访问缓冲区
  • 当使用BufferedInputStream读取字节文件时,BufferedInputStream会一次性从文件中读取8192个(8Kb),存在缓冲区中,直到缓冲区装满了,才重新从文件中读取下一个8192个字节数组
  • 向流中写入字节时,不会直接写到文件,先写到缓冲区中直到缓冲区写满,BufferedOutputStream才会把缓冲区中的数据一次性写到文件里。使用方法flush()可以强制将缓冲区的内容全部写入输出流
  • 关闭流的顺序和打开流的顺序相反,只要关闭最外层流即可,关闭最外层流也会相应关闭内层节点流
  • flush()方法的使用:手动将buffer中内容写入文件
  • 如果是带缓冲区的流对象的close()方法,不但会关闭流,还会在关闭流之前刷新缓冲区,关闭后不能再写出

    使用BufferInputStream和BufferOutputStream实现文件的复制

    1. @Test
    2. public void BufferedStreamTest() throws FileNotFoundException {
    3. BufferedInputStream bis = null;
    4. BufferedOutputStream bos = null;
    5. try {
    6. // 1.造文件
    7. File srcFile = new File("爱情与友情.jpg");
    8. File destFile = new File("爱情与友情3.jpg");
    9. // 2.造流
    10. // 2.1 造节点流
    11. FileInputStream fis = new FileInputStream((srcFile));
    12. FileOutputStream fos = new FileOutputStream(destFile);
    13. // 2.2 造缓冲流
    14. bis = new BufferedInputStream(fis);
    15. bos = new BufferedOutputStream(fos);
    16. // 3.复制的细节:读取、写入
    17. byte[] buffer = new byte[10];
    18. int len;
    19. while((len = bis.read(buffer)) != -1) {
    20. bos.write(buffer, 0, len);
    21. // bos.flush(); // 刷新缓冲区
    22. }
    23. } catch (IOException e) {
    24. e.printStackTrace();
    25. } finally {
    26. // 4.资源关闭
    27. // 要求:先关闭外层的流,再关闭内层的流
    28. if(bos != null){
    29. try {
    30. bos.close();
    31. } catch (IOException e) {
    32. e.printStackTrace();
    33. }
    34. }
    35. if(bis != null){
    36. try {
    37. bis.close();
    38. } catch (IOException e) {
    39. e.printStackTrace();
    40. }
    41. }
    42. // 说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
    43. // fos.close();
    44. // fis.close();
    45. }
    46. }

    实现文件复制的方法 ```java public void copyFileWithBuffered(String srcPath, String destPath) { BufferedInputStream bis = null; BufferedOutputStream bos = null; try {

    1. File srcFile = new File(srcPath);
    2. File destFile = new File(destPath);
    3. FileInputStream fis = new FileInputStream((srcFile));
    4. FileOutputStream fos = new FileOutputStream(destFile);
    5. bis = new BufferedInputStream(fis);
    6. bos = new BufferedOutputStream(fos);
    7. byte[] buffer = new byte[1024];
    8. int len;
    9. while((len = bis.read(buffer)) != -1){
    10. bos.write(buffer,0,len);
    11. }

    } catch (IOException e) {

    1. e.printStackTrace();

    } finally {

    1. if(bos != null){
    2. try {
    3. bos.close();
    4. } catch (IOException e) {
    5. e.printStackTrace();
    6. }
    7. }
    8. if(bis != null){
    9. try {
    10. bis.close();
    11. } catch (IOException e) {
    12. e.printStackTrace();
    13. }
    14. }

    } }

@Test public void testCopyFileWithBuffered(){ long start = System.currentTimeMillis(); String srcPath = “C:\Users\Administrator\Desktop\01-视频.avi”; String destPath = “C:\Users\Administrator\Desktop\03-视频.avi”; copyFileWithBuffered(srcPath,destPath); long end = System.currentTimeMillis(); System.out.println(“复制操作花费的时间为:” + (end - start)); // 176 }

  1. <a name="xCWlf"></a>
  2. ### 使用BufferedReader和BufferedWriter实现文本文件的复制
  3. ```java
  4. @Test
  5. public void testBufferedReaderBufferedWriter(){
  6. BufferedReader br = null;
  7. BufferedWriter bw = null;
  8. try {
  9. br = new BufferedReader(new FileReader(new File("dbcp.txt")));
  10. bw = new BufferedWriter(new FileWriter(new File("dbcp1.txt")));
  11. // 读写操作
  12. // 方式一:使用char[]数组
  13. // char[] cbuf = new char[1024];
  14. // int len;
  15. // while((len = br.read(cbuf)) != -1) {
  16. // bw.write(cbuf, 0, len);
  17. // }
  18. // bw.flush();
  19. // 方式二:使用String
  20. String data;
  21. while((data = br.readLine()) != null){ //🔴
  22. //方法一:
  23. // bw.write(data + "\n"); // data中不包含换行符
  24. //方法二:
  25. bw.write(data); // data中不包含换行符
  26. bw.newLine(); // 🔴提供换行的操作
  27. }
  28. } catch (IOException e) {
  29. e.printStackTrace();
  30. } finally {
  31. if(bw != null){
  32. try {
  33. bw.close();
  34. } catch (IOException e) {
  35. e.printStackTrace();
  36. }
  37. }
  38. if(br != null){
  39. try {
  40. br.close();
  41. } catch (IOException e) {
  42. e.printStackTrace();
  43. }
  44. }
  45. }
  46. }

缓冲流实现复制操作

  1. public void copyFileWithBuffered(String srcPath,String destPath) {
  2. BufferedInputStream bis = null;
  3. BufferedOutputStream bos = null;
  4. try {
  5. File srcFile = new File(srcPath);
  6. File destFile = new File(destPath);
  7. FileInputStream fis = new FileInputStream((srcFile));
  8. FileOutputStream fos = new FileOutputStream(destFile);
  9. bis = new BufferedInputStream(fis);
  10. bos = new BufferedOutputStream(fos);
  11. byte[] buffer = new byte[1024];
  12. int len;
  13. while((len = bis.read(buffer)) != -1) {
  14. bos.write(buffer, 0, len);
  15. }
  16. } catch (IOException e) {
  17. e.printStackTrace();
  18. } finally {
  19. if(bos != null){
  20. try {
  21. bos.close();
  22. } catch (IOException e) {
  23. e.printStackTrace();
  24. }
  25. }
  26. if(bis != null){
  27. try {
  28. bis.close();
  29. } catch (IOException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. }
  34. }

练习

加密操作
将图片文件通过字节流读取到程序中
将图片的字节流逐一进行^5操作
将处理后的图片字节流输出

  1. @Test
  2. public void test1() {
  3. FileInputStream fis = null;
  4. FileOutputStream fos = null;
  5. try {
  6. fis = new FileInputStream("爱情与友情.jpg");
  7. fos = new FileOutputStream("爱情与友情secret.jpg");
  8. byte[] buffer = new byte[20];
  9. int len;
  10. while ((len = fis.read(buffer)) != -1) {
  11. //字节数组进行修改
  12. //错误的
  13. // for(byte b : buffer){
  14. // b = (byte) (b ^ 5);
  15. // }
  16. //正确的
  17. for (int i = 0; i < len; i++) {
  18. buffer[i] = (byte) (buffer[i] ^ 5);
  19. }
  20. fos.write(buffer, 0, len);
  21. }
  22. } catch (IOException e) {
  23. e.printStackTrace();
  24. } finally {
  25. if (fos != null) {
  26. try {
  27. fos.close();
  28. } catch (IOException e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. if (fis != null) {
  33. try {
  34. fis.close();
  35. } catch (IOException e) {
  36. e.printStackTrace();
  37. }
  38. }
  39. }
  40. }

解密操作
将加密后图片文件通过字节流读取到程序中
将图片的字节流逐一进行^操作(原理:A^B^B = A)
将处理后的图片字节流输出

  1. @Test
  2. public void test2() {
  3. FileInputStream fis = null;
  4. FileOutputStream fos = null;
  5. try {
  6. fis = new FileInputStream("爱情与友情secret.jpg");
  7. fos = new FileOutputStream("爱情与友情4.jpg");
  8. byte[] buffer = new byte[20];
  9. int len;
  10. while ((len = fis.read(buffer)) != -1) {
  11. for (int i = 0; i < len; i++) {
  12. buffer[i] = (byte) (buffer[i] ^ 5);
  13. }
  14. fos.write(buffer, 0, len);
  15. }
  16. } catch (IOException e) {
  17. e.printStackTrace();
  18. } finally {
  19. if (fos != null) {
  20. try {
  21. fos.close();
  22. } catch (IOException e) {
  23. e.printStackTrace();
  24. }
  25. }
  26. if (fis != null) {
  27. try {
  28. fis.close();
  29. } catch (IOException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. }
  34. }
  • 统计文本字符出现次数

实现思路:

  1. 遍历文本每一个字符
  2. 字符出现的次数存在Map中
  3. 把map中的数据写入文件

    1. @Test
    2. public void testWordCount() {
    3. FileReader fr = null;
    4. BufferedWriter bw = null;
    5. try {
    6. // 1.创建Map集合
    7. Map<Character, Integer> map = new HashMap<>();
    8. // 2.遍历每一个字符,每一个字符出现的次数放到map中
    9. fr = new FileReader("dbcp.txt");
    10. int c = 0;
    11. while ((c = fr.read()) != -1) {
    12. // int 还原 char
    13. char ch = (char) c;
    14. // 判断char是否在map中第一次出现
    15. if (map.get(ch) == null) {
    16. map.put(ch, 1);
    17. } else {
    18. map.put(ch, map.get(ch) + 1);
    19. }
    20. }
    21. // 3.把map中数据存在文件count.txt
    22. // 3.1 创建Writer
    23. bw = new BufferedWriter(new FileWriter("wordCount.txt"));
    24. // 3.2 遍历map,再写入数据
    25. Set<Map.Entry<Character, Integer>> entrySet = map.entrySet();
    26. for (Map.Entry<Character, Integer> entry : entrySet) {
    27. switch (entry.getKey()) {
    28. case ' ':
    29. bw.write("空格=" + entry.getValue());
    30. break;
    31. case '\t':
    32. bw.write("tab键=" + entry.getValue());
    33. break;
    34. case '\r':
    35. bw.write("回车=" + entry.getValue());
    36. break;
    37. case '\n':
    38. bw.write("换行=" + entry.getValue());
    39. break;
    40. default:
    41. bw.write(entry.getKey() + "=" + entry.getValue());
    42. break;
    43. }
    44. bw.newLine();
    45. }
    46. } catch (IOException e) {
    47. e.printStackTrace();
    48. } finally {
    49. if (fr != null) {
    50. try {
    51. fr.close();
    52. } catch (IOException e) {
    53. e.printStackTrace();
    54. }
    55. }
    56. if (bw != null) {
    57. try {
    58. bw.close();
    59. } catch (IOException e) {
    60. e.printStackTrace();
    61. }
    62. }
    63. }
    64. }

转换流

转换流提供了在字节流和字符流之间的转换,属于字符流

  • InputstreamReader 将Inputstream转换为Reader
  • OutputStreamWriter 将Writer转换为OutputStream
  • 字节流中的数据都是字符时,转成字符流操作更高效
  • 很多时候我们使用转换流来处理文件乱码问题。实现编码和解码的功能

缓冲流、转换流 - 图4
InputStreamReader

  • InputStreamReader将一个字节输入流转换为字符输入流

解码:字节、字节数组 —->字符数组、字符串

  • 构造器

**public InputStreamReader(InputStream in)**
**public InputStreamReader(Inputstream in,String charsetName)** 可以指定编码集
OutputStreamWriter

  • OutputStreamWriter将一个字符的输出流转换为字节的输出流

编码:字符数组、字符串 —-> 字节、字节数组

  • 构造器

**public OutputStreamWriter(OutputStream out)**
**public OutputStreamWriter(Outputstream out,String charsetName)** 可以指定编码集

  1. // 此时处理异常的话,仍然应该使用try-catch-finally
  2. // InputStreamReader的使用,实现字节的输入流到字符的输入流的转换
  3. @Test
  4. public void test1() throws IOException {
  5. FileInputStream fis = new FileInputStream("dbcp.txt");
  6. // InputStreamReader isr = new InputStreamReader(fis); //使用系统默认的字符集
  7. // 参数2指明了字符集,具体使用哪个字符集,取决于文件dbcp.txt当初保存时使用的字符集
  8. InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
  9. char[] cbuf = new char[20];
  10. int len;
  11. while((len = isr.read(cbuf)) != -1) {
  12. String str = new String(cbuf, 0, len);
  13. System.out.print(str);
  14. }
  15. isr.close();
  16. }
  17. // 此时处理异常的话,仍然应该使用try-catch-finally
  18. // 综合使用InputStreamReader和OutputStreamWriter
  19. @Test
  20. public void test2() throws Exception {
  21. // 1.造文件、造流
  22. File file1 = new File("dbcp.txt");
  23. File file2 = new File("dbcp_gbk.txt");
  24. FileInputStream fis = new FileInputStream(file1);
  25. FileOutputStream fos = new FileOutputStream(file2);
  26. InputStreamReader isr = new InputStreamReader(fis, "utf-8");
  27. OutputStreamWriter osw = new OutputStreamWriter(fos, "gbk");
  28. // 2.读写过程
  29. char[] cbuf = new char[20];
  30. int len;
  31. while((len = isr.read(cbuf)) != -1){
  32. osw.write(cbuf, 0, len);
  33. }
  34. // 3.关闭资源
  35. isr.close();
  36. osw.close();
  37. }

说明:文件编码的方式比如GBK,决定了解析时使用的字符集也只能是GBK

编码集

常见的编码表

  • ASCII:美国标准信息交换码,用一个字节的7位可以表示
  • ISO8859-1:拉丁码表,欧洲码表用一个字节的8位表示
  • GB2312:中国的中文编码表,最多两个字节编码所有字符
  • GBK:中国的中文编码表升级,融合了更多的中文文字符号。最多两个字节编码
  • Unicode:国际标准码,融合了目前人类使用的所字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示
  • UTF-8:变长的编码方式,可用1-4个字节来表示一个字符

缓冲流、转换流 - 图5
说明

  • 面向传输的众多UTF(UCS Transfer Format)标准出现了,顾名思义,UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位。这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。
  • Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯确定的编号,具体存储成什么样的字节流,取决于字符编码方案。推荐的Unicode编码是UTF-8和UTF-16。

UTF-8变长编码表示
缓冲流、转换流 - 图6
编码应用

  • 编码:字符串—>字节数组
  • 解码:字节数组—>字符串
  • 转换流的编码应用
    • 可以将字符按指定编码格式存储
    • 可以对文本数据按指定编码格式来解读
    • 指定编码表的动作由构造器完成

使用要求
客户端/浏览器端 <——> 后台(java,GO,Python,Node.js,php) <——> 数据库
要求前前后后使用的字符集都要统一:UTF-8