Java IO操作

Java IO

Java IO流在Java中分为输入流和输出流,根据数据的处理方式又被分为字节流和字符流;
Java IO流的类都是从4个抽象基类中派生出来:
字节流(Bytes Stream)基类:

  • 字节输入流(InputStream)
  • 字节输出流(OutputStream)

字符流(Character Stream)基类:

  • 字符输入流(Reader)
  • 字符输出流(Writer)

字节流是最底层的,因而可以处理任何类型的文件。
如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。
字符流默认采用的是 Unicode 编码,我们可以通过构造方法自定义编码。

场景 字节流 字符流
文本文件(含编码) 不推荐(需手动编解码) 推荐(自动处理编码)
二进制文件(如图片) 推荐(直接操作字节) 不适用
网络传输(协议层数据) 推荐(原始字节) 若为文本内容,可转换后使用
需要按行读取/处理文本 复杂(需手动解析) 推荐(readLine())

字节流

InputStream(字节输入流)

InputStream用于从源头(通常是文件)读取数据(字节信息)到内存中,java.io.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 ,通常会配合 BufferedInputStream(字节缓冲输入流)来使用。
  • DataInputStream 用于读取指定类型数据,不能单独使用,必须结合其它流,比如 FileInputStream 。
  • ObjectInputStream 用于从输入流中读取 Java 对象(反序列化),ObjectOutputStream 用于将对象写入到输出流(序列化)。

代码示例

package com.basic.io;

import java.io.*;

/**
 * @Title: FileInputStreamExample
 * @description: 字节输入流案例
 */
public class InputStreamExample {
    /**
     * 文件输入流案例
     * FileInputStream 使用案例
     */
    public void fileInputStreamExample(){

        //getResource param:URL object for reading the resource
        //String filePath = getClass().getClassLoader().getResource("com/tahacoo/io/test.txt").getPath();
        String filePath = "src/main/java/com/tahacoo/io/test.txt";
        try(InputStream fis = new FileInputStream(filePath)) {
            System.out.println("Number of remaining bytes:" + fis.available());

            long skip = fis.skip(2);
            System.out.println("The actual number of bytes skipped:" + skip);
            System.out.print("The content read from file:");
            int content;
            while ((content = fis.read() )!= -1) {
                System.out.print((char) content);
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 文件输出流案例
     * 一般我们是不会直接单独使用 FileInputStream ,通常会配合 BufferedInputStream(字节缓冲输入流)来使用。
     */
    public void fileInputStreamExample2(){
        String filePath = "src/main/java/com/tahacoo/io/test.txt";
        try (FileInputStream fileInputStream = new FileInputStream(filePath)) {
            // 新建一个 BufferedInputStream 对象
            BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
            int content;
            while((content = bufferedInputStream.read()) != -1){
                System.out.print((char)content);
            }
            //readAllBytes()是Java 9引入的,如果开发环境使用的是Java 8或更早版本,会引发编译错误。
            //String s = new String(bufferedInputStream.readAllBytes());
            System.out.println();
            bufferedInputStream.close();
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 使用字节数据缓冲区(BufferedInputStream)读取文件
     * 从一个文件中读取内容并将其打印到控制台
     */
    public void fileInputStreamExample3(){
        String filePath = "src/main/java/com/tahacoo/io/test.txt";
        //打开一个文件输入流,用于从指定路径filePath的文件中读取数据
        try(FileInputStream fileInputStream = new FileInputStream(filePath)){
            //包装FileInputStream来创建一个缓冲输入流,这可以提高读取性能,因为它减少了实际读取操作的次数。
            BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
            //创建了一个字节数组输出流,用于收集从文件中读取的字节数据。
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            //使用一个字节数组byte[] data = new byte[1024];作为缓冲区。
            byte[] data = new byte[1024];
            int bytesRead;
           while((bytesRead = bufferedInputStream.read(data,0,data.length)) != -1){
               byteArrayOutputStream.write(data,0,bytesRead);
           }
           //使用toByteArray()方法获取ByteArrayOutputStream中收集的所有字节数据。
           String s = new String(byteArrayOutputStream.toByteArray());
            System.out.println(s);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void dataInputStreamExample(){
        String filePath = "src/main/java/com/tahacoo/io/test.txt";
        //先写入数据
        try(DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream(filePath))){
            dataOutputStream.writeUTF("这是测试数据,Hello World!");
            dataOutputStream.writeInt(12345);
            dataOutputStream.writeByte(1);
       } catch (FileNotFoundException e) {
           throw new RuntimeException(e);
       } catch (IOException e) {
           throw new RuntimeException(e);
       }
        // 读取时保持与写入完全相同的顺序和类型
        try(FileInputStream fileInputStream = new FileInputStream(filePath)){
            DataInputStream dataInputStream = new DataInputStream(fileInputStream);
            String s = dataInputStream.readUTF();
            System.out.println("readUTF():" + s);
            int i = dataInputStream.readInt();
            System.out.println("readInt():" + i);
            byte b = dataInputStream.readByte();
            System.out.println("readByte():" + b);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        InputStreamExample inputStreamExample = new InputStreamExample();
        //inputStreamExample.fileInputStreamExample();
        //inputStreamExample.fileInputStreamExample2();
        //inputStreamExample.fileInputStreamExample3();
        inputStreamExample.dataInputStreamExample();
    }
}

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 是最常用的字节输出流对象,可直接指定文件路径,可以直接输出单字节数据,也可以输出指定的字节数组。
    • FileOutputStream 类的write()方法写入文件,默认情况下它会覆盖文件中的内容。
    • 使用FileOutputStream(filePath, true),指定第二个参数为 true。这个参数用于指示是否以追加模式打开文件。
  • DataOutputStream : 用于写入指定类型数据,不能单独使用,必须结合其它流,比如 FileOutputStream 。
  • ObjectInputStream : 用于从输入流中读取 Java 对象(ObjectInputStream,反序列化),ObjectOutputStream将对象写入到输出流(ObjectOutputStream,序列化)。

代码示例

package com.basic.io;

import java.io.*;

/**
 * @Title: OutputStreamExample
 * @description: 字节输出流案例
 */
public class OutputStreamExample {

    private String filePath = "src/main/java/com/tahacoo/io/output.txt";

    /**
     * FileOutputStream 是最常用的字节输出流对象,可直接指定文件路径,可以直接输出单字节数据,也可以输出指定的字节数组。
     * FileOutputStream 类的write()方法写入文件,默认情况下它会覆盖文件中的内容。
     * 使用FileOutputStream(filePath, true),指定第二个参数为 true。这个参数用于指示是否以追加模式打开文件。
     */
    public void outPutStreamExample(){
        try (FileOutputStream fileOutputStream = new FileOutputStream(filePath,true)) {
            byte[] bytes = "FileOutputStreamText \n".getBytes();
            fileOutputStream.write(bytes);

            //66对应ASCII编码中的B,文件中将会写入B;
            fileOutputStream.write(67);

        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 类似于 FileInputStream,FileOutputStream 通常也会配合 BufferedOutputStream(字节缓冲输出流)来使用。
     */
    public void outPutStreamExample2(){
        //BufferedOutputStream是在try块内部创建的,但没有被作为资源声明在try的括号里。
        // 因此,当try块结束时,BufferedOutputStream可能没有被关闭,从而数据未被写入。
        // 解决方法,将BufferedOutputStream声明在try的括号,或者,在写入数据后手动调用flush方法或close方法,确保缓冲区的内容被强制写入文件。
        try(FileOutputStream  fileOutputStream = new FileOutputStream(filePath, true)) {
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
            bufferedOutputStream.write(filePath.getBytes());
            bufferedOutputStream.flush();
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void dataOutPutStreamExample(){
        try(FileOutputStream fileOutputStream = new FileOutputStream(filePath, true)) {
            DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
            dataOutputStream.writeByte(67);
            dataOutputStream.writeBoolean(true);
            dataOutputStream.writeBytes("\n test Data");
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public static void main(String[] args) {
        OutputStreamExample outputStreamExample = new OutputStreamExample();
        //outputStreamExample.outPutStreamExample();
        //outputStreamExample.outPutStreamExample2();
        outputStreamExample.dataOutPutStreamExample();
    }
}

字符流

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

  1. 字符编码与解码的需求:字节的本质:所有数据最终存储为字节,但文本字符需要根据编码规则(如UTF-8、GBK)转换为字节序列。 问题:直接通过字节流读写文本时,开发者需手动处理编码和解码,容易出错(例如中文乱码)。
  2. 文本处理的便捷性:字符流提供高层抽象,直接操作字符(char)或字符串(String),而非单个字节。 支持按行读写(BufferedReader.readLine())、格式化文本解析(Scanner)等。
  3. 国际化(i18n)支持:多语言字符集:不同语言字符的编码长度不同(如ASCII是1字节,UTF-8是1~4字节)。Reader/Writer内部自动处理变长编码,开发者无需关注底层字节长度,可以读取一个包含中文和英文的UTF-8文件。
  4. 性能优化:字符流通常默认使用缓冲(如BufferedReader),减少底层I/O操作次数。

Reader(字符输入流)

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

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() : 关闭输入流并释放相关的系统资源。

常用的字符输入流:

  • FileReader;

代码示例

package com.basic.io;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

/**
 * @Title: InputStreamReaderExample
 * @description:字符输入流案例
 */
public class ReaderExample {
    private String filePath = "src/main/java/com/tahacoo/io/test.txt";

    /**
     * InputStreamReader 是字节流转换为字符流的桥梁,其子类 FileReader 是基于该基础上的封装,可以直接操作字符文件。
     * // 字节流转换为字符流的桥梁
     * public class InputStreamReader extends Reader {
     * }
     * // 用于读取字符文件
     * public class FileReader extends InputStreamReader {
     * }
     */
    public void fileReaderExample(){
        try(FileReader fileReader = new FileReader(filePath)) {
            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.println((char)content);
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        ReaderExample inputStreamReaderExample = new ReaderExample();
        inputStreamReaderExample.fileReaderExample();
    }

}

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():关闭输出流释放相关的系统资源。

常用的字符输出流:

  • FileWriter;

代码示例

package com.basic.io;

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

/**
 * @Title: WriterExample
 * @description: 字符输出流案例
 */
public class WriterExample {
    private String filePath = "src/main/java/com/tahacoo/io/output.txt";

    /**
     * OutputStreamWriter 是字符流转换为字节流的桥梁,其子类 FileWriter 是基于该基础上的封装,可以直接将字符写入到文件。
     * // 字符流转换为字节流的桥梁
     * public class OutputStreamWriter extends Writer {
     * }
     * // 用于写入字符到文件
     * public class FileWriter extends OutputStreamWriter {
     * }
     */
    public void writerExample(){
        try(FileWriter fileWriter = new FileWriter(filePath, true)) {
            fileWriter.write("/n 你好,这是测试数据!");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public static void main(String[] args) {
        WriterExample writerExample = new WriterExample();
        writerExample.writerExample();
    }
}

字节缓冲流

IO 操作是很消耗性能的,缓冲流将数据加载至缓冲区,一次性读取/写入多个字节,从而避免频繁的 IO 操作,提高流的传输效率。字节缓冲流这里采用了装饰器模式来增强 InputStream 和OutputStream子类对象的功能。
举个例子,我们可以通过 BufferedInputStream(字节缓冲输入流)来增强 FileInputStream 的功能。

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

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

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)
这个构造方法来指定缓冲区的大小。

BufferedOutputStream(字节缓冲输出流)

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

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

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

字符缓冲流

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

打印流

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

System.out 实际是用于获取一个 PrintStream 对象,print方法实际调用的是 PrintStream 对象的 write 方法。
PrintStream 属于字节打印流,与之对应的是 PrintWriter (字符打印流)。
PrintStream 是 OutputStream 的子类,PrintWriter 是 Writer 的子类。

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

随机访问流

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

参考链接

  1. https://javaguide.cn/java/io/io-basis.html
posted @ 2025-06-05 21:25  joudys  阅读(14)  评论(0)    收藏  举报