阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

InputStream

26次阅读
没有评论

共计 4363 个字符,预计需要花费 11 分钟才能阅读完成。

InputStream就是 Java 标准库提供的最基本的输入流。它位于 java.io 这个包里。java.io包提供了所有同步 IO 的功能。

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

public abstract int read() throws IOException;

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

FileInputStreamInputStream 的一个子类。顾名思义,FileInputStream就是从文件流中读取数据。下面的代码演示了如何完整地读取一个 FileInputStream 的所有字节:

public void readFile() throws IOException {// 创建一个 FileInputStream 对象:
    InputStream input = new FileInputStream("src/readme.txt");
    for (;;) {int n = input.read(); // 反复调用 read()方法,直到返回 -1
        if (n == -1) {break;
        }
        System.out.println(n); // 打印 byte 的值
    }
    input.close(); // 关闭流
}

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

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

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

仔细观察上面的代码,会发现一个潜在的问题:如果读取过程中发生了 IO 错误,InputStream就没法正确地关闭,资源也就没法及时释放。

因此,我们需要用 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() 方法。InputStreamOutputStream 都实现了这个接口,因此,都可以用在 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("src/readme.txt")) {// 定义 1000 个字节大小的缓冲区:
        byte[] buffer = new byte[1000];
        int n;
        while ((n = input.read(buffer)) != -1) {// 读取到缓冲区
            System.out.println("read" + n + "bytes.");
        }
    }
}

阻塞

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

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

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

InputStream 实现类

FileInputStream 可以从文件获取输入流,这是 InputStream 常用的一个实现类。此外,ByteArrayInputStream可以在内存中模拟一个InputStream

import java.io.*;

public class Main {public static void main(String[] args) throws IOException {byte[] data = { 72, 101, 108, 108, 111, 33 };
        try (InputStream input = new ByteArrayInputStream(data)) {int n;
            while ((n = input.read()) != -1) {System.out.println((char)n);
            }
        }
    }
}

ByteArrayInputStream实际上是把一个 byte[] 数组在内存中变成一个InputStream,虽然实际应用不多,但测试的时候,可以用它来构造一个InputStream

举个例子:我们想从文件中读取所有字节,并转换成 char 然后拼成一个字符串,可以这么写:

public class Main {public static void main(String[] args) throws IOException {
        String s;
        try (InputStream input = new FileInputStream("C:\\test\\README.txt")) {int n;
            StringBuilder sb = new StringBuilder();
            while ((n = input.read()) != -1) {sb.append((char) n);
            }
            s = sb.toString();}
        System.out.println(s);
    }
}

要测试上面的程序,就真的需要在本地硬盘上放一个真实的文本文件。如果我们把代码稍微改造一下,提取一个 readAsString() 的方法:

public class Main {public static void main(String[] args) throws IOException {
        String s;
        try (InputStream input = new FileInputStream("C:\\test\\README.txt")) {s = readAsString(input);
        }
        System.out.println(s);
    }

    public static String readAsString(InputStream input) throws IOException {int n;
        StringBuilder sb = new StringBuilder();
        while ((n = input.read()) != -1) {sb.append((char) n);
        }
        return sb.toString();}
}

对这个 String readAsString(InputStream input) 方法进行测试就相当简单,因为不一定要传入一个真的FileInputStream

import java.io.*;

public class Main {public static void main(String[] args) throws IOException {byte[] data = { 72, 101, 108, 108, 111, 33 };
        try (InputStream input = new ByteArrayInputStream(data)) {String s = readAsString(input);
            System.out.println(s);
        }
    }

    public static String readAsString(InputStream input) throws IOException {int n;
        StringBuilder sb = new StringBuilder();
        while ((n = input.read()) != -1) {sb.append((char) n);
        }
        return sb.toString();}
}

这就是面向抽象编程原则的应用:接受 InputStream 抽象类型,而不是具体的 FileInputStream 类型,从而使得代码可以处理 InputStream 的任意实现类。

小结

Java 标准库的 java.io.InputStream 定义了所有输入流的超类:

  • FileInputStream实现了文件流输入;
  • ByteArrayInputStream在内存中模拟一个字节流输入。

总是使用 try(resource) 来保证 InputStream 正确关闭。

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-08-05发表,共计4363字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中