【Java学习笔记八】——I/O流之InputStream和OutputStream

声明:本文章内容主要摘选自尚硅谷宋红康Java教程、《Java核心卷二》、廖雪峰Java教程,示例代码部分出自本人,更多详细内容推荐直接观看以上教程及书籍,若有错误之处请指出,欢迎交流。

I/O流的笔记附带比较长的实例代码,所以分成几篇发。

1.InputStream

在JavaAPI中,可以从其中读入一个字节序列的对象称做输入流,而可以向其中写入一个字节序列的对象称做输出流。这些字节序列的来源地和目的地可以是文件,而且通常都是文件,但是也可以是网络连接,甚至是内存块。抽象类Inputstream和Outputstream构成了输入/输出(I/O)类层次结构的基础。

特别注意的一点是,InputStream并不是一个接口,而是一个抽象类,它是所有输入流的超类。这个抽象类定义的一个最重要的方法就是int read(),签名如下:

  public abstract int read() throws IOException;

这个方法会读取输入流的下一个字节,并返回字节表示的int值(0~255)。如果已读到末尾,返回-1表示不能继续读取了。

在计算机中,类似文件、网络端口这些资源,都是由操作系统统一管理的。应用程序在运行的过程中,如果打开了一个文件进行读写,完成后要及时地关闭,以便让操作系统把资源释放掉,否则,应用程序占用的资源会越来越多,不但白白占用内存,还会影响其他应用程序的运行。

InputStream和OutputStream都是通过close()方法来关闭流。关闭流就会释放对应的底层资源。

我们还要注意到在读取或写入IO流的过程中,可能会发生错误,例如,文件不存在导致无法读取,没有写权限导致写入失败,等等,这些底层错误由Java虚拟机自动封装成IOException异常并抛出。因此,所有与IO操作相关的代码都必须正确处理IOException。

因此,我们需要用try … finally来保证InputStream在无论是否发生IO错误的时候都能够正确地关闭:

public void readFile() throws IOException {
    InputStream input = null;
    try {
        input = new FileInputStream("src/readme.txt");
        int n;
        while ((n = input.read()) != -1) { // 利用while同时读取并判断
            System.out.println(n);
        }
    } finally {
        if (input != null) { input.close(); }
    }
}

用try … finally来编写上述代码会感觉比较复杂,更好的写法是利用Java 7引入的新的try(resource)的语法,只需要编写try语句,让编译器自动为我们关闭资源。推荐的写法如下:

public void readFile() throws IOException {
    try (InputStream input = new FileInputStream("src/readme.txt")) {
        int n;
        while ((n = input.read()) != -1) {
            System.out.println(n);
        }
    } // 编译器在此自动为我们写入finally并调用close()
}

实际上,编译器并不会特别地为InputStream加上自动关闭。编译器只看try(resource = …)中的对象是否实现了java.lang.AutoCloseable接口,如果实现了,就自动加上finally语句并调用close()方法。InputStream和OutputStream都实现了这个接口,因此,都可以用在try(resource)中。

在读取流的时候,一次读取一个字节并不是最高效的方法。很多流支持一次性读取多个字节到缓冲区,对于文件和网络流来说,利用缓冲区一次性读取多个字节效率往往要高很多。InputStream提供了两个重载方法来支持读取多个字节:

  int read(byte[] b):读取若干字节并填充到byte[]数组,返回读取的字节数
  int read(byte[] b, int off, int len):指定byte[]数组的偏移量和最大填充数

利用上述方法一次读取多个字节时,需要先定义一个byte[]数组作为缓冲区,read()方法会尽可能多地读取字节到缓冲区,但不会超过缓冲区的大小。read()方法的返回值不再是字节的int值,而是返回实际读取了多少个字节。如果返回-1,表示没有更多的数据了。

利用缓冲区一次读取多个字节的代码如下:

public void readFile() throws IOException {
    try (InputStream input = new FileInputStream("a.txt")) {
        // 定义1000个字节大小的缓冲区:
        byte[] buffer = new byte[1000];
        int n;
        while ((n = input.read(buffer)) != -1) { // 读取到缓冲区
            System.out.println("read " + n + " bytes.");
        }
    }
}

2.OutputStream

和InputStream相反,OutputStream是Java标准库提供的最基本的输出流。

和InputStream类似,OutputStream也是抽象类,它是所有输出流的超类。这个抽象类定义的一个最重要的方法就是void write(int b),签名如下:

  public abstract void write(int b) throws IOException;

这个方法会写入一个字节到输出流。要注意的是,虽然传入的是int参数,但只会写入一个字节,即只写入int最低8位表示字节的部分(相当于b & 0xff)。

和InputStream类似,OutputStream也提供了close()方法关闭输出流,以便释放系统资源。要特别注意:OutputStream还提供了一个flush()方法,它的目的是将缓冲区的内容真正输出到目的地。

为什么要有flush()?因为向磁盘、网络写入数据的时候,出于效率的考虑,操作系统并不是输出一个字节就立刻写入到文件或者发送到网络,而是把输出的字节先放到内存的一个缓冲区里(本质上就是一个byte[]数组),等到缓冲区写满了,再一次性写入文件或者网络。对于很多IO设备来说,一次写一个字节和一次写1000个字节,花费的时间几乎是完全一样的,所以OutputStream有个flush()方法,能强制把缓冲区内容输出。

3.阻塞

在调用InputStream的read()方法读取数据时,我们说read()方法是阻塞(Blocking)的。(和InputStream一样,OutputStream的write()方法也是阻塞的)它的意思是,对于下面的代码:

int n;
n = input.read(); // 必须等待read()方法返回才能执行下一行代码
int m = n;

执行到第二行代码时,必须等read()方法返回后才能继续。因为读取IO流相比执行普通代码,速度会慢很多,因此,无法确定read()方法调用到底要花费多长时间。

4.实现类

用FileInputStream可以从文件获取输入流,这是InputStream常用的一个实现类。

//节点流复制文件
    public void copyFile(String srcPath, String destPath) throws IOException {
        FileInputStream fis = null;
        FileOutputStream fos = null;

        try {
            File srcFile = new File(srcPath);
            File destFile = new File(destPath);

            fis = new FileInputStream(srcFile);             //InputStream实现类    
            fos = new FileOutputStream(destFile);           //OutputStream实现类

            byte[] data = new byte[1024];                   //缓存区,byte数组的大小可以自己取,越大则每次缓存的字节越多
            int len;
            while((len = fis.read(data)) != -1){
                fos.write(data, 0, len);
            }

        }catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fis != null){
                try{
                    fis.close();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
            if(fos != null){
                try{
                    fos.close();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
        }
    }
    @Test
    public void testCopyFile() throws IOException {
        //复制文件
        long start = System.currentTimeMillis();

        String srcPath = "D:\File1.0\1.flv";            //此地址为复制源,即想要复制的文件的地址
        String destPath = "D:\File1.0\3.flv";           //此地址为目标源,即想要创建的文件的地址,注意文件格式必须与被复制文件相同,无须提前创建文件
        copyFile(srcPath, destPath);

        long end = System.currentTimeMillis();

        System.out.println("复制操作花费的时间:" + (end - start));

    }

接下来顺便介绍一下缓冲流BufferedInputStream和BufferedOutputStream,缓冲流本身并不具有IO流的读取与写入功能,只是在别的流(节点流或其他处理流)上加上缓冲功能提高效率,就像是把别的流包装起来一样,因此缓冲流是一种处理流(包装流)。当对文件或者其他数据源进行频繁的读写操作时,效率比较低,这时如果使用缓冲流就能够更高效的读写信息。因为缓冲流是先将数据缓存起来,然后当缓存区存满后或者手动刷新时再一次性的读取到程序或写入目的地。

//利用缓冲流复制文件
    public void bufferedCopyFile(String srcPath, String destPath) throws IOException {
        FileInputStream fis;
        FileOutputStream fos;

        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        try{
            File srcFile = new File(srcPath);
            File destFile = new File(destPath);

            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);

            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);
            byte[] data = new byte[1024];
            int len;
            while((len = bis.read(data)) != -1){
                bos.write(data, 0, len);
            }

        }catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(bis != null){
                try{
                    bis.close();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
            if(bos != null){
                try{
                    bos.close();
                }catch(IOException e){
                    e.printStackTrace();
                }
            }
        }
    }
     @Test
    public void testCopyFile() throws IOException {
        //复制文件
        long start = System.currentTimeMillis();

        String srcPath = "D:\File1.0\1.flv";
        String destPath = "D:\File1.0\3.flv";
        bufferedCopyFile(srcPath, destPath);

        long end = System.currentTimeMillis();

        System.out.println("复制操作花费的时间:" + (end - start));//普通节点流耗时:1249    缓冲节点流耗时:316

    }

此笔记仅针对有一定编程基础的同学,且本人只记录比较重要的知识点,若想要入门Java可以先行观看相关教程或书籍后再阅读此笔记。

最后附一下相关链接:
Java在线API中文手册
Java platform se8下载
尚硅谷Java教学视频
《Java核心卷二》

原文地址:https://www.cnblogs.com/66ccffly/p/13420362.html