MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java I/O流的基本概念与应用

2022-07-022.0k 阅读

Java I/O流的基本概念

在Java编程中,I/O流(Input/Output Streams)是一个至关重要的概念,它提供了一种统一的方式来处理输入和输出操作。无论是从文件读取数据,还是向网络发送信息,I/O流都扮演着不可或缺的角色。

流的定义

流可以被看作是一个有序的数据序列,它代表了数据的来源或目的地。数据如同水流一样,从源头流向目标。在Java中,流是一种抽象,它隐藏了数据传输的底层细节,例如磁盘读写、网络通信等,为开发者提供了一个通用的接口来处理数据的输入和输出。

字节流与字符流

Java的I/O流主要分为两大类:字节流(Byte Streams)和字符流(Character Streams)。

  • 字节流:用于处理二进制数据,以字节(8位)为单位进行读写操作。字节流的基类是 InputStreamOutputStream。例如,FileInputStream 用于从文件中读取字节数据,FileOutputStream 用于向文件中写入字节数据。字节流适用于处理图像、音频、视频等二进制文件。
  • 字符流:用于处理字符数据,以字符(16位,对应Unicode字符)为单位进行读写操作。字符流的基类是 ReaderWriter。例如,FileReader 用于从文件中读取字符数据,FileWriter 用于向文件中写入字符数据。字符流更适合处理文本文件,因为它可以处理不同字符编码,如UTF-8、GBK等。

Java字节流

InputStream类

InputStream 是所有字节输入流的抽象基类。它定义了一系列用于读取字节数据的方法。

  • int read():从输入流中读取一个字节的数据,并返回该字节的整数值(0 - 255)。如果到达流的末尾,则返回 -1。
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class InputStreamExample {
    public static void main(String[] args) {
        try (InputStream inputStream = new FileInputStream("example.txt")) {
            int data;
            while ((data = inputStream.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,通过 FileInputStream 从名为 example.txt 的文件中读取字节数据,并将其转换为字符输出。read() 方法每次读取一个字节,当读取到流的末尾时,返回 -1,循环结束。

  • int read(byte[] b):从输入流中读取一定数量的字节,并将其存储到指定的字节数组 b 中。返回实际读取的字节数,如果到达流的末尾,则返回 -1。
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class InputStreamByteArrayExample {
    public static void main(String[] args) {
        try (InputStream inputStream = new FileInputStream("example.txt")) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                System.out.write(buffer, 0, bytesRead);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这里使用一个字节数组 buffer 作为缓冲区,read(buffer) 方法将读取的字节存储到该数组中,bytesRead 记录实际读取的字节数,然后通过 System.out.write() 方法将缓冲区中的数据输出。

  • int read(byte[] b, int off, int len):从输入流中读取最多 len 个字节的数据,并将其存储到字节数组 b 中,从偏移量 off 开始存储。返回实际读取的字节数,如果到达流的末尾,则返回 -1。

OutputStream类

OutputStream 是所有字节输出流的抽象基类。它定义了用于写入字节数据的方法。

  • void write(int b):将指定的字节(b 的低8位)写入输出流。
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class OutputStreamExample {
    public static void main(String[] args) {
        try (OutputStream outputStream = new FileOutputStream("output.txt")) {
            String message = "Hello, I/O Stream!";
            byte[] data = message.getBytes();
            for (byte b : data) {
                outputStream.write(b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这段代码中,先将字符串转换为字节数组,然后通过 write(int b) 方法逐个字节写入到名为 output.txt 的文件中。

  • void write(byte[] b):将字节数组 b 中的所有字节写入输出流。
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class OutputStreamByteArrayExample {
    public static void main(String[] args) {
        try (OutputStream outputStream = new FileOutputStream("output.txt")) {
            String message = "Hello, I/O Stream!";
            byte[] data = message.getBytes();
            outputStream.write(data);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

此代码直接将字节数组中的所有字节写入输出流,更加简洁。

  • void write(byte[] b, int off, int len):将字节数组 b 中从偏移量 off 开始的 len 个字节写入输出流。

Java字符流

Reader类

Reader 是所有字符输入流的抽象基类。它用于读取字符数据。

  • int read():从输入流中读取一个字符,并返回该字符的整数值(0 - 65535)。如果到达流的末尾,则返回 -1。
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class ReaderExample {
    public static void main(String[] args) {
        try (Reader reader = new FileReader("example.txt")) {
            int data;
            while ((data = reader.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

该代码通过 FileReader 从文件中读取字符数据,并将其输出。与字节流的 read() 方法类似,只不过这里处理的是字符。

  • int read(char[] cbuf):从输入流中读取一定数量的字符,并将其存储到指定的字符数组 cbuf 中。返回实际读取的字符数,如果到达流的末尾,则返回 -1。
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class ReaderCharArrayExample {
    public static void main(String[] args) {
        try (Reader reader = new FileReader("example.txt")) {
            char[] buffer = new char[1024];
            int charsRead;
            while ((charsRead = reader.read(buffer)) != -1) {
                System.out.print(new String(buffer, 0, charsRead));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这里使用字符数组 buffer 作为缓冲区,read(buffer) 方法将读取的字符存储到该数组中,charsRead 记录实际读取的字符数,然后通过 String 的构造函数将缓冲区中的字符转换为字符串输出。

  • int read(char[] cbuf, int off, int len):从输入流中读取最多 len 个字符的数据,并将其存储到字符数组 cbuf 中,从偏移量 off 开始存储。返回实际读取的字符数,如果到达流的末尾,则返回 -1。

Writer类

Writer 是所有字符输出流的抽象基类。它用于写入字符数据。

  • void write(int c):将指定的字符(c 的低16位)写入输出流。
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class WriterExample {
    public static void main(String[] args) {
        try (Writer writer = new FileWriter("output.txt")) {
            String message = "Hello, Character Stream!";
            for (char c : message.toCharArray()) {
                writer.write(c);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

此代码将字符串逐个字符通过 write(int c) 方法写入到名为 output.txt 的文件中。

  • void write(String str):将指定的字符串写入输出流。
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class WriterStringExample {
    public static void main(String[] args) {
        try (Writer writer = new FileWriter("output.txt")) {
            String message = "Hello, Character Stream!";
            writer.write(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这是一种更简洁的写入字符串的方式,直接将整个字符串写入输出流。

  • void write(String str, int off, int len):将字符串 str 中从偏移量 off 开始的 len 个字符写入输出流。

缓冲流

缓冲字节流

  • BufferedInputStreamBufferedInputStreamFilterInputStream 的子类,它为字节输入流提供了缓冲功能。通过使用缓冲区,减少了实际的磁盘或网络读取次数,提高了读取效率。
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class BufferedInputStreamExample {
    public static void main(String[] args) {
        try (InputStream inputStream = new FileInputStream("example.txt");
             BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)) {
            int data;
            while ((data = bufferedInputStream.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,BufferedInputStream 包装了 FileInputStream,当调用 read() 方法时,它会先从缓冲区中读取数据,如果缓冲区为空,则从底层输入流中读取一批数据填充缓冲区。

  • BufferedOutputStreamBufferedOutputStreamFilterOutputStream 的子类,它为字节输出流提供了缓冲功能。数据先被写入缓冲区,当缓冲区满或调用 flush() 方法时,数据才会被真正写入底层输出流。
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class BufferedOutputStreamExample {
    public static void main(String[] args) {
        try (OutputStream outputStream = new FileOutputStream("output.txt");
             BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)) {
            String message = "Buffered Output Stream Example";
            byte[] data = message.getBytes();
            bufferedOutputStream.write(data);
            bufferedOutputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这里 BufferedOutputStream 包装了 FileOutputStreamwrite() 方法将数据写入缓冲区,flush() 方法确保缓冲区中的数据被写入文件。

缓冲字符流

  • BufferedReaderBufferedReaderFilterReader 的子类,它为字符输入流提供了缓冲功能,并增加了一些方便的方法,如 readLine(),用于读取一行文本。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class BufferedReaderExample {
    public static void main(String[] args) {
        try (Reader reader = new FileReader("example.txt");
             BufferedReader bufferedReader = new BufferedReader(reader)) {
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

BufferedReader 包装了 FileReaderreadLine() 方法每次读取一行文本,直到文件末尾返回 null

  • BufferedWriterBufferedWriterFilterWriter 的子类,它为字符输出流提供了缓冲功能。与 BufferedOutputStream 类似,数据先写入缓冲区,通过 flush() 方法将缓冲区的数据写入底层输出流。
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class BufferedWriterExample {
    public static void main(String[] args) {
        try (Writer writer = new FileWriter("output.txt");
             BufferedWriter bufferedWriter = new BufferedWriter(writer)) {
            String message = "Buffered Writer Example";
            bufferedWriter.write(message);
            bufferedWriter.newLine();
            bufferedWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这里 BufferedWriter 包装了 FileWriterwrite() 方法写入数据,newLine() 方法写入一个换行符,flush() 方法确保数据被写入文件。

数据流

DataInputStream和DataOutputStream

DataInputStreamDataOutputStream 用于以机器无关的方式读写基本数据类型(如 intfloatboolean 等)和字符串。

  • DataInputStreamDataInputStream 允许应用程序以与机器无关的方式从底层输入流中读取基本数据类型。
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class DataInputStreamExample {
    public static void main(String[] args) {
        try (InputStream inputStream = new FileInputStream("data.txt");
             DataInputStream dataInputStream = new DataInputStream(inputStream)) {
            int number = dataInputStream.readInt();
            float fraction = dataInputStream.readFloat();
            boolean flag = dataInputStream.readBoolean();
            String text = dataInputStream.readUTF();
            System.out.println("Number: " + number);
            System.out.println("Fraction: " + fraction);
            System.out.println("Flag: " + flag);
            System.out.println("Text: " + text);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,DataInputStream 从文件 data.txt 中读取 intfloatbooleanString 类型的数据,这些数据必须是之前通过 DataOutputStream 写入的。

  • DataOutputStreamDataOutputStream 允许应用程序以适当方式将基本数据类型和字符串写入底层输出流。
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class DataOutputStreamExample {
    public static void main(String[] args) {
        try (OutputStream outputStream = new FileOutputStream("data.txt");
             DataOutputStream dataOutputStream = new DataOutputStream(outputStream)) {
            int number = 12345;
            float fraction = 3.14f;
            boolean flag = true;
            String text = "Hello, Data Stream!";
            dataOutputStream.writeInt(number);
            dataOutputStream.writeFloat(fraction);
            dataOutputStream.writeBoolean(flag);
            dataOutputStream.writeUTF(text);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

此代码将 intfloatbooleanString 类型的数据通过 DataOutputStream 写入文件 data.txt

对象流

ObjectInputStream和ObjectOutputStream

ObjectInputStreamObjectOutputStream 用于对象的序列化和反序列化。

  • 对象序列化:将对象转换为字节序列的过程称为对象序列化。ObjectOutputStream 用于将对象写入输出流。要使一个对象可序列化,该对象的类必须实现 Serializable 接口。
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;

class Person implements Serializable {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class ObjectOutputStreamExample {
    public static void main(String[] args) {
        try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
            Person person = new Person("Alice", 30);
            objectOutputStream.writeObject(person);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这段代码中,Person 类实现了 Serializable 接口,ObjectOutputStreamPerson 对象写入名为 person.ser 的文件。

  • 对象反序列化:将字节序列转换回对象的过程称为对象反序列化。ObjectInputStream 用于从输入流中读取对象。
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class ObjectInputStreamExample {
    public static void main(String[] args) {
        try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("person.ser"))) {
            Person person = (Person) objectInputStream.readObject();
            System.out.println(person);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

此代码通过 ObjectInputStreamperson.ser 文件中读取 Person 对象,并将其输出。

打印流

PrintStream和PrintWriter

PrintStreamPrintWriter 主要用于格式化输出。

  • PrintStreamPrintStream 是一个字节流,它提供了一系列 print()println() 方法,用于将各种数据类型以文本形式输出。System.out 就是一个 PrintStream 实例。
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;

public class PrintStreamExample {
    public static void main(String[] args) {
        try (PrintStream printStream = new PrintStream(new FileOutputStream("output.txt"))) {
            int number = 100;
            float fraction = 2.5f;
            printStream.println("Number: " + number);
            printStream.println("Fraction: " + fraction);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在上述代码中,PrintStream 将格式化后的文本写入文件 output.txt

  • PrintWriterPrintWriter 是一个字符流,同样提供了 print()println() 方法用于格式化输出。它的使用方式与 PrintStream 类似,但更适合处理字符数据。
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

public class PrintWriterExample {
    public static void main(String[] args) {
        try (PrintWriter printWriter = new PrintWriter(new FileWriter("output.txt"))) {
            int number = 100;
            float fraction = 2.5f;
            printWriter.println("Number: " + number);
            printWriter.println("Fraction: " + fraction);
            printWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这里 PrintWriter 将格式化后的文本写入文件 output.txt,需要注意的是,使用 PrintWriter 时,如果不调用 flush() 方法,数据可能不会及时写入文件。

通过深入了解Java I/O流的基本概念和各种流的应用,开发者能够更加高效地处理输入输出操作,无论是在文件处理、网络通信还是其他数据传输场景中,都能运用合适的流来实现所需的功能。同时,合理地选择和组合不同类型的流,可以显著提高程序的性能和可读性。