字节流

InputStream

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

InputStream并不是一个接口,而是一个抽象类,它是所有输入流的超类,这个抽象定义了一个最重要的方法就是int read() 。这个方法会读取输入流的下一个字节,并返回字节表示的int值(0~255)。如果读到末尾,返回-1表示不能再读取了。

基本使用

FileInputStreamInputStream的一个子类,它可以从文件中读取数据。

复制

FileInputStream fileInputStream = new FileInputStream("test.txt");

// 读取文件中的内容,返回值是数据的下一个字节,如果到达文件末尾则为-1 。
int v1 = fileInputStream.read(); // 104
int v2 = fileInputStream.read(); // 101
int v3 = fileInputStream.read(); // 108
int v4 = fileInputStream.read(); // 108
int v5 = fileInputStream.read(); // 111

// 关闭资源
fileInputStream.close();

通过ASCII对照表可以得知,文件中存储的字符串是hello

上面的程序还存在一些问题,即在读取文件数据之前,我们须提前得到文件中的字符数量,如果字符比较多的情况下,就需要调用很多次read方法。此时我们可以通过循环来去读文件中的字符。

复制

FileInputStream fileInputStream = new FileInputStream("test.txt");

int temp;
while ((temp = fileInputStream.read()) != -1) {
   System.out.println((char)temp);
}

// 关闭流资源
fileInputStream.close()

异常处理

在读取或写入 IO 流的过程中,可能会发生错误,这些错误由 Java 虚拟机自动封装成IOException异常并抛出。因此,所有IO操作都必须正确处理IOException异常。

复制

FileInputStream fileInputStream = null;
try {
    fileInputStream = new FileInputStream("test.txt");
    int temp;
    while ((temp = fileInputStream.read()) != -1) {
        System.out.println((char) temp);
    }
} catch (IOException e) {
    System.out.println("文件读取出错");
} finally {
    if (fileInputStream != null) {
        fileInputStream.close();
    }
}

使用try...finally来处理上述代码会比较复杂,更好的写法是利用 Java7 引入的try(resource)语法,只需要编写try语句,让编译器自动来关闭资源。

复制

try (FileInputStream fileInputStream = new FileInputStream("test.txt")) {
    int temp;
    while ((temp = fileInputStream.read()) != -1) {
        System.out.println((char)temp);
    }
} catch (IOException e) {
    System.out.println("文件读取出错");
}

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

缓冲

在读取流资源的时候,一次一个字节的读取效率不高。很多流支持一次性读取多个字节到缓冲区,对于文件和网络流来说,利用缓冲区一次性读取多个字节效率往往要高很多。

复制

try (FileInputStream fileInputStream = new FileInputStream("test.txt")) {
    byte[] buffer = new byte[2];
    int temp;
    StringBuilder stringBuilder = new StringBuilder();
    while ((temp = fileInputStream.read(buffer)) != -1) {
        System.out.println("读取" + temp + "个字节");
        stringBuilder.append(new String(buffer, 0, temp));
    }
    System.out.println(stringBuilder);
} catch (IOException e) {
    System.out.println("文件读取出错");
}

中文字符乱码

当文本文件中出现中文字符时,会出现中文乱码问题,这是因为在utf-8字符编码下,一个中文字符占3个字节,所以按照字节读取时,容易把一个完整的文字劈开,所以会出现中文乱码问题。

无论是在 IO 的读取还是写入中,通过字节流来进行读写操作都会导致中文乱码的问题,要想解决问题,需要使用字符流ReaderWriter

ByteArrayInputStream

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

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

因为在使用FileInputStream时,就需要在本地磁盘中放一个真实的文本文件,而在测试的时候,往往没有这个文件,就可以用ByteArrayInputStream来模拟一个InputStream

复制

StringBuilder stringBuilder = new StringBuilder();
try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream("hello word".getBytes())) {
    byte[] buffer = new byte[5];
    int temp;
    while ((temp = byteArrayInputStream.read(buffer)) != -1) {
        stringBuilder.append(new String(buffer, 0, temp));
    }
    System.out.println(stringBuilder);
} catch (IOException e) {
    System.out.println("文件读取出错");
}

Outputstream

基本使用

InputStream类似,OutputStream也是抽象类,它是所有输出流的超类。OutputStream最重要的一个方法就是void write(int b)

复制

public abstract void write(int b) throws IOException;

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

复制

try (FileOutputStream fileOutputStream = new FileOutputStream("test_01.txt")) {
    byte[] bytes = "hello word".getBytes(StandardCharsets.UTF_8);
    fileOutputStream.write(bytes);
    // 刷新缓冲区,保证数据写入文件
    fileOutputStream.flush();
}

OutputStream提供了closeflush方法。

flush方法

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

通常情况下,我们不需要调用这个flush方法,因为缓冲区写满了OutputStream会自动调用它,并且在调用close方法关闭之前,也会自动调用flush方法。

手动调用flush的案例

例如你正在开发一个应用程序,它需要记录重要事件和信息,以便后续分析和监控,这个应用程序需要在实时性方面非常敏感,因为事件的记录需要尽快写入日志文件,以便管理员能够及时查看并采取必要的措施。

在这种情况下,你会使用OutputStream(通常是FileOutputStream)来写入日志文件。为了确保日志信息尽快写入磁盘,你会手动调用flush()方法,而不是等待缓冲区填满或等待自动刷新。这确保了日志事件的即时记录,即使应用程序在某个时间点崩溃,也可以确保已记录的事件不会丢失。

使用IO流进行文件拷贝

使用流进行文件拷贝使用缓冲进行文件拷贝

复制

try (FileInputStream fileInputStream = new FileInputStream("test.txt");
     FileOutputStream fileOutputStream = new FileOutputStream("test_01.txt")) {
    byte[] bytes = new byte[1024];
    int temp;
    while ((temp = fileInputStream.read(bytes)) != -1) {
        // write(数据, 起始索引, 写入数据的长度)
        fileOutputStream.write(bytes, 0, temp);
    }
}

最后更新于

这有帮助吗?