zoukankan      html  css  js  c++  java
  • JavaSE学习笔记(二十六)—— IO流概述&字节流

    一、IO流概述

    1.1 什么是IO

      Java中I/O操作主要是指使用Java进行输入,输出操作。Java所有的I/O机制都是基于数据流进行输入输出,这些数据流表示了字符或者字节数据的流动序列。Java的I/O流提供了读写数据的标准方法。任何Java中表示数据源的对象都会提供以数据流的方式读写它的数据的方法。 

      IO又分为流IO(java.io)和块IO(java.nio)

      Java.io是大多数面向数据流的输入/输出类的主要软件包。此外,Java也对块传输提供支持,在核心库 java.nio中采用的便是块IO。

      流IO的好处是简单易用,缺点是效率较低。块IO效率很高,但编程比较复杂。

      这里先讲流IO。

    1.2 流的基本概念

      流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。

      在电脑上的数据有三种存储方式,一种是外存,一种是内存,一种是缓存。比如电脑上的硬盘,磁盘,U盘等都是外存,在电脑上有内存条,缓存是在CPU里面的。外存的存储量最大,其次是内存,最后是缓存,但是外存的数据的读取最慢,其次是内存,缓存最快。这里总结从外存读取数据到内存以及将数据从内存写到外存中。对于内存和外存的理解,我们可以简单的理解为容器,即外存是一个容器,内存又是另外一个容器。那又怎样把放在外存这个容器内的数据读取到内存这个容器?以及怎么把内存这个容器里的数据存到外存中呢?

      在Java类库中,IO部分的内容是很庞大的,因为它涉及的领域很广泛:

      标准输入输出,文件的操作,网络上的数据流,字符串流,对象流,zip文件流等等,java中将输入输出抽象称为流,就好像水管,将两个容器连接起来。将数据从外存中读取到内存中的称为输入流,将数据从内存写入外存中的称为输出流

    1.3 流的分类

      

    【根据流向分为输入流和输出流】

      注意输入流和输出流是相对于程序而言的。

      输出:把程序(内存)中的内容输出到磁盘、光盘等存储设备中
      
      输入:读取外部数据(磁盘、光盘等存储设备的数据)到程序(内存)中
      

      综合起来:

    根据传输数据单位分为字节流和字符流】

    • 字节流:数据流中最小的数据单元是字节 
    • 字符流:数据流中最小的数据单元是字符, Java中的字符是Unicode编码,一个字符占用两个字节(无论中文还是英文都是两个字节)。

       

      上面的也是 Java IO流中的四大基流。这四大基流都是抽象类,其他流都是继承于这四大基流的。 

      每种基类的子类都是以父类名作为后缀名:

        XxxOutputStream
        XxxInputStream
        XxxReader
        XxxWriter

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

      注意:一般我们在探讨IO流的时候,如果没有明确说明按哪种分类来说,默认情况下是按照数据类型来分的。

      什么情况下使用哪种流呢?
      如果数据所在的文件通过windows自带的记事本打开并能读懂里面的内容,就用字符流。其他用字节流。
      如果你什么都不知道,就用字节流。

    二、OutputStream字节流

      需求:我要往一个文本文件中输入一句话:"hello,io"

      分析:

        A:这个操作最好是采用字符流来做,但是呢,字符流是在字节流之后才出现的,所以,先讲解字节流如何操作。

        B:由于我是要往文件中写一句话,所以我们要采用字节输出流。

      通过上面的分析后我们知道要使用:OutputStream。

      但是通过查看API,我们发现该流对象是一个抽象类,不能实例化。所以,我们要找一个具体的子类。

      而我们要找的子类是什么名字的呢?这个时候,很简单,我们回想一下,我们是不是要往文件中写东西。文件是哪个单词?File。然后用的是字节输出流,联起来就是:FileOutputStream

    2.1 FileOutputStream 

    【构造方法】

    FileOutputStream(File file) 
    FileOutputStream(String name)

    【字节输出流操作步骤】

      A:创建字节输出流对象
      B:写数据
      C:释放资源

    public class FileOutputStreamDemo {
        public static void main(String[] args) throws IOException {
            // 创建字节输出流对象
            // FileOutputStream(File file)
            // File file = new File("fos.txt");
            // FileOutputStream fos = new FileOutputStream(file);
            // FileOutputStream(String name)
            FileOutputStream fos = new FileOutputStream("fos.txt");
            /*
             * 创建字节输出流对象了做了几件事情:
             * A:调用系统功能去创建文件
             * B:创建fos对象
             * C:把fos对象指向这个文件
             */
    
            // 写数据
            fos.write("hello,IO".getBytes());
            fos.write("java".getBytes());
    
            // 释放资源
            // 关闭此文件输出流并释放与此流有关的所有系统资源。
            fos.close();
            /*
             * 为什么一定要close()呢?
             * A:让流对象变成垃圾,这样就可以被垃圾回收器回收了
             * B:通知系统去释放跟该文件相关的资源
             */
        }
    }

      注意:程序中打开的文件 IO 资源不属于内存里的资源,垃圾回收机制无法回收该资源。如果不关闭该资源,那么磁盘的文件将一直被程序引用着,不能删除也不能更改。所以应该手动调用 close() 方法关闭流资源。

    【字节流写数据的方式】

    • public void write(int b):写一个字节
    • public void write(byte[] b):写一个字节数组
    • public void write(byte[] b,int off,int len):写一个字节数组的一部分
    public class FileOutputStreamDemo2 {
        public static void main(String[] args) throws IOException {
            // 创建字节输出流对象
            // OutputStream os = new FileOutputStream("fos2.txt");//多态
            FileOutputStream fos = new FileOutputStream("fos2.txt");
    
            // 调用write()方法
            // public void write(int b):写一个字节
            // fos.write(97);//a
    
            //public void write(byte[] b):写一个字节数组
            byte[] bys = {97, 98, 99, 100, 101};
            fos.write(bys);//abcde
    
            //public void write(byte[] b,int off,int len):写一个字节数组的一部分
            fos.write(bys, 1, 3);//bcd
    
            // 释放资源
            fos.close();
        }
    }

    【如何实现数据的换行】

       先看下面代码:

    public class FileOutputStreamDemo3 {
        public static void main(String[] args) throws IOException {
            // 创建字节输出流对象
            FileOutputStream fos = new FileOutputStream("fos3.txt");
    
            // 写数据
            for (int x = 0; x < 10; x++) {
                fos.write(("hello" + x).getBytes());
            }
    
            // 释放资源
            fos.close();
        }
    }

      我们发现写入的fos3.txt文件是没有换行的。为什么呢?因为你值写了字节数据,并没有写入换行符号。

      如何实现呢?写入换行符号即可。

      不同的系统针对不同的换行符号识别是不一样的?

        windows:
        linux:
        Mac:

      而一些常见的个高级记事本,是可以识别任意换行符号的。

      在上述代码的“写数据”操作中加入换行符号:

    // 写数据
    for (int x = 0; x < 10; x++) {
        fos.write(("hello" + x).getBytes());
        fos.write("
    ".getBytes());
    }

    【如何实现数据的追加写入】

      上面我们的写操作每执行一次,都是将原有的数据覆盖掉的。如果我们添加完一次数据后,想要在上一次数据的末尾添加新的数据,怎么做呢?

      我们可以用FileOutputStream的另一个构造方法:public FileOutputStream(File file, boolean append)

      第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处。

      如果将上面FileOutputStreamDemo3的构造方法修改为:

    // 创建一个向具有指定 name 的文件中写入数据的输出文件流。如果第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处。
    FileOutputStream fos = new FileOutputStream("fos3.txt", true);

      那么我们每次执行代码,都会在原有的数据末尾添加数据。

    【加入异常处理的字节输出流操作】

    public class FileOutputStreamDemo4 {
        public static void main(String[] args) {
            // 为了在finally里面能够看到该对象就必须定义到外面,为了访问不出问题,还必须给初始化值
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream("fos4.txt");
                fos.write("java".getBytes());
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                // 如果fos不是null,才需要close()
                if (fos != null) {
                    try {
                        // 为了保证close()一定会执行,就放到finally了
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    2.2 BufferedOutputStream

      通过定义数组的方式确实比以前一次读取一个字节的方式快很多,所以,看来有一个缓冲区还是非常好的。既然是这样的话,那么,java开始在设计的时候,它也考虑到了这个问题,就专门提供了带缓冲区的字节类。
    这种类被称为:缓冲区类(高效类)。

      写数据的高效类是:BufferedOutputStream

    【构造方法】

      BufferedOutputStream(OutputStream out) :创建一个新的缓冲输出流,以将数据写入指定的底层输出流。

      BufferedOutputStream(OutputStream out, int size) :创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流。

      构造方法可以指定缓冲区的大小,但是我们一般用不上,因为默认缓冲区大小就足够了。

    public class BufferedOutputStreamDemo {
        public static void main(String[] args) throws IOException {
            // BufferedOutputStream(OutputStream out)
            // FileOutputStream fos = new FileOutputStream("bos.txt");
            // BufferedOutputStream bos = new BufferedOutputStream(bos);
            // 简单写法
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));
    
            // 写数据
            bos.write("hello".getBytes());
    
            // 释放资源
            bos.close();
        }
    }

      为什么不传递一个具体的文件或者文件路径,而是传递一个OutputStream对象呢?
      原因很简单,字节缓冲区流仅仅提供缓冲区,为高效而设计的。但是呢,真正的读写操作还得靠基本的流对象实现。

    三、InputStream字节流

      需求:将文件的数据读取出来显示在控制台

    3.1 FileInputStream

    【字节输入流操作步骤】

      A:创建字节输入流对象
      B:调用read()方法读取数据,并把数据显示在控制台
      C:释放资源

    【读取数据的方式】

      需求:读取fis.txt文件的内容,并显示在控制台

      

    1. int read():一次读取一个字节
      public class FileInputStreamDemo {
          public static void main(String[] args) throws IOException {
              // FileInputStream(String name)
              FileInputStream fis = new FileInputStream("fis.txt");
      
              // 调用read()方法读取数据,并把数据显示在控制台
              // 第一次读取
              // int by = fis.read();
              // System.out.println(by);
              // System.out.println((char) by);
      
              // 第二次读取
              // by = fis.read();
              // System.out.println(by);
              // System.out.println((char) by);
      
              // 第三次读取
              // by = fis.read();
              // System.out.println(by);
              // System.out.println((char) by);
              // 我们发现代码的重复度很高,所以我们要用循环改进
              // 而用循环,最麻烦的事情是如何控制循环判断条件呢?
              // 第四次读取
              // by = fis.read();
              // System.out.println(by);
              // 第五次读取
              // by = fis.read();
              // System.out.println(by);
              // 通过测试,我们知道如果你读取的数据是-1,就说明已经读取到文件的末尾了
      
              // 用循环改进
              /*int by = fis.read();
              while (by != -1) {
                  System.out.print((char) by);
                  by = fis.read();
              }*/
      
              // 最终版代码
              int by = 0;
              // 读取,赋值,判断
              while ((by = fis.read()) != -1) {
                  System.out.print((char) by);
              }
      
              // 释放资源
              fis.close();
          }
      }
    2. int read(byte[] b):一次读取一个字节数组,返回值其实是实际读取的字节个数。
      public class FileInputStreamDemo2 {
          public static void main(String[] args) throws IOException {
              // 创建字节输入流对象
              FileInputStream fis = new FileInputStream("fis.txt");
      
              // 读取数据
              // 定义一个字节数组
              // 第一次读取
              byte[] bys = new byte[5];
              // int len = fis.read(bys);
              // System.out.println(len);//5
              // System.out.println(new String(bys));//hello
              // System.out.print(new String(bys, 0, len));//hello
      
              // 第二次读取
              // len = fis.read(bys);
              // System.out.println(len);//5
              // System.out.print(new String(bys, 0, len));//wor
      
              // 第三次读取
              // len = fis.read(bys);
              // System.out.println(len);
              // System.out.print(new String(bys, 0, len));
      
              // 第四次读取
              // len = fis.read(bys);
              // System.out.println(len);
              // System.out.println(new String(bys, 0, len));
              // System.out.print(new String(bys, 0, len));
      
              // 代码重复了,用循环改进
              // 但是,我不知道结束条件
              // len = fis.read(bys);
              // 如果读取到的实际长度是-1,就说明没有数据了
              // System.out.println(len);//-1
      
              // 循环
              int len = 0;
              while ((len = fis.read(bys)) != -1) {
                  // System.out.println(new String(bys, 0, len));
                  System.out.println(new String(bys));
              }
      
              // 释放资源
              fis.close();
          }
      }

       这时候控制台的输出结果如下:

        

      我们分析一下为什么会是这样的结果?

      每一行的末尾其实隐藏了 的换行符,自定义的字节数组长度为5,所以fis.txt可以这样表示:

    hello
    
    world
    
    java
    
    
    第一次:
    hello
    
    第二次:
    
    wor
    
    第三次:
    ld
    j
    
    第四次:
    ava
    j

      前三次的输出结果都容易理解,第四次的结果就有点迷惑了。其实是这样的,第四次获取时,len的长度是3了,而数组的长度是5,所以在覆盖数组的时候只覆盖了数组的前3个位置,剩下的2个位置还是上一次数据保留下来的。

      

      上面的代码中,我们将字节数组的长度定为5了。事实上,在实际开发中,我们一般将数组的长度定为1024或者1024的整数倍。代码改善如下:

    public class FileInputStreamDemo2 {
        public static void main(String[] args) throws IOException {
            // 创建字节输入流对象
            FileInputStream fis = new FileInputStream("fis.txt");
    
            // 读取数据
            // 定义一个字节数组
            // 最终版
            // 数组的长度一般是1024或者1024的整数倍
            byte[] bys = new byte[1024];
            int len = 0;
            while ((len = fis.read(bys)) != -1) {
                System.out.print(new String(bys, 0, len));
                // 千万要带上len的使用,因为字节数组的默认值是0,如果读取的文件的内容小于数组的长度,数组后面就会多很多0
                // 这些都是没有意义的数据,而这些数据又参与了转换,打印出来的数据也是无意义的,有可能会出现乱码问题
                // System.out.print(new String(bys));
            }
    
            // 释放资源
            fis.close();
        }
    }

    3.2 BufferedInputStream

    public class BufferedInputStreamDemo {
        public static void main(String[] args) throws IOException {
            // BufferedInputStream(InputStream in)
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bos.txt"));
    
            // 一次读取一个字节
            /*int by = 0;
            while ((by = bis.read()) != -1) {
                System.out.print((char) by);
            }*/
    
            // 一次读取一个字节数组
            byte[] bys = new byte[1024];
            int len = 0;
            while ((len = bis.read(bys)) != -1) {
                System.out.print(new String(bys, 0, len));
            }
    
            // 释放资源
            bis.close();
        }
    }

      注意:虽然我们有两种方式可以读取,但是,请注意,这两种方式针对同一个对象在一个代码中只能使用一个。

    四、字节流案例

    4.1 字节流复制数据

    【复制文本文件】

    /**
     * 分析:
     *  数据源:从哪里来
     *  a.txt    --    读取数据    --    FileInputStream
     *
     *  目的地:到哪里去
     *  b.txt    --    写数据        --    FileOutputStream
     */
    public class CopyFileDemo {
        public static void main(String[] args) throws IOException {
            // 封装数据源
            FileInputStream fis = new FileInputStream("a.txt");
            // 封装目的地
            FileOutputStream fos = new FileOutputStream("b.txt");
    
            // 一次读取一个字节
            /*int by = 0;
            while ((by = fis.read()) != -1) {
                fos.write(by);
            }*/
    
            // 一次读取一个字节数组
            byte[] bys = new byte[1024];
            int len = 0;
            while ((len = fis.read(bys)) != -1) {
                fos.write(bys, 0, len);
            }
    
            // 释放资源(先关谁都行)
            fos.close();
            fis.close();
        }
    }

      这样复制不会出现中文乱码问题,因为这是通过IO流读取数据,写到文本文件,你读取一个字节,我就写入一个字节,你没有做任何的转换。 它会自己做转换。

      那么,计算机是如何识别什么时候该把两个字节转换为一个中文呢?

    /*
     * 在计算机中中文的存储分两个字节:
     *         第一个字节肯定是负数。
     *         第二个字节常见的是负数,可能有正数。但是没影响。
     */
    public class StringDemo {
        public static void main(String[] args) {
            // String s = "abcde";
            // // [97, 98, 99, 100, 101]
    
            String s = "我爱你中国";
            // [-50, -46, -80, -82, -60, -29, -42, -48, -71, -6]
    
            byte[] bys = s.getBytes();
            System.out.println(Arrays.toString(bys));
        }
    }

    【复制图片】

      需求:把e:\a.jpg内容复制到当前项目目录下的b.jpg中

    /**
     * 数据源:
     *         e:\a.jpg    --读取数据--FileInputStream
     * 目的地:
     *         b.jpg--写出数据--FileOutputStream
     */
    public class CopyImageDemo {
        public static void main(String[] args) throws IOException {
            // 封装数据源
            FileInputStream fis = new FileInputStream("E:\a.jpg");
            // 封装目的地
            FileOutputStream fos = new FileOutputStream("b.jpg");
    
            // 复制数据
            // 一次读取一个字节
            /*int by = 0;
            while ((by = fis.read()) != -1) {
                fos.write(by);
            }*/
    
            // 一次读取一个字节数组
            byte[] bys = new byte[1024];
            int len = 0;
            while ((len = fis.read(bys)) != -1) {
                fos.write(bys,0,len);
            }
    
            // 释放资源
            fos.close();
            fis.close();
        }
    }

    【复制音频】

       需求:把e:\哥有老婆.mp4复制到当前项目目录下的copy.mp4中

    /*
     * 数据源:
     *         e:\哥有老婆.mp4--读取数据--FileInputStream
     * 目的地:
     *         copy.mp4--写出数据--FileOutputStream
     */
    public class CopyMp4Demo {
        public static void main(String[] args) throws IOException {
            // 封装数据源
            FileInputStream fis = new FileInputStream("e:\哥有老婆.mp4");
            // 封装目的地
            FileOutputStream fos = new FileOutputStream("copy.mp4");
    
            // 复制数据
            /*int by = 0;
            while ((by = fis.read()) != -1) {
                fos.write(by);
            }*/
    
            // 一次读取一个字节数组
            byte[] bys = new byte[1024];
            int len = 0;
            while ((len = fis.read(bys)) != -1) {
                fos.write(bys, 0, len);
            }
    
            // 释放资源
            fos.close();
            fis.close();
        }
    }

    4.2 字节缓冲流复制数据

    【复制音频】

       需求:把e:\哥有老婆.mp4复制到当前项目目录下的copy.mp4中 

    /*
     * 需求:把e:\哥有老婆.mp4复制到当前项目目录下的copy.mp4中
     * 
     * 字节流四种方式复制文件:
     * 基本字节流一次读写一个字节:    共耗时:117235毫秒
     * 基本字节流一次读写一个字节数组: 共耗时:156毫秒
     * 高效字节流一次读写一个字节: 共耗时:1141毫秒
     * 高效字节流一次读写一个字节数组: 共耗时:47毫秒
     */
    public class CopyMp4Demo {
        public static void main(String[] args) throws IOException {
            long start = System.currentTimeMillis();
            // method1("e:\哥有老婆.mp4", "copy1.mp4");
            // method2("e:\哥有老婆.mp4", "copy2.mp4");
            // method3("e:\哥有老婆.mp4", "copy3.mp4");
            method4("e:\哥有老婆.mp4", "copy4.mp4");
            long end = System.currentTimeMillis();
            System.out.println("共耗时:" + (end - start) + "毫秒");
        }
    
        // 高效字节流一次读写一个字节数组:
        public static void method4(String srcString, String destString)
                throws IOException {
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
                    srcString));
            BufferedOutputStream bos = new BufferedOutputStream(
                    new FileOutputStream(destString));
    
            byte[] bys = new byte[1024];
            int len = 0;
            while ((len = bis.read(bys)) != -1) {
                bos.write(bys, 0, len);
            }
    
            bos.close();
            bis.close();
        }
    
        // 高效字节流一次读写一个字节:
        public static void method3(String srcString, String destString)
                throws IOException {
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
                    srcString));
            BufferedOutputStream bos = new BufferedOutputStream(
                    new FileOutputStream(destString));
    
            int by = 0;
            while ((by = bis.read()) != -1) {
                bos.write(by);
    
            }
    
            bos.close();
            bis.close();
        }
    
        // 基本字节流一次读写一个字节数组
        public static void method2(String srcString, String destString)
                throws IOException {
            FileInputStream fis = new FileInputStream(srcString);
            FileOutputStream fos = new FileOutputStream(destString);
    
            byte[] bys = new byte[1024];
            int len = 0;
            while ((len = fis.read(bys)) != -1) {
                fos.write(bys, 0, len);
            }
    
            fos.close();
            fis.close();
        }
    
        // 基本字节流一次读写一个字节
        public static void method1(String srcString, String destString)
                throws IOException {
            FileInputStream fis = new FileInputStream(srcString);
            FileOutputStream fos = new FileOutputStream(destString);
    
            int by = 0;
            while ((by = fis.read()) != -1) {
                fos.write(by);
            }
    
            fos.close();
            fis.close();
        }
    }

      

    参考:http://www.cnblogs.com/furaywww/p/8849850.html

  • 相关阅读:
    Spring+MyBatis纯注解零XML整合(4)
    spring boot-mybatis全注解化(3)
    SpringBoot使用Mybatis注解进行一对多和多对多查询(2)
    Spring Boot中使用MyBatis注解配置详解(1)
    mysql 俩个时间相减后取分钟
    mysql加减时间-函数-时间加减
    十种常见排序算法(转)
    开源VS商用,IBM区块链从Hyperledger到商用平台之道 | 对话IBM高级架构师【 笔记】(转)
    java中正则表达式基本用法(转)
    《数学之美》——维特比和他维特比算法
  • 原文地址:https://www.cnblogs.com/yft-javaNotes/p/10885031.html
Copyright © 2011-2022 走看看