Java基础之IO基本流学习

简介

  • 概念

    流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。

  • 作用

    主要解决设备与设备之间的数据传输问题。 内存—>硬盘 硬盘—>内存

  • IO流选用标准

    1. 明确要操作的数据是数据源还是数据目的(也就是要读还是要写)

    2. 明确要操作的设备上的数据是字节还是文本

    3. 明确数据所在的具体设备

    4. 明确是否需要额外功能(比如是否需要转换流、高效流等)

分类

划分方式

  • 根据处理数据类型划分为:字符流和字节流

    字节流:以字节为单位,读写数据的流。
    字符流:以字符为单位,读写数据的流。

    字符流 = 字节流 + 解码

  • 根据数据流向划分为:输入流和输出流

    可以看做是一种数据的流动,按照流动的方向,以内存为基准,即流向内存是输入流,流出内存的输出流

    序列化:将一个具体的对象的数据转换为一堆字节数据的过程。

    反序列化:将一堆字节数据转换为一个具体对象实例的过程。

  • 命名规范

    IO流命名方式

    由这四个类的子类名称基本都是以其父类名作为子类名的后缀。

    如:InputStream的子类FileInputStream。
    如:Reader的子类FileReader。

  • 类层次图

    输入流和输出流的类层次图整理版

  • 跨服务传输流向过程分析

    跨服务传输IO流向说明

介绍

字节流OutputStream与InputStream

  • OutputStream与InputStream类图

    在操作流的时候,我们要时刻明确,无论使用什么样的流对象,底层传输的始终为二进制数据

    OutputStream与InputStream类图

字节输出流OutputStream

  • 定义

    java.io.OutputStream 抽象类是表示字节输出流的所有类的超类(父类),将指定的字节信息写出到目的地

  • 共性方法

    
    
        public void close() // 关闭此输出流并释放与此流相关联的任何系统资源。
        public void flush()  // 刷新此输出流并强制任何缓冲的输出字节被写出。
        public void write(byte[] b) // 将 b.length个字节从指定的字节数组写入此输出流。
        public void write(byte[] b, int off, int len)  // 从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。 也就是说从off个字节数开始读取一直到len个字节结束
        public abstract void write(int b) // 将指定的字节输出流。
    
    
    
文件输出流FileOutputStream
  • 定义

    OutputStream有很多子类,我们从最简单的一个子类FileOutputStream开始。用于将数据写出到文件

  • 构造方法

    只要是对象,就从构造方法开始学。

    
    
        public FileOutputStream(File file) // 根据File对象为参数创建对象。 
    
        // 推荐使用
        public FileOutputStream(String name) // 根据名称字符串为参数创建对象。
    
        public FileOutputStream(File file, boolean append)
    
        public FileOutputStream(String name, boolean append)
    
    
    
    
  • 注意事项

    
        // 创建字节输出流主要做了3件事:
        // 1. 调用系统功能去创建文件(输出流对象才会自动创建)
        // 2. 创建OutputStream对象
        // 3. 把outputStream对象指向这个文件
        FileOutputStream out = new FileOutputStream("E:\\Blog\\202010131719.txt");
    
    

    在指定路径下,如果没有这个文件,会创建该文件。如果有这个文件,会清空这个文件的数据

  • FileOutputStream写出字节数据

    使用FileOutputStream写出字节数据主要通过Write方法,而write方法分如下三种

    
        public void write(int b)
        public void write(byte[] b)
        public void write(byte[] b,int off,int len)  //从`off`索引开始,`len`个字节
    
    
        /**
        * @Author charlesYan
        * @Description //FileOutputStream写出字节数据
        * @Date 17:24 2020/10/23
        * @Param []
        * @return void
        **/
        @Test
        public void testFileOutputStreamWriteByte() throws Exception{
    
            // 创建字节输出流主要做了3件事:
            // 1. 调用系统功能去创建文件(输出流对象才会自动创建)
            // 2. 创建OutputStream对象
            // 3. 把outputStream对象指向这个文件
            FileOutputStream out = new FileOutputStream("E:\\Blog\\202010131719.txt");
    
            out.write(98);
            out.write(99);
            out.close();
        }
    
    
        /**
        * @Author charlesYan
        * @Description //FileOutputStream写出字节数组
        * @Date 17:57 2020/10/23
        * @Param []
        * @return void
        **/
        @Test
        public void testFileOutputStreamWriteByteArray() throws Exception {
    
            FileOutputStream out = new FileOutputStream("E:\\Blog\\202010131719.txt");
            // 字符串转换为字节数组
            byte[] bytes = "加班使我快乐".getBytes();
            // 写出字节数组
            out.write(bytes);
            //关闭资源
            out.close();
        }
    
    
        /**
        * @Author charlesYan
        * @Description //写出指定长度数组
        * @Date 18:08 2020/10/23
        * @Param []
        * @return void
        **/
        @Test
        public void testFileOutputStreamWriteByteArrayLength() throws Exception{
    
            FileOutputStream out = new FileOutputStream("E:\\Blog\\202010131719.txt");
            // 字符串转换为字节数组
            byte[] bytes = "加班使我快乐".getBytes();
            // 写出指定长度数组:从索引0开始,3个字节,也就是 加
            out.write(bytes,0,3);
            out.close();
        }
    
    
    
    
  • FileOutputStream追加续写数据

    回车符\r和换行符\n :

    回车符:回到一行的开头(return)。
    换行符:下一行(newline)。
    

    系统中的换行:

    Windows系统里,每行结尾是 回车+换行 ,即\r\n;
    Unix系统里,每行结尾只有 换行 ,即\n;
    Mac系统里,每行结尾是 回车 ,即\r。从 Mac OS X开始与Linux统一
    
    
    
    
    
        /**
        * @Author charlesYan
        * @Description //FileOutputStream实现数据追加续写
        * @Date 18:15 2020/10/23
        * @Param []
        * @return void
        **/
        @Test
        public void testFileOutputStreamWriteAppend() throws Exception{
    
            FileOutputStream out = new FileOutputStream("E:\\Blog\\202010131719.txt",true);
            // 字符串转换为字节数组
            byte[] bytes = "加班使我快乐".getBytes();
            // 写出指定长度数组:从索引3开始,3个字节,也就是 班
            out.write(bytes,3,3);
            out.close();
        }
    
        /**
        * @Author charlesYan
        * @Description //FileOutputStream实现数据追加续写换行
        * @Date 18:29 2020/10/23
        * @Param []
        * @return void
        **/
        @Test
        public void testFileOutputStreamWriteLineFeed() throws Exception{
    
            FileOutputStream out = new FileOutputStream("E:\\Blog\\202010131719.txt",true);
            // 字符串转换为字节数组
            byte[] bytes = "\r\n".getBytes();
            // 写出指定长度数组:从索引3开始,3个字节,也就是 班
            byte[] contentBytes = "加班使我快乐".getBytes();
            out.write(bytes);
            out.write(contentBytes,6,3);
            out.close();
        }
        
    
    

字节输入流InputStream

  • 定义

    java.io.InputStream 抽象类是表示字节输入流的所有类的超类(父类),可以读取字节信息到内存中

  • 共性方法

    
    
        public void close() // 关闭此输入流并释放与此流相关联的任何系统资源。
    
        public abstract int read() // 从输入流读取数据的下一个字节。
    
        public int read(byte[] b) // 该方法返回的int值代表的是读取了多少个字节,读到几个返回几个,读取不到返回-1
    
    
    
文件输入流FileInputStream
  • 定义

    java.io.FileInputStream类是文件输入流,从文件中读取字节。

  • 构造方法

    
    
        FileInputStream(File file) // 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
    
        // 推荐使用
        FileInputStream(String name) // 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名name命名。
    
    
    
    
  • 注意事项

    当创建一个流对象时,必须传入一个文件路径。该路径下,如果没有该文件会抛出FileNotFoundException。

  • FileInputStream读取字节数据

    
      
    
        /**
        * @Author charlesYan
        * @Description //FileInputSteam读取字节:read方法,每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回-1
        * @Date 10:23 2020/10/26
        * @Param []
        * @return void
        **/
        @Test
        public void testFileInputStreamReadByte() throws Exception {
    
            FileInputStream in = new FileInputStream("E:\\Blog\\202011121615.txt");
    
            // 读取数据,返回一个字节
            int read = in.read();
            System.out.println("读取原数据内容:" + read);
            System.out.println("转换原数据内容:" + (char)read);
            in.close();
    
    
        }
    
    
    
        /**
        * @Author charlesYan
        * @Description // 循环改进读取方式
        * @Date 16:25 2020/11/12
        * @Param []
        * @return void
        **/
        @Test
        public void testFileInputStreamCycleReadByte() throws Exception {
    
            // 使用文件名称创建流对象
            FileInputStream in = new FileInputStream("E:\\Blog\\202011121615.txt");
    
            // 定义变量,保存数据
            int b;
    
            // 循环读取,读取中文时,打印乱码(字节流读取中文字符时,可能不会显示完整的字符,那是因为一个中文字符占用多个字节存储。)
            while ((b = in.read()) != -1) {
                System.out.println((char)b);
            }
    
            // 关闭资源
            in.close();
        }
    
    
        /**
        * @Author charlesYan
        * @Description //使用字节数组读取:read(byte[] b),每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回-1
        * @Date 16:42 2020/11/12
        * @Param []
        * @return void
        **/
        @Test
        public void testFileInputStreamCycleReadByteArray() throws Exception {
    
            // 使用文件名称创建流对象
            FileInputStream in = new FileInputStream("E:\\Blog\\202011121615.txt");
    
            // 定义变量,作为有效个数
            int len;
    
            // 定义字节数组,作为装字节数据的容器
            byte[] b = new byte[2];
    
            // 循环读取 
            while (((len = in.read(b)) != -1)) { 
                // 每次读取后,把数组变成字符串打印
                System.out.println(new String(b));
            }
    
            // 关闭资源
            in.close();
    
        }
    
        // 注意事项:由于202011121615.txt文件中内容为abc123e,而错误数据3,是由于最后一次读取时,只读取一个字节e,数组中,上次读取的数据没有被完全替换
    
    
        /**
        * @Author charlesYan
        * @Description //优化使用字节数组读取,只读取数组的有效字节部分
        * @Date 17:20 2020/11/12
        * @Param []
        * @return void
        **/
        @Test
        public void testFileInputStreamCycleReadByteArrayByOptimization() throws Exception {
    
            // 使用文件名称创建流对象
            FileInputStream in = new FileInputStream("E:\\Blog\\202011121615.txt");
    
            // 定义变量,作为有效个数
            int len;
    
            // 定义字符数组,用于存读取字节数据
            byte[] b = new byte[2];
    
            // 循环读取
            while ((len = in.read(b)) != -1) {
                // 只获取每次读取的有效长度
                System.out.println(new String(b,0,len));
            }
    
            // 关闭资源
            in.close();
    
        }
    
    
    
        /**
        * @Author charlesYan
        * @Description //实际工作中强烈推荐使用数组读取文件,jdk1.7以后捕获异常关流写法
        * @Date 18:01 2020/11/12
        * @Param []
        * @return void
        **/
        @Test
        public void testFileInputStreamCycleReadByteByWork() {
    
            // 使用文件名创建流对象,在java1.7以后版本中try
            try (FileInputStream in = new FileInputStream("E:\\Blog\\202011121615.txt")) {
    
                // 定义变量,作为有效个数
                int len;
    
                // 定义字符数组,用于存储读取的字节数据
                byte[] b = new byte[1024];
    
                // 循环读取
                while ((len = in.read(b)) != -1) {
                    // 只获取每次读取的长度
                    System.out.println(new String(b,0,len));
                }
    
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
    
    
    
    
  • FileInputStream读取字节数据

    
    
      /**
      * @Author charlesYan
      * @Description //复制文件
      * @Date 18:17 2020/11/12
      * @Param []
      * @return void
      **/
      @Test
      public void testCopyImage() throws Exception {
    
          // 1.创建流对象
          // 1.1 指定数据源
          FileInputStream fis = new FileInputStream("E:\\Blog\\202011121615.txt");
          // 1.2 指定目的地
          FileOutputStream fos = new FileOutputStream("E:\\Blog\\202011121615_copy.txt");
    
          // 2.读写数据
          // 2.1 定义数组
          byte[] b = new byte[1024];
          // 2.2 定义长度
          int len;
          // 2.3 循环读取
          while ((len = fis.read(b)) != -1) {
              fos.write(b,0,len);
          }
    
          // 3.关闭流
          fos.close();// 先关闭输出流
          fis.close();
      }
    
    
    
    

字符流Reader和Writer

  • 字符流Reader和Writer继承类图

    Reader和Writer类图

  • 字符流起源

    因为数据编码的不同,因而有了对字符进行高效操作的流对象,字符流本质其实就是基于字节流读取时,去查了指定的码表,而字节流直接读取数据会有乱码的问题(读中文会乱码)

    字符流 = 字节流 + 编码表

  • 字节流读取中文

    
    
      /**
      * @Author charlesYan
      * @Description //测试文件输入流读取中文
      * @Date 9:34 2020/11/13
      * @Param []
      * @return void
      **/
      @Test
      public void testFileInputStreamCycleReadChinese() throws Exception{
    
          // 创建流对象
          FileInputStream fis = new FileInputStream("E:\\Blog\\202011121615_copy.txt");
    
          // 初始化字节数组用于存读取数据
          byte[] b = new byte[1024];
    
          // 读取长度
          int len;
    
          // 循环读取
          while ((len = fis.read(b)) != -1) {
              System.out.println(new String(b,0,len));
          }
    
          fis.close();
    
        
    
      
    
      }
    
    
    
  • 分析中文正常读取原因

    读取的文件的编码格式为UTF-8,IDEA中设置的项目的编码格式也是UTF-8

    
      System.out.println(Charset.defaultCharset()); // IDEA中设置项目的编码格式,tomcat设置的编码格式,是配置的语言编码
      System.out.println(System.getProperty("file.encoding")); // file.encoding是唯一的,与main入口函数所在的java类文件编码保持一致
      
      // 使用String的有参构造方法
      String str = new String("hhhh ty智障%shfu摸淑芬十分uif内服NSF黑");
      // 1.以GBK编码方式获取str的字节数组,再用String有参构造函数构造字符串
      System.out.println(new String(str.getBytes("GBK")));
      // 2.以UTF-8编码方式获取str的字节数组,再以默认编码构造字符串
      System.out.println(new String(str.getBytes("UTF-8")));
    
      // new String源码分析:底层new String()的源码,String构造方法有解码功能,并且默认编码通过获取配置的编码,如果为空则是ISO-8859-1
      this.value = StringCoding.decode(bytes, offset, length);
      String csn = Charset.defaultCharset().name();
    
    
    
    
  • 选用场景

    尽管字节流也能有办法决绝乱码问题,但是还是比较麻烦,于是java就有了字符流,字符为单位读写数据,字符流专门用于处理文本文件。如果处理纯文本的数据优先考虑字符流,其他情况就只能用字节流了(图片、视频、等等只文本例外)

字符输入流Reader

  • 定义

    java.io.Reader抽象类是字符输入流的所有类的超类(父类),可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。

  • 共性方法

    
      public void close() :关闭此流并释放与此流相关联的任何系统资源。
      public int read(): 从输入流读取一个字符。
      public int read(char[] cbuf): 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中
    
    
    
    
FileReader类
  • 定义

    java.io.FileReader类是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区

  • 构造方法

    
    
      FileReader(File file): 创建一个新的 FileReader ,给定要读取的File对象。
    
      FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的字符串名称。
    
    
    
    
  • FileReader读取字符数据

    
      /**
      * @Author charlesYan
      * @Description //字符输入流读取字符 - 读取字符:read方法,每次可以读取一个字符的数据,提升为int类型,读取到文件末尾,返回-1,循环读取
      * @Date 11:28 2020/11/13
      * @Param []
      * @return void
      **/
      @Test
      public void testFileReaderReadCharacter() throws Exception{
    
          // 使用文件名创建字符输入流对象
          FileReader reader = new FileReader("E:\\Blog\\202011121615.txt");
    
          // 定义变量,存储读取的数据
          int b;
    
          // 循环读取
          while ((b = reader.read()) != -1) {
              System.out.println((char)b);
          }
    
          // 关闭资源
          reader.close();
      }
    
    

字符输出流Writer

  • 定义

    java.io.Writer抽象类是字符输出流的所有类的超类(父类),将指定的字符信息写出到目的地。

  • 共性方法

    
      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() 关闭此流,但要先刷新它。
    
    
    
FileWriter类
  • 定义

    java.io.FileWriter类是写出字符到文件的便利类构造时使用系统默认的字符编码和默认字节缓冲区。

  • 构造方法

    
      FileWriter(File file): 创建一个新的 FileWriter,给定要读取的File对象。
      FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件的名称。
    
    
    
  • 注意事项

    1. 关闭close和刷新flush

    因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush 方法了。

    
    
      
    
      flush:刷新缓冲区,流对象可以继续使用。
      close:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
    
    
    
    
    1. 字符流,只能操作文本文件,不能操作图片,视频等非文本文件。当我们单纯读或者写文本文件时 使用字符流 其他情况使用字节流
  • FileWriter写出字符数据

    
      /**
      * @Author charlesYan
      * @Description //字符输出流写出字符
      * @Date 15:22 2020/11/13
      * @Param []
      * @return void
      **/
      @Test
      public void testFileWriterWriteCharacter() throws Exception{
    
          // 使用文件名创建流对象
          FileWriter writer = new FileWriter("E:\\Blog\\202011131518.txt");
    
          // 写出数据
          writer.write(97); // 写出第1个字符
          writer.write("晏"); // 写出第2个字符
    
          // 关闭资源时,与FileOutputStream不同。如果不关闭,数据只是保存到缓冲区,并非保存到文件。
          writer.close();
      }
    
    
    
      /**
      * @Author charlesYan
      * @Description //字符输出流写出字符
      * @Date 15:22 2020/11/13
      * @Param []
      * @return void
      **/
      @Test
      public void testFileWriterWriteCharacter() throws Exception{
    
          // 使用文件名创建流对象
          FileWriter writer = new FileWriter("E:\\Blog\\202011131518.txt");
    
          // 写出数据
          writer.write(97); // 写出第1个字符
          writer.write("晏"); // 写出第2个字符
    
          // 关闭资源时,与FileOutputStream不同。如果不关闭,数据只是保存到缓冲区,并非保存到文件。
          writer.close();
      }
    
      /**
      * @Author charlesYan
      * @Description // 使用字符流进行文本文件复制
      * @Date 15:43 2020/11/13
      * @Param []
      * @return void
      **/
      @Test
      public void testCharacterFileCopy() throws Exception{
    
          // 根据文件名创建字符输入输出流
          FileReader reader = new FileReader("E:\\Blog\\202011131518.txt");
    
          /*创建输出流做的工作:
          *      1、调用系统资源创建了一个文件
          *      2、创建输出流对象
          *      3、把输出流对象指向文件
          * */
          FileWriter writer = new FileWriter("E:\\Blog\\202011131518_copy.txt");
    
          //一次读取一个字符进行文本复制 4毫秒
          copyMethodByCharacter(reader,writer);
    
          //一次读取一个字符数组 1毫秒
          copyMethodByCharacterArray(reader,writer);
    
    
          reader.close();
    
          writer.close();
    
    
      }
    
      private void copyMethodByCharacterArray(FileReader reader, FileWriter writer) throws Exception {
    
          System.out.println("start time:" + System.currentTimeMillis());
          // 定义字符数组
          char[] chs = new char[1024];
    
          // 定义长度
          int len;
    
          while ((len = reader.read(chs)) != -1) {
    
              writer.write(chs,0,len);
          }
    
          writer.flush();
    
          System.out.println("end time:" + System.currentTimeMillis());
    
    
    
      }
    
    
      private void copyMethodByCharacter(FileReader reader, FileWriter writer) throws Exception {
    
          System.out.println("start time:" + System.currentTimeMillis());
    
          int len;
    
          while ((len = reader.read()) != -1) {
    
              writer.write(len);
          }
    
          writer.flush();
    
          System.out.println("end time:" + System.currentTimeMillis());
      }
    
    
    
    
    
    

区别

  • 实现方式

    字符流本质上就是基于字节流,读取时查询相应的码表。

  • 读写单位不同

    字节流以字节为单位;字符流以字符为单位,一次可能读多个字节

  • 处理对象不同

    字节流能处理所有类型的数据(图片、视频等),而字符流只能处理字符类型的数据(文本数据优先考虑字符流,其他情况都用字节流)。

参考链接

  • 史上最骚最全最详细的IO流教程,没有之一!

    https://www.cnblogs.com/yichunguo/p/11775270.html

  • IO流

    https://www.jianshu.com/p/b274fe091048

  • Java 常用IO流操作详解

    https://blog.csdn.net/MAGIC_JSS/article/details/51475923

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值