IO流,文件操作

参考

Java IO 基础知识总结 | JavaGuide

史上最骚最全最详细的IO流教程,没有之一! - 宜春 - 博客园

零、io-流简介

IO 即 Input/Output,输入和输出。数据输入到计算机内存的过程即输入,反之输出到外部存储(比如数据库,文件,远程主机)的过程即输出。数据传输过程类似于水流,因此称为 IO 流。IO 流在 Java 中分为输入流和输出流,而根据数据的处理方式又分为字节流和字符流。

Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。

  • InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。

一、字节流

1. InputStream(字符输入流)

InputStream 常用方法:

  • read():返回输入流中下一个字节的数据。返回的值介于 0 到 255 之间。如果未读取任何字节,则代码返回 -1 ,表示文件结束。
  • read(byte b[ ]) : 从输入流中读取一些字节存储到数组 b 中。如果数组 b 的长度为零,则不读取。如果没有可用字节读取,返回 -1。如果有可用字节读取,则最多读取的字节数最多等于 b.length , 返回读取的字节数。这个方法等价于 read(b, 0, b.length)
  • read(byte b[], int off, int len):在read(byte b[ ]) 方法的基础上增加了 off 参数(偏移量)和 len 参数(要读取的最大字节数)。
  • skip(long n):忽略输入流中的 n 个字节 ,返回实际忽略的字节数。
  • available():返回输入流中可以读取的字节数。
  • close():关闭输入流释放相关的系统资源。

从 Java 9 开始,InputStream 新增加了多个实用的方法:

  • readAllBytes():读取输入流中的所有字节,返回字节数组。
  • readNBytes(byte[] b, int off, int len):阻塞直到读取 len 个字节。
  • transferTo(OutputStream out):将所有字节从一个输入流传递到一个输出流。

FileInputStream 是一个比较常用的字节输入流对象,可直接指定文件路径,可以直接读取单字节数据,也可以读取至字节数组中。

FileInputStream 代码示例:

try (InputStream fis = new FileInputStream("input.txt")) {
    System.out.println("Number of remaining bytes:"
            + fis.available());
    int content;
    long skip = fis.skip(2);
    System.out.println("The actual number of bytes skipped:" + skip);
    System.out.print("The content read from file:");
    while ((content = fis.read()) != -1) {
        System.out.print((char) content);
    }
} catch (IOException e) {
    e.printStackTrace();
}

input.txt 文件内容:

输出:

Number of remaining bytes:11
The actual number of bytes skipped:2
The content read from file:JavaGuide

不过,一般我们是不会直接单独使用 FileInputStream ,通常会配合 BufferedInputStream(字节缓冲输入流,后文会讲到)来使用。

像下面这段代码在我们的项目中就比较常见,我们通过 readAllBytes() 读取输入流所有字节并将其直接赋值给一个 String 对象。

// 新建一个 BufferedInputStream 对象
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("input.txt"));
// 读取文件的内容并复制到 String 对象中
String result = new String(bufferedInputStream.readAllBytes());
System.out.println(result);

DataInputStream 用于读取指定类型数据,不能单独使用,必须结合其它流,比如 FileInputStream 。

FileInputStream fileInputStream = new FileInputStream("input.txt");
//必须将fileInputStream作为构造参数才能使用
DataInputStream dataInputStream = new DataInputStream(fileInputStream);
//可以读取任意具体的类型数据
dataInputStream.readBoolean();
dataInputStream.readInt();
dataInputStream.readUTF();

ObjectInputStream 用于从输入流中读取 Java 对象(反序列化),ObjectOutputStream 用于将对象写入到输出流(序列化)。

ObjectInputStream input = new ObjectInputStream(new FileInputStream("object.data"));
MyClass object = (MyClass) input.readObject();
input.close();

另外,用于序列化和反序列化的类必须实现 Serializable 接口,对象中如果有属性不想被序列化,使用 transient 修饰。

package com.fatfish.iostream;

import java.io.*;
import java.util.Arrays;

public class InputStreamDemo {
    public static void main(String[] args) {
        test1();
        test2();
        test3();
        test4();
        test5();
        test6();
    }

    private static void test6() {
        System.out.println("================================test6()================================");
        try (FileInputStream fileInputStream = new FileInputStream("test.txt")) {
            DataInputStream dataInputStream = new DataInputStream(fileInputStream);
            boolean b = dataInputStream.readBoolean();
            System.out.println(b);
            int i = dataInputStream.readInt();
            System.out.println(i);
//            String s = dataInputStream.readUTF();
//            System.out.println(s);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void test5() {
        System.out.println("================================test5()================================");
        try (InputStream inputStream = new FileInputStream("test.txt");
             BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)) {
            long start = System.currentTimeMillis();
            byte[] bytes = bufferedInputStream.readAllBytes();
            for (byte aByte : bytes) {
                System.out.println((char) aByte);
            }
            String s = new String(bytes);
            System.out.println(s);
            long end = System.currentTimeMillis();
            System.out.println(end-start + "ms");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void test4() {
        System.out.println("================================test4()================================");
        try (InputStream inputStream = new FileInputStream("test.txt")) {
            byte[] bytes = new byte[1024];
            int len;
            while (inputStream.read(bytes, 0, len = Math.min(inputStream.available(), bytes.length)) > 0) {
                for (int i = 0; i < len; i++) {
                    System.out.println((char) bytes[i]);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void test3() {
        System.out.println("================================test3()================================");
        try (InputStream inputStream = new FileInputStream("test.txt")) {
            byte[] bytes = new byte[1024];
            while (inputStream.read(bytes) > 0) {
                for (byte aByte : bytes) {
                    System.out.println((char) aByte);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void test2() {
        System.out.println("================================test2()================================");
        try (InputStream inputStream = new FileInputStream("test.txt")) {
            int content;
            while ((content = inputStream.read()) != -1) {
                System.out.println((char) content);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void test1() {
        System.out.println("================================test1()================================");
        try (InputStream inputStream = new FileInputStream("test.txt")) {

            int available = inputStream.available();
            System.out.println("available: " + available);

            int firstByte = inputStream.read();
            System.out.println("firstByte: " + firstByte);

            byte[] b = new byte[4];
            int count = inputStream.read(b);
            System.out.println("byteArrayCount: " + count + ", readToByteArray: " + Arrays.toString(b));

            int readToByteArrayByOffAndLen = inputStream.read(b, 0, 3);
            System.out.println("readToByteArrayByOffAndLen: " + readToByteArrayByOffAndLen + ", readToByteArray: " + Arrays.toString(b));

            long skip = inputStream.skip(2);
            System.out.println("skip: " + skip);

            available = inputStream.available();
            System.out.println("available: " + available);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2. OutputStream(字符输出流)

OutputStream用于将数据(字节信息)写入到目的地(通常是文件),java.io.OutputStream抽象类是所有字节输出流的父类。

OutputStream 常用方法:

  • write(int b):将特定字节写入输出流。
  • write(byte b[ ]) : 将数组b 写入到输出流,等价于 write(b, 0, b.length)
  • write(byte[] b, int off, int len) : 在write(byte b[ ]) 方法的基础上增加了 off 参数(偏移量)和 len 参数(要读取的最大字节数)。
  • flush():刷新此输出流并强制写出所有缓冲的输出字节。
  • close():关闭输出流释放相关的系统资源。

FileOutputStream 是最常用的字节输出流对象,可直接指定文件路径,可以直接输出单字节数据,也可以输出指定的字节数组。

FileOutputStream 代码示例:

try (FileOutputStream output = new FileOutputStream("output.txt")) {
    byte[] array = "JavaGuide".getBytes();
    output.write(array);
} catch (IOException e) {
    e.printStackTrace();
}

运行结果:

类似于 FileInputStreamFileOutputStream 通常也会配合 BufferedOutputStream(字节缓冲输出流,后文会讲到)来使用。

FileOutputStream fileOutputStream = new FileOutputStream("output.txt");
BufferedOutputStream bos = new BufferedOutputStream(fileOutputStream)

DataOutputStream 用于写入指定类型数据,不能单独使用,必须结合其它流,比如 FileOutputStream

// 输出流
FileOutputStream fileOutputStream = new FileOutputStream("out.txt");
DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
// 输出任意数据类型
dataOutputStream.writeBoolean(true);
dataOutputStream.writeByte(1);

ObjectInputStream 用于从输入流中读取 Java 对象(ObjectInputStream,反序列化),ObjectOutputStream将对象写入到输出流(ObjectOutputStream,序列化)。

ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("file.txt")
Person person = new Person("Guide哥", "JavaGuide作者");
output.writeObject(person);
package com.fatfish.iostream;

import com.fatfish.iostream.dto.Person;

import java.io.*;

/**
 * @author 24716
 * @version 1.0
 * @date 2024/6/5 21:23
 */
public class OutputStreamDemo {
    public static void main(String[] args) {
        test1();
        test2();
        test3();
        test4();
    }

    private static void test4() {
        System.out.println("================================序列化与反序列化================================");
        try (OutputStream outputStream = new FileOutputStream("personData.txt");
             ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
             InputStream inputStream = new FileInputStream("personData.txt");
             ObjectInputStream objectInputStream = new ObjectInputStream(inputStream)) {
            // 序列化
            Person person = new Person(1, "zhangsan", 35);
            objectOutputStream.writeObject(person);
            objectOutputStream.flush();

            // 反序列化
            Object object = objectInputStream.readObject();
            if (object instanceof Person) {
                System.out.println((Person) object);
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private static void test3() {
        System.out.println("================================DataOutputStream================================");
        try (OutputStream outputStream = new FileOutputStream("outputFile.txt");
             DataOutputStream dataOutputStream = new DataOutputStream(outputStream)) {
            // 将特定字节写入输出流,写入 (char) int
            dataOutputStream.write(97);
            // 写入字节数组
            byte[] bytes = "Hello IOStream!".getBytes();
            dataOutputStream.write(bytes);
            dataOutputStream.write(bytes, 6, bytes.length - 6);
            // 写入任意数据类型
            dataOutputStream.writeBoolean(true);
            dataOutputStream.writeBoolean(false);
//            dataOutputStream.writeDouble(1.2345); // 确保在读取时使用正确的方法来解析二进制数据,而不是将其视为字符数据进行解码。否则乱码
            dataOutputStream.writeByte(98);
            dataOutputStream.writeBytes("xyz");
            // 强制将缓冲区中的数据写入目标位置
            dataOutputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void test2() {
        System.out.println("================================BufferedOutputStream================================");
        try (OutputStream outputStream = new FileOutputStream("outputFile.txt");
             BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)) {
            // 将特定字节写入输出流,写入 (char) int
            bufferedOutputStream.write(97);
            // 写入字节数组
            byte[] bytes = "Hello IOStream!".getBytes();
            bufferedOutputStream.write(bytes);
            bufferedOutputStream.write(bytes, 6, bytes.length - 6);
            // 强制将缓冲区中的数据写入目标位置
            bufferedOutputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void test1() {
        System.out.println("================================OutputStream 常用方法================================");
        try (OutputStream outputStream = new FileOutputStream("outputFile.txt")) {
            // 将特定字节写入输出流,写入 (char) int
            outputStream.write(97);
            // 写入字节数组
            byte[] bytes = "Hello IOStream!".getBytes();
            outputStream.write(bytes);
            outputStream.write(bytes, 6, bytes.length - 6);
            // 强制将缓冲区中的数据写入目标位置
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

二、字符流

不管是文件读写还是网络发送接收,信息的最小存储单元都是字节。 那为什么 I/O 流操作要分为字节流操作和字符流操作呢?

个人认为主要有两点原因:

  • 字符流是由 Java 虚拟机将字节转换得到的,这个过程还算是比较耗时。
  • 如果我们不知道编码类型就很容易出现乱码问题。
  1. 字节流操作(Byte Stream Operations)
  • 字节流操作以字节为单位进行读取和写入,适用于处理二进制数据,如图像、音频、视频等。
  • 字节流操作不对数据进行任何解释或转换,直接处理数据的原始字节表示。
  • 字节流通常用于处理二进制文件或网络流,因为它们不对数据进行修改,能够直接传输数据的原始形式。
  1. 字符流操作(Character Stream Operations)
  • 字符流操作以字符为单位进行读取和写入,适用于处理文本数据。
  • 字符流操作会将字节转换为字符,并根据指定的字符编码进行解码或编码。
  • 字符流通常用于处理文本文件或网络通信中的文本数据,因为它们能够正确地处理字符编码和字符集,避免了乱码等问题。

所以,尽管信息的存储和传输都是以字节为单位的,但在实际处理数据时,我们需要根据数据的类型和特性选择合适的流操作。字节流操作适用于处理二进制数据,而字符流操作适用于处理文本数据。这样做能够更加方便、高效地处理不同类型的数据,并确保数据在传输和处理过程中的完整性和正确性。

乱码问题这个很容易就可以复现,我们只需要将上面提到的 FileInputStream 代码示例中的 input.txt 文件内容改为中文即可,原代码不需要改动。

输出:

Number of remaining bytes:9
The actual number of bytes skipped:2
The content read from file:§å®¶å¥½

可以很明显地看到读取出来的内容已经变成了乱码。

因此,I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。

字符流默认采用的是 Unicode 编码,我们可以通过构造方法自定义编码。顺便分享一下之前遇到的笔试题:常用字符编码所占字节数?

  • utf8 :英文占 1 字节,中文占 3 字节

  • unicode:任何字符都占 2 个字节

  • gbk:英文占 1 字节,中文占 2 字节。

1. Reader(字符输入流)

Reader用于从源头(通常是文件)读取数据(字符信息)到内存中,java.io.Reader抽象类是所有字符输入流的父类。

Reader 用于读取文本, InputStream 用于读取原始字节。

Reader 常用方法:

  • read() : 从输入流读取一个字符。
  • read(char[] cbuf) : 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中,等价于 read(cbuf, 0, cbuf.length)
  • read(char[] cbuf, int off, int len):在read(char[] cbuf) 方法的基础上增加了 off 参数(偏移量)和 len 参数(要读取的最大字符数)。
  • skip(long n):忽略输入流中的 n 个字符 ,返回实际忽略的字符数。
  • close() : 关闭输入流并释放相关的系统资源。

InputStreamReader 是字节流转换为字符流的桥梁,其子类 FileReader 是基于该基础上的封装,可以直接操作字符文件。

// 字节流转换为字符流的桥梁
public class InputStreamReader extends Reader {
}
// 用于读取字符文件
public class FileReader extends InputStreamReader {
}

FileReader 代码示例:

try (FileReader fileReader = new FileReader("input.txt");) {
    int content;
    long skip = fileReader.skip(3);
    System.out.println("The actual number of bytes skipped:" + skip);
    System.out.print("The content read from file:");
    while ((content = fileReader.read()) != -1) {
        System.out.print((char) content);
    }
} catch (IOException e) {
    e.printStackTrace();
}

input.txt 文件内容:

输出:

The actual number of bytes skipped:3
The content read from file:我是Guide
package com.fatfish.iostream;

import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Arrays;

/**
 * @author fatfish
 * @version 1.0
 * @date 2024/6/5 22:14
 */
public class FileReaderDemo {
    public static void main(String[] args) {
        test1();
        test2();
    }

    private static void test2() {
        System.out.println("=================================Reader()全文读取=================================");
        try (Reader reader = new FileReader("test.txt")) {
            int content;
            while ((content = reader.read()) != -1) {
                System.out.println((char) content);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void test1() {
        System.out.println("=================================Reader()常用方法=================================");
        try (Reader reader = new FileReader("test.txt")) {
            long skip = reader.skip(2);

            int readACharacter = reader.read();
            System.out.println("readACharacter: " + readACharacter);

            char[] chars = new char[5];
            int readCnt = reader.read(chars);
            System.out.println("读取至字符数组:" + Arrays.toString(chars));

            readCnt = reader.read(chars, 0, 2);
            System.out.println("读取" + readCnt + "个字符至字符数组:" + Arrays.toString(chars));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2. Writer(字符输出流)

Writer用于将数据(字符信息)写入到目的地(通常是文件),java.io.Writer抽象类是所有字符输出流的父类。

Writer 常用方法:

  • write(int c) : 写入单个字符。
  • write(char[] cbuf):写入字符数组 cbuf,等价于write(cbuf, 0, cbuf.length)
  • write(char[] cbuf, int off, int len):在write(char[] cbuf) 方法的基础上增加了 off 参数(偏移量)和 len 参数(要读取的最大字符数)。
  • write(String str):写入字符串,等价于 write(str, 0, str.length())
  • write(String str, int off, int len):在write(String str) 方法的基础上增加了 off 参数(偏移量)和 len 参数(要读取的最大字符数)。
  • append(CharSequence csq):将指定的字符序列附加到指定的 Writer 对象并返回该 Writer 对象。
  • append(char c):将指定的字符附加到指定的 Writer 对象并返回该 Writer 对象。
  • flush():刷新此输出流并强制写出所有缓冲的输出字符。
  • close():关闭输出流释放相关的系统资源。

OutputStreamWriter 是字符流转换为字节流的桥梁,其子类 FileWriter 是基于该基础上的封装,可以直接将字符写入到文件。

// 字符流转换为字节流的桥梁
public class OutputStreamWriter extends Writer {
}
// 用于写入字符到文件
public class FileWriter extends OutputStreamWriter {
}

FileWriter 代码示例:

try (Writer output = new FileWriter("output.txt")) {
    output.write("你好,我是Guide。");
} catch (IOException e) {
    e.printStackTrace();
}

输出结果:

package com.fatfish.iostream;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

/**
 * @author fatfish
 * @version 1.0
 * @date 2024/6/5 22:26
 */
public class WriterDemo {
    public static void main(String[] args) {
        try (Writer writer = new FileWriter("outputFile.txt")) {
            writer.write(97);
            writer.write("你好世界!".toCharArray());
            writer.write("你好世界!".toCharArray(), 2, 2);
            writer.write("生活真美好。");
            writer.write("生活真美好。", 3, 2);
            writer.append("天天向上");
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

三、缓冲流

1. 字节缓冲流

IO 操作是很消耗性能的,缓冲流将数据加载至缓冲区,一次性读取/写入多个字节,从而避免频繁的 IO 操作,提高流的传输效率。

字节缓冲流这里采用了装饰器模式来增强 InputStreamOutputStream子类对象的功能。

举个例子,我们可以通过 BufferedInputStream(字节缓冲输入流)来增强 FileInputStream 的功能。

// 新建一个 BufferedInputStream 对象
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("input.txt"));

字节流和字节缓冲流的性能差别主要体现在我们使用两者的时候都是调用 write(int b)read() 这两个一次只读取一个字节的方法的时候。由于字节缓冲流内部有缓冲区(字节数组),因此,字节缓冲流会先将读取到的字节存放在缓存区,大幅减少 IO 次数,提高读取效率

我使用 write(int b)read() 方法,分别通过字节流和字节缓冲流复制一个 524.9 mb 的 PDF 文件耗时对比如下:

使用缓冲流复制PDF文件总耗时:15428 毫秒
使用普通字节流复制PDF文件总耗时:2555062 毫秒

两者耗时差别非常大,缓冲流耗费的时间是字节流的 1/165。

测试代码如下:

@Test
void copy_pdf_to_another_pdf_buffer_stream() {
    // 记录开始时间
    long start = System.currentTimeMillis();
    try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("深入理解计算机操作系统.pdf"));
         BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("深入理解计算机操作系统-副本.pdf"))) {
        int content;
        while ((content = bis.read()) != -1) {
            bos.write(content);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    // 记录结束时间
    long end = System.currentTimeMillis();
    System.out.println("使用缓冲流复制PDF文件总耗时:" + (end - start) + " 毫秒");
}

@Test
void copy_pdf_to_another_pdf_stream() {
    // 记录开始时间
    long start = System.currentTimeMillis();
    try (FileInputStream fis = new FileInputStream("深入理解计算机操作系统.pdf");
         FileOutputStream fos = new FileOutputStream("深入理解计算机操作系统-副本.pdf")) {
        int content;
        while ((content = fis.read()) != -1) {
            fos.write(content);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    // 记录结束时间
    long end = System.currentTimeMillis();
    System.out.println("使用普通流复制PDF文件总耗时:" + (end - start) + " 毫秒");
}

如果是调用 read(byte b[])write(byte b[], int off, int len) 这两个写入一个字节数组的方法的话,只要字节数组的大小合适,两者的性能差距其实不大,基本可以忽略。

这次我们使用 read(byte b[])write(byte b[], int off, int len) 方法,分别通过字节流和字节缓冲流复制一个 524.9 mb 的 PDF 文件耗时对比如下:

使用缓冲流复制PDF文件总耗时:695 毫秒
使用普通字节流复制PDF文件总耗时:989 毫秒

两者耗时差别不是很大,缓冲流的性能要略微好一点点。

测试代码如下:

@Test
void copy_pdf_to_another_pdf_with_byte_array_buffer_stream() {
    // 记录开始时间
    long start = System.currentTimeMillis();
    try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("深入理解计算机操作系统.pdf"));
         BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("深入理解计算机操作系统-副本.pdf"))) {
        int len;
        byte[] bytes = new byte[4 * 1024];
        while ((len = bis.read(bytes)) != -1) {
            bos.write(bytes, 0, len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    // 记录结束时间
    long end = System.currentTimeMillis();
    System.out.println("使用缓冲流复制PDF文件总耗时:" + (end - start) + " 毫秒");
}

@Test
void copy_pdf_to_another_pdf_with_byte_array_stream() {
    // 记录开始时间
    long start = System.currentTimeMillis();
    try (FileInputStream fis = new FileInputStream("深入理解计算机操作系统.pdf");
         FileOutputStream fos = new FileOutputStream("深入理解计算机操作系统-副本.pdf")) {
        int len;
        byte[] bytes = new byte[4 * 1024];
        while ((len = fis.read(bytes)) != -1) {
            fos.write(bytes, 0, len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    // 记录结束时间
    long end = System.currentTimeMillis();
    System.out.println("使用普通流复制PDF文件总耗时:" + (end - start) + " 毫秒");
}

1.1 BufferedInputStream(字节缓冲输入流)

BufferedInputStream 从源头(通常是文件)读取数据(字节信息)到内存的过程中不会一个字节一个字节的读取,而是会先将读取到的字节存放在缓存区,并从内部缓冲区中单独读取字节。这样大幅减少了 IO 次数,提高了读取效率。

BufferedInputStream 内部维护了一个缓冲区,这个缓冲区实际就是一个字节数组,通过阅读 BufferedInputStream 源码即可得到这个结论。

public class BufferedInputStream extends FilterInputStream {
    // 内部缓冲区数组
    protected volatile byte buf[];
    // 缓冲区的默认大小
    private static int DEFAULT_BUFFER_SIZE = 8192;
    // 使用默认的缓冲区大小
    public BufferedInputStream(InputStream in) {
        this(in, DEFAULT_BUFFER_SIZE);
    }
    // 自定义缓冲区大小
    public BufferedInputStream(InputStream in, int size) {
        super(in);
        if (size <= 0) {
            throw new IllegalArgumentException("Buffer size <= 0");
        }
        buf = new byte[size];
    }
}

缓冲区的大小默认为 8192 字节,当然了,你也可以通过 BufferedInputStream(InputStream in, int size) 这个构造方法来指定缓冲区的大小。

1.2 BufferedOutputStream(字节缓冲输出流)

BufferedOutputStream 将数据(字节信息)写入到目的地(通常是文件)的过程中不会一个字节一个字节的写入,而是会先将要写入的字节存放在缓存区,并从内部缓冲区中单独写入字节。这样大幅减少了 IO 次数,提高了读取效率

try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("output.txt"))) {
    byte[] array = "JavaGuide".getBytes();
    bos.write(array);
} catch (IOException e) {
    e.printStackTrace();
}

类似于 BufferedInputStreamBufferedOutputStream 内部也维护了一个缓冲区,并且,这个缓存区的大小也是 8192 字节。

2. 字符缓冲流

BufferedReader (字符缓冲输入流)和 BufferedWriter(字符缓冲输出流)类似于 BufferedInputStream(字节缓冲输入流)和BufferedOutputStream(字节缓冲输入流),内部都维护了一个字节数组作为缓冲区。不过,前者主要是用来操作字符信息。

四、打印流

下面这段代码大家经常使用吧?

System.out.print("Hello!");
System.out.println("Hello!");

System.out 实际是用于获取一个 PrintStream 对象,print方法实际调用的是 PrintStream 对象的 write 方法。

PrintStream 属于字节打印流,与之对应的是 PrintWriter (字符打印流)。PrintStreamOutputStream 的子类,PrintWriterWriter 的子类。

public class PrintStream extends FilterOutputStream
    implements Appendable, Closeable {
}
public class PrintWriter extends Writer {
}

五、随机访问流

这里要介绍的随机访问流指的是支持随意跳转到文件的任意位置进行读写的 RandomAccessFile 。

RandomAccessFile 的构造方法如下,我们可以指定 mode(读写模式)。

// openAndDelete 参数默认为 false 表示打开文件并且这个文件不会被删除
public RandomAccessFile(File file, String mode)
    throws FileNotFoundException {
    this(file, mode, false);
}
// 私有方法
private RandomAccessFile(File file, String mode, boolean openAndDelete)  throws FileNotFoundException{
  // 省略大部分代码
}

读写模式主要有下面四种:

  • r : 只读模式。
  • rw: 读写模式
  • rws: 相对于 rwrws 同步更新对“文件的内容”或“元数据”的修改到外部存储设备。
  • rwd : 相对于 rwrwd 同步更新对“文件的内容”的修改到外部存储设备。

文件内容指的是文件中实际保存的数据,元数据则是用来描述文件属性比如文件的大小信息、创建和修改时间。

RandomAccessFile 中有一个文件指针用来表示下一个将要被写入或者读取的字节所处的位置。我们可以通过 RandomAccessFileseek(long pos) 方法来设置文件指针的偏移量(距文件开头 pos 个字节处)。如果想要获取文件指针当前的位置的话,可以使用 getFilePointer() 方法。

RandomAccessFile 代码示例:

RandomAccessFile randomAccessFile = new RandomAccessFile(new File("input.txt"), "rw");
System.out.println("读取之前的偏移量:" + randomAccessFile.getFilePointer() + ",当前读取到的字符" + (char) randomAccessFile.read() + ",读取之后的偏移量:" + randomAccessFile.getFilePointer());
// 指针当前偏移量为 6
randomAccessFile.seek(6);
System.out.println("读取之前的偏移量:" + randomAccessFile.getFilePointer() + ",当前读取到的字符" + (char) randomAccessFile.read() + ",读取之后的偏移量:" + randomAccessFile.getFilePointer());
// 从偏移量 7 的位置开始往后写入字节数据
randomAccessFile.write(new byte[]{'H', 'I', 'J', 'K'});
// 指针当前偏移量为 0,回到起始位置
randomAccessFile.seek(0);
System.out.println("读取之前的偏移量:" + randomAccessFile.getFilePointer() + ",当前读取到的字符" + (char) randomAccessFile.read() + ",读取之后的偏移量:" + randomAccessFile.getFilePointer());

input.txt 文件内容:

输出:

读取之前的偏移量:0,当前读取到的字符A,读取之后的偏移量:1
读取之前的偏移量:6,当前读取到的字符G,读取之后的偏移量:7
读取之前的偏移量:0,当前读取到的字符A,读取之后的偏移量:1

input.txt 文件内容变为 ABCDEFGHIJK

RandomAccessFilewrite 方法在写入对象的时候如果对应的位置已经有数据的话,会将其覆盖掉。

RandomAccessFile randomAccessFile = new RandomAccessFile(new File("input.txt"), "rw");
randomAccessFile.write(new byte[]{'H', 'I', 'J', 'K'});

假设运行上面这段程序之前 input.txt 文件内容变为 ABCD ,运行之后则变为 HIJK

断点续传

RandomAccessFile 比较常见的一个应用就是实现大文件的 断点续传 。何谓断点续传?简单来说就是上传文件中途暂停或失败(比如遇到网络问题)之后,不需要重新上传,只需要上传那些未成功上传的文件分片即可。分片(先将文件切分成多个文件分片)上传是断点续传的基础。

RandomAccessFile 可以帮助我们合并文件分片,示例代码如下:

我在《Java 面试指北》open in new window中详细介绍了大文件的上传问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/682371.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

什么是 Batch Normalization 批标准化和全连接层

Batch Normalization 神经元在经过激活函数之后会处于饱和状态&#xff0c;无论后续怎么变化都不会再起作用。 每一层都会进行batch normalization的处理&#xff01; without normalization 会导致数据分布再饱和区 全连接层&#xff1a; 全连接层(fully connected layers&a…

微信公众号文章背景颜色改成白色

微信公众号文章背景颜色黑色&#xff0c;看不清字。 按F12 , 找到 rich_media_area_primary &#xff0c;把 background 改成 white .rich_media_area_primary {background: white; }

云端狂飙:Django项目部署与性能优化的极速之旅

Hello&#xff0c;我是阿佑&#xff0c;这次阿佑将手把手带你亲自踏上Django项目从单机到云端的全过程&#xff0c;以及如何通过Docker实现项目的无缝迁移和扩展。不仅详细介绍了Docker的基本概念和操作&#xff0c;还深入探讨Docker Compose、Swarm和Kubernetes等高级工具的使…

python语言中循环语句的小结

如上图所示&#xff0c;在C/C/Java中如果使用的for循环语句和do while语句都与python中的while循环语句类似&#xff0c;所以在C/C/Java中如果使用的for循环语句在python中可以用while语句来替换。

上位机图像处理和嵌入式模块部署(f407 mcu中的udp server开发)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 既然lwip已经port到407上面了&#xff0c;接下来其实就可以做一些测试了。本身lwip支持tcp、udp&#xff0c;也支持client和server&#xff0c;既然…

python语言中的break和continue

continue立即结束当前这次循环&#xff0c;进入下次循环 break立即结束整个循环。 如上图所示&#xff0c;在python语言中break和continue语句常常搭配条件语句一起使用。 如上图所示&#xff0c;while True&#xff1a; 光看到while True 不一定是死循环&#xff0c; 关键是…

【Kubernetes】 emptyDir、nfs存储卷 和 PV、PVC

emptyDir存储卷 当pod被分配给节点 容器和容器之间进行共享存储 hostPath nfs共享存储卷 NAS 专业的存储设备&#xff1b;一般是与NFS 搭配&#xff0c;然后共享出去 GFS 自己搭&#xff1b;CEPH(至少要9台) 第三方&#xff1b;NAS 第三方&#xff1b; 云端 oss …

一维时间序列信号的小波时间散射变换(MATLAB 2021)

小波散射变换的目的在于获取第一层次的特征信息&#xff0c;即免疫平移、轻微形变的信息。而低通的滤波器能够获取输入信号的概貌&#xff0c;获取反映其整体大尺度特征的信息&#xff0c;以图像为例&#xff0c;由低通滤波器选取的信号对于图像的平移、伸缩、旋转等局部变化有…

信息学奥赛初赛天天练-20-完善程序-vector数组参数引用传递、二分中值与二分边界应用的深度解析

PDF文档公众号回复关键字:20240605 1 2023 CSP-J 完善程序1 完善程序&#xff08;单选题&#xff0c;每小题 3 分&#xff0c;共计 30 分&#xff09; 原有长度为 n1,公差为1等升数列&#xff0c;将数列输到程序的数组时移除了一个元素&#xff0c;导致长度为 n 的开序数组…

阿里云私有CA使用教程

点击免费生成 根CA详情 启用根CA -----BEGIN CERTIFICATE----- MIIDpzCCAogAwIBAgISBZ2QPcfDqvfI8fqoPkOq6AoMA0GCSqGSIb3DQEBCwUA MFwxCzAJBgNVBAYTAkNOMRAwDgYDVQQIDAdiZWlqaW5nMRAwDgYDVQQHDAdiZWlq aW5nMQ0wCwYDVQQKDARDU0REMQ0wCwYDVQQLDARDU0REMQswCQYDVQQDDAJDTjA…

【踏雪无痕的痕六】——数学中有意思的问题

一、背景介绍 提出一个问题往往比解决一个问题更有意义&#xff0c;因为提出一个问题相当于提出了一个思考问题的维度&#xff1b;而解决一个问题是沿着这个维度将已有的知识串起来的过程 三、过程 1.数人数你会吗&#xff1f; 小名再第10位&#xff0c;小李再第15位&#…

AI论文工具推荐

AI 在学术界的使用情况也比较疯狂&#xff0c;特别是一些美国大学&#xff0c;用 AI 来辅助阅读文献以及辅助写论文的越来越多&#xff0c;毕竟确实可以提高写作效率&#xff0c;特别是在文献综述和初稿生成方面。 但在科研界其实&#xff0c;发现看论文的速度已经赶不上发论文…

【踩坑记录】代码看起来没问题 但是报错No tab with id:1682523514.-作者:【小可耐教你学影刀RPA】

前言 有一个企业用户反馈 同一个代码 跑出来不同的结果 我也有点疑惑 是bug吗&#xff1f;&#xff1f;我让他环境保持一致 还是出现这个报错~~~ 为了避免影响他的业务我还是决定远程~~~ 不远程还真发现不了这个问题~~~ 原因 业务的的代码如下 就一个很简单的循环点击获取新…

【Linux】Linux工具——gdb

1. gdb 概述 GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。或许&#xff0c;各位比较喜欢那种图形界面方式的&#xff0c;像VC、BCB等IDE的调试&#xff0c;但如果你是在 UNIX平台下做软件&#xff0c;你会发现GDB这个调试工具有比VC、BCB的图形化调试器更强大的功能…

Vue3中的常见组件通信之v-model

Vue3中的常见组件通信之v-model 概述 ​ 在vue3中常见的组件通信有props、mitt、v-model、 r e f s 、 refs、 refs、parent、provide、inject、pinia、slot等。不同的组件关系用不同的传递方式。常见的撘配形式如下表所示。 组件关系传递方式父传子1. props2. v-model3. $r…

Spring Boot整合Jasypt 库实现配置文件和数据库字段敏感数据的加解密

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

4.2 索引及其操作

对数据库中的表进行查询操作时有两种搜索扫描方式&#xff0c;一种是全表扫描&#xff0c;另一种就是使用表上建立的索引进行扫描。 全表扫描要查找某个特定的行&#xff0c;必须从头开始一一查看表中的每一行&#xff0c;与查询条件做对比&#xff0c;返回满足条件的记录&…

二叉树和堆

二叉树和堆 树的概念及结构树的一些术语&#xff08;概念&#xff09;树的表示二叉树的概念及结构二叉树概念与其结构 二叉树的性质二叉树的存储 堆堆的概念堆的实现向上调整算法向下调整算法 实现堆数据结构堆的插入取堆顶数据堆顶数据删除 堆排序TopK问题 本文主要介绍二叉树…

mysql buffer pool 详解

概念&#xff1a;为了缓存磁盘中的页&#xff0c;mysql服务器启动时会向操作系统申请一片连续的内存空间&#xff0c;这片连续的内存空间叫做buffer pool&#xff0c;即缓冲池。 buffer pool 默认大小&#xff1a;128M innodb_buffer_pool_size&#xff1a;自定义缓冲池大小 …

这家公司的39亿存款,无法收回了?

新闻提要 4日晚间&#xff0c;亿利洁能发布公告称&#xff0c;亿利财务公司对于公司存放在亿利财务公司的 39.06 亿元货币资金的用途主要是向亿利集团及其关联方发放贷款&#xff0c;近日公司获悉相关贷款已被划分为次级贷款&#xff08;不良贷款的一种&#xff09;&#xff0…