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:\r\n
    linux:\n
    Mac:\r

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

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

// 写数据
for (int x = 0; x < 10; x++) {
    fos.write(("hello" + x).getBytes());
    fos.write("\r\n".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();
        }
    }

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

    

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

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

hello\r\n
world\r\n
java


第一次:
hello

第二次:
\r\nwor

第三次:
ld\r\nj

第四次:
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

posted @ 2019-05-18 17:37  yi0123  阅读(378)  评论(0编辑  收藏  举报