Bootstrap

Java I/O流详解

什么是Java I/O流

  Java I/O流是一套用于在Java程序中处理数据输入和输出的类和接口。它们用于从各种数据源(如文件、网络等)读取数据和向这些数据源写入数据。主要分为字节流(处理字节数据)和字符流(处理文本数据)。

Java I/O流的分类

字节流与字符流

在Java中,字节流和字符流是处理数据输入和输出的两种主要方式。它们之间的主要区别在于处理的数据类型:

  1. 字节流(Byte Streams):字节流用于处理原始字节(8位)数据。这意味着它们可以用于处理任何类型的数据,如文本、图片、音频和视频文件等。字节流的基类是InputStream(用于输入操作)和OutputStream(用于输出操作)。一些常见的字节流实现包括FileInputStreamFileOutputStreamByteArrayInputStreamByteArrayOutputStream

  2. 字符流(Character Streams):字符流用于处理文本数据(字符)。这些流处理的是字符数据(通常是16位或更高),因此它们特别适用于处理Unicode文本。字符流的基类是Reader(用于输入操作)和Writer(用于输出操作)。一些常见的字符流实现包括FileReaderFileWriterStringReaderStringWriter

简而言之,字节流主要用于处理二进制数据,而字符流主要用于处理文本数据。选择哪种流取决于你的数据类型和特定需求。

Java I/O流主要类

字节流类

FileInputStream & FileOutputStream

FileInputStreamFileOutputStream是Java I/O流中用于处理文件输入和输出的字节流实现。它们分别继承自InputStreamOutputStream基类,可以直接处理二进制数据。

  1. FileInputStreamFileInputStream是用于从文件中读取数据的输入流。它可以用于读取任何类型的文件,如文本文件、图片、音频和视频文件等。FileInputStream将文件内容作为字节序列读取,每次可以读取一个或多个字节。以下是一个简单的FileInputStream示例:
import java.io.FileInputStream;
import java.io.IOException;

public class FileInputStreamExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("input.txt")) {
            int data;
            while ((data = fis.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  1. FileOutputStreamFileOutputStream是用于将数据写入文件的输出流。它可以用于创建新文件或覆盖/追加现有文件的内容。与FileInputStream类似,FileOutputStream也可以处理任何类型的文件。以下是一个简单的FileOutputStream示例:
import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputStreamExample {
    public static void main(String[] args) {
        String data = "Hello, World!";

        try (FileOutputStream fos = new FileOutputStream("output.txt")) {
            fos.write(data.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

DataInputStream & DataOutputStream
  1. DataInputStreamDataInputStream 是用于从数据源(如文件、网络连接等)按照特定格式读取基本数据类型和字符串的输入流。它的构造函数接收一个 InputStream 参数,例如 FileInputStreamDataInputStream 提供了各种方法,如 readInt()readFloat()readBoolean()readUTF() 等,用于从输入流中读取相应类型的数据。以下是一个简单的 DataInputStream 示例:
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class DataInputStreamExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("input.bin");
             DataInputStream dis = new DataInputStream(fis)) {
            int num = dis.readInt();
            float fnum = dis.readFloat();
            boolean flag = dis.readBoolean();
            String str = dis.readUTF();

            System.out.println(num);
            System.out.println(fnum);
            System.out.println(flag);
            System.out.println(str);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  1. DataOutputStreamDataOutputStream 是用于将基本数据类型和字符串按照特定格式写入目标(如文件、网络连接等)的输出流。它的构造函数接收一个 OutputStream 参数,例如 FileOutputStreamDataOutputStream 提供了各种方法,如 writeInt()writeFloat()writeBoolean()writeUTF() 等,用于向输出流中写入相应类型的数据。以下是一个简单的 DataOutputStream 示例:
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class DataOutputStreamExample {
    public static void main(String[] args) {
        try (FileOutputStream fos = new FileOutputStream("output.bin");
             DataOutputStream dos = new DataOutputStream(fos)) {
            int num = 42;
            float fnum = 3.14f;
            boolean flag = true;
            String str = "Hello, World!";

            dos.writeInt(num);
            dos.writeFloat(fnum);
            dos.writeBoolean(flag);
            dos.writeUTF(str);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3.DataInputStreamDataOutputStream 可以与其他流结合使用,以便实现更复杂的数据处理需求。在创建 DataInputStreamDataOutputStream 对象时,需要将它们与底层的 InputStreamOutputStream 对象关联起来。这种关联可以实现在不同类型的流之间的转换。

例如,我们可以将 DataInputStreamDataOutputStreamBufferedInputStreamBufferedOutputStream 结合使用,以提高数据读写的性能。以下是一个简单的示例:

import java.io.*;

public class DataStreamExample {
    public static void main(String[] args) {
        String fileName = "data.bin";
        try (FileOutputStream fos = new FileOutputStream(fileName);
             BufferedOutputStream bos = new BufferedOutputStream(fos);
             DataOutputStream dos = new DataOutputStream(bos)) {

            dos.writeInt(42);
            dos.writeDouble(3.14);
            dos.writeUTF("Hello, World!");

        } catch (IOException e) {
            e.printStackTrace();
        }

        try (FileInputStream fis = new FileInputStream(fileName);
             BufferedInputStream bis = new BufferedInputStream(fis);
             DataInputStream dis = new DataInputStream(bis)) {

            int intValue = dis.readInt();
            double doubleValue = dis.readDouble();
            String stringValue = dis.readUTF();

            System.out.println(intValue);
            System.out.println(doubleValue);
            System.out.println(stringValue);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这个示例中,我们首先使用 FileOutputStream 打开一个文件输出流,然后将其包装在一个 BufferedOutputStream 中,以缓冲写操作。接下来,我们将 BufferedOutputStream 传递给 DataOutputStream 的构造函数。这样,我们就可以使用 DataOutputStream 的方法将数据按照特定格式写入缓冲区,然后将缓冲区的内容写入文件。类似地,我们可以在读取数据时使用 FileInputStreamBufferedInputStreamDataInputStream 的组合。

总之,DataInputStreamDataOutputStream 可以与其他流结合使用,以实现更高级的功能和更高效的数据处理。将它们与其他流组合时,可以轻松地在不同类型的流之间进行转换。

ps: 当使用 DataInputStream 读取文件时,文件中的格式和数据类型必须与读取操作相符。换句话说,文件中的数据顺序和类型应该与你在程序中使用的 DataInputStream 方法相匹配。

例如,如果你先使用 DataOutputStreamwriteInt() 方法写入一个整数,然后使用 writeDouble() 方法写入一个双精度浮点数,那么在读取该文件时,你需要首先使用 DataInputStreamreadInt() 方法读取整数,接着使用 readDouble() 方法读取双精度浮点数。如果你尝试使用错误的方法读取数据,可能会导致错误的结果或抛出异常。

因此,在使用 DataInputStreamDataOutputStream 读写文件时,确保按照相同的顺序和数据类型进行操作是很重要的。如果文件的结构非常复杂,你可能需要定义一个明确的文件格式规范,以便在读取和写入数据时遵循相同的规则。另外,一种解决方法是使用更高级的数据序列化和反序列化库(如 Google 的 Protocol Buffers 或 Java 自带的序列化机制),这些库可以自动处理数据类型和结构的匹配问题。

BufferedInputStream & BufferedOutputStream

BufferedInputStreamBufferedOutputStream 是 Java I/O 流中用于缓冲输入和输出操作的字节流实现。它们分别继承自 FilterInputStreamFilterOutputStream,可以与其他 InputStreamOutputStream 结合使用,以提高数据读写的性能。

  1. BufferedInputStreamBufferedInputStream 是用于缓冲输入操作的字节流类。它在内部维护一个字节缓冲区,用于一次性从底层输入流中读取多个字节。这样可以减少实际的 I/O 操作次数,从而提高读取性能。要使用 BufferedInputStream,你需要将它与一个底层的 InputStream 对象关联,例如 FileInputStream。以下是一个简单的 BufferedInputStream 示例:
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;

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

  1. BufferedOutputStreamBufferedOutputStream 是用于缓冲输出操作的字节流类。与 BufferedInputStream 类似,它在内部维护一个字节缓冲区,用于一次性将多个字节写入底层输出流。这样可以减少实际的 I/O 操作次数,从而提高写入性能。要使用 BufferedOutputStream,你需要将它与一个底层的 OutputStream 对象关联,例如 FileOutputStream。以下是一个简单的 BufferedOutputStream 示例:
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class BufferedOutputStreamExample {
    public static void main(String[] args) {
        String data = "Hello, World!";

        try (FileOutputStream fos = new FileOutputStream("output.txt");
             BufferedOutputStream bos = new BufferedOutputStream(fos)) {
            bos.write(data.getBytes());
            bos.flush(); // Ensure that all buffered data is written to the output stream
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

ByteArrayInputStream&ByteArrayOutputStream

ByteArrayInputStreamByteArrayOutputStream是Java IO类库中的两个基于内存的流类。它们都是使用一个字节数组作为数据源,不需要从磁盘或网络中读取或写入数据,因此速度较快。下面是这两个类的一些常见用法:

ByteArrayInputStream类:

  • 从字节数组中读取数据:可以使用ByteArrayInputStream类的read()read(byte[] b, int off, int len)等方法,从字节数组中读取指定数量的字节数据,并返回读取的字节数。

  • 将字节数组包装为输入流:可以使用ByteArrayInputStream类的构造函数,将一个字节数组包装为一个输入流对象,然后可以对这个输入流进行读取操作。

    byte[] data = new byte[] {0x01, 0x02, 0x03};
    ByteArrayInputStream in = new ByteArrayInputStream(data);
    int byteRead = in.read(); // 读取一个字节数据
    byte[] buffer = new byte[1024];
    int bytesRead = in.read(buffer, 0, buffer.length); // 读取指定数量的字节数据
    
    

ByteArrayOutputStream类:

  • 向字节数组中写入数据:可以使用ByteArrayOutputStream类的write()write(byte[] b, int off, int len)等方法,向字节数组中写入指定数量的字节数据。

  • 获取写入的字节数组:可以使用ByteArrayOutputStream类的toByteArray()方法,返回写入的字节数组。

  • 将字节数组包装为输出流:可以使用ByteArrayOutputStream类的构造函数,将一个字节数组包装为一个输出流对象,然后可以对这个输出流进行写入操作。

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    out.write(0x01); // 写入一个字节数据
    byte[] data = new byte[] {0x02, 0x03, 0x04};
    out.write(data, 0, data.length); // 写入指定数量的字节数据
    byte[] result = out.toByteArray(); // 获取写入的字节数组
    

字符流类

FileReader & FileWriter

FileReaderFileWriter 是 Java I/O 流中用于处理字符输入和输出的字符流实现。它们分别继承自 InputStreamReaderOutputStreamWriter,并为文件读取和写入操作提供方便的方法。

  1. FileReaderFileReader 是用于从文件中读取字符数据的字符流类。它的构造函数接收一个文件名或 File 对象。FileReader 可以逐个字符地读取文件,或者一次性读取多个字符到字符数组中。以下是一个简单的 FileReader 示例:
import java.io.FileReader;
import java.io.IOException;

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

  1. FileWriterFileWriter 是用于将字符数据写入文件的字符流类。与 FileReader 类似,它的构造函数也接收一个文件名或 File 对象。FileWriter 可以逐个字符地写入文件,或者一次性写入多个字符从字符数组中。以下是一个简单的 FileWriter 示例:
import java.io.FileWriter;
import java.io.IOException;

public class FileWriterExample {
    public static void main(String[] args) {
        String data = "Hello, World!";

        try (FileWriter fw = new FileWriter("output.txt")) {
            fw.write(data);
            fw.flush(); 
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

fr.read() 是一个方法调用,用于从 FileReader(或更一般地说,从任何 java.io.Reader 子类)读取单个字符。read() 方法属于 java.io.Reader 类(FileReader 的父类),它的作用是从字符输入流中读取一个字符。

read() 方法返回一个整数,该整数表示从输入流中读取的字符的 Unicode 代码点(在 0 到 65535 之间)。当已到达流的末尾,没有可读取的字符时,read() 方法返回 -1,表示已经读取完所有数据。

fw.flush() 是一个方法调用,用于确保所有缓冲的数据被写入底层文件。flush() 方法属于 java.io.Writer 类(FileWriter 的父类),它的作用是清空缓冲区,并将任何还未写入的数据立即写入底层输出流。

在某些情况下,为了提高性能,Writer 可能会将写入操作的数据暂存在内部缓冲区,而不是立即写入底层输出流(例如文件)。当调用 flush() 方法时,它会强制将缓冲区中的数据写入输出流,确保数据的完整性。

在使用 FileWriter 或其他 Writer 子类时,建议在关闭流之前调用 flush() 方法,以确保所有数据都已写入文件。不过,在使用 try-with-resources 结构时,当流对象离开 try 块范围时,它们的 close() 方法会自动被调用,close() 方法会隐式调用 flush() 方法。所以,在这种情况下,你不一定需要显式调用 flush()。但是,如果你想在关闭流之前确保数据已经写入文件,显式调用 flush() 是一个好习惯。

FileReaderFileWriter 是处理字符输入和输出的字符流类,特别适合处理文本文件。它们提供了一种方便的方法来读取和写入字符数据。在处理文本文件时,相较于字节流,字符流可以更好地处理字符编码和解码,以确保文本数据的正确表示。

BufferedReader & BufferedWriter

BufferedReaderBufferedWriter 是 Java I/O 流中用于缓冲字符输入和输出操作的字符流实现。它们分别继承自 ReaderWriter 类,可以与其他字符流结合使用,以提高数据读写的性能。

  1. BufferedReaderBufferedReader 是用于缓冲字符输入操作的字符流类。它在内部维护一个字符缓冲区,用于一次性从底层输入流中读取多个字符。这样可以减少实际的 I/O 操作次数,从而提高读取性能。BufferedReader 还提供了一个方便的 readLine() 方法,用于一次性读取一行文本。要使用 BufferedReader,你需要将它与一个底层的 Reader 对象关联,例如 FileReader。以下是一个简单的 BufferedReader 示例:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

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

  1. BufferedWriterBufferedWriter 是用于缓冲字符输出操作的字符流类。与 BufferedReader 类似,它在内部维护一个字符缓冲区,用于一次性将多个字符写入底层输出流。这样可以减少实际的 I/O 操作次数,从而提高写入性能。要使用 BufferedWriter,你需要将它与一个底层的 Writer 对象关联,例如 FileWriter。以下是一个简单的 BufferedWriter 示例:
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class BufferedWriterExample {
    public static void main(String[] args) {
        String data = "Hello, World!";

        try (FileWriter fw = new FileWriter("output.txt");
             BufferedWriter bw = new BufferedWriter(fw)) {
            bw.write(data);
            bw.newLine(); 
            bw.flush(); 
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

InputStreamReader & OutputStreamWriter

InputStreamReaderOutputStreamWriter 是 Java I/O 流中用于在字节流和字符流之间进行转换的桥梁。它们分别继承自 ReaderWriter 类,可以与其他输入/输出流结合使用,以处理字符编码和解码。

  1. InputStreamReaderInputStreamReader 是一个字符输入流,它将底层的字节输入流(如 InputStream 子类)转换为字符流。它可以处理字符集编码,从字节转换为 Java 中的 Unicode 字符。要使用 InputStreamReader,你需要将其与一个底层的 InputStream 对象关联,例如 FileInputStream。可以在构造函数中指定字符集,如果不指定,则使用平台默认的字符集。以下是一个简单的 InputStreamReader 示例:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class InputStreamReaderExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("input.txt");
             InputStreamReader isr = new InputStreamReader(fis, "UTF-8")) {
            int data;
            while ((data = isr.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

  1. OutputStreamWriterOutputStreamWriter 是一个字符输出流,它将字符流转换为底层的字节输出流(如 OutputStream 子类)。它可以处理字符集编码,将 Java 中的 Unicode 字符转换为字节。要使用 OutputStreamWriter,你需要将其与一个底层的 OutputStream 对象关联,例如 FileOutputStream。可以在构造函数中指定字符集,如果不指定,则使用平台默认的字符集。以下是一个简单的 OutputStreamWriter 示例:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;

public class OutputStreamWriterExample {
    public static void main(String[] args) {
        String data = "Hello, World!";

        try (FileOutputStream fos = new FileOutputStream("output.txt");
             OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8")) {
            osw.write(data);
            osw.flush(); // Ensure that all buffered data is written to the output stream
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

其他流类

SequenceInputStream

SequenceInputStream 是 Java I/O 流中的一个特殊输入流,它允许你将两个或多个输入流连接起来,使它们看起来像一个单独的输入流。它继承自 InputStream 类。当你从 SequenceInputStream 读取数据时,它首先读取第一个输入流的数据,然后读取第二个输入流的数据,依此类推,直到所有输入流的数据都被读取。这样,你可以在一个循环中连续读取多个输入流,而不必逐个处理它们。

要使用 SequenceInputStream,你可以将两个 InputStream 对象传递给构造函数,或者传递一个 Enumeration,其中包含要连接的输入流。以下是一个简单的 SequenceInputStream 示例,它将两个文件的内容连接起来:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;

public class SequenceInputStreamExample {
    public static void main(String[] args) {
        try (InputStream is1 = new FileInputStream("input1.txt");
             InputStream is2 = new FileInputStream("input2.txt");
             SequenceInputStream sis = new SequenceInputStream(is1, is2)) {
            int data;
            while ((data = sis.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这个示例中,我们创建了两个 FileInputStream 对象(is1is2),分别关联到名为 “input1.txt” 和 “input2.txt” 的文件。然后,我们使用这两个输入流创建一个 SequenceInputStreamsis)。在循环中,我们使用 sis.read() 读取数据,首先读取 “input1.txt” 的内容,然后读取 “input2.txt” 的内容。这样,我们可以将两个文件的内容连续输出到控制台。

总之,SequenceInputStream 是一种特殊的输入流,允许你将多个输入流连接成一个输入流。这在需要按顺序处理多个输入源的场景中非常有用。

PipedInputStream & PipedOutputStream

PipedInputStreamPipedOutputStream 是 Java I/O 流中用于在两个线程之间传输数据的一对特殊字节流。它们分别继承自 InputStreamOutputStream 类。这两个类可以创建管道,一个线程通过 PipedOutputStream 向管道写入数据,而另一个线程通过 PipedInputStream 从管道读取数据。这种方式允许在两个线程之间实现线程安全的数据传输。

以下是一个简单的示例,演示了如何使用 PipedInputStreamPipedOutputStream 在两个线程之间传输数据:

import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

public class PipedStreamExample {
    public static void main(String[] args) throws IOException {
        PipedInputStream pis = new PipedInputStream();
        PipedOutputStream pos = new PipedOutputStream(pis);

        // Writer thread
        Thread writerThread = new Thread(() -> {
            try {
                for (int i = 1; i <= 10; i++) {
                    pos.write(i);
                    pos.flush(); // Ensure that data is sent to the input stream
                    Thread.sleep(1000); // Sleep for 1 second
                }
                pos.close(); // Close the output stream
            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
            }
        });

        // Reader thread
        Thread readerThread = new Thread(() -> {
            try {
                int data;
                while ((data = pis.read()) != -1) {
                    System.out.println("Received data: " + data);
                }
                pis.close(); // Close the input stream
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        writerThread.start(); // Start the writer thread
        readerThread.start(); // Start the reader thread
    }
}

在此示例中,我们首先创建了一个 PipedInputStreampis)和一个 PipedOutputStreampos),并将它们连接在一起。然后,我们创建了两个线程:一个用于写入数据(writerThread),另一个用于读取数据(readerThread)。writerThreadpos 写入 1 到 10 的整数,每次写入后暂停 1 秒。readerThreadpis 读取数据,并将接收到的数据输出到控制台。这个示例演示了如何在两个线程之间使用管道传输数据。

总之,PipedInputStreamPipedOutputStream 是一对特殊的字节流,用于在两个线程之间实现线程安全的数据传输。在需要在多线程环境中传输数据时,这些类非常有用。

ObjectInputStream & ObjectOutputStream

ObjectInputStreamObjectOutputStream 是 Java I/O 流中用于实现对象序列化和反序列化的特殊流。它们分别继承自 InputStreamOutputStream 类。对象序列化是将对象的状态(即其属性值)转换为字节流的过程,而反序列化是从字节流重建对象的状态的过程。这些流允许你将对象写入文件、网络套接字或其他输出流,然后从输入流中重新构建对象。

在使用 ObjectInputStreamObjectOutputStream 之前,请确保你的对象实现了 Serializable 接口,这表示对象可以序列化和反序列化。Serializable 接口是一个标记接口,没有任何方法需要实现。

以下是一个简单的示例,演示了如何使用 ObjectInputStreamObjectOutputStream 进行对象序列化和反序列化:

import java.io.*;

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 ObjectStreamExample {
    public static void main(String[] args) {
        Person person = new Person("Alice", 30);

        // Serialize the object
        try (FileOutputStream fos = new FileOutputStream("person.ser");
             ObjectOutputStream oos = new ObjectOutputStream(fos)) {
            oos.writeObject(person);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Deserialize the object
        try (FileInputStream fis = new FileInputStream("person.ser");
             ObjectInputStream ois = new ObjectInputStream(fis)) {
            Person deserializedPerson = (Person) ois.readObject();
            System.out.println("Deserialized person: " + deserializedPerson);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

在此示例中,我们首先创建了一个实现了 Serializable 接口的 Person 类。接着,我们创建了一个 Person 对象(person)并使用 ObjectOutputStream 将其序列化到一个名为 “person.ser” 的文件。然后,我们使用 ObjectInputStream 从文件中反序列化 Person 对象,并将反序列化的对象输出到控制台。

总之,ObjectInputStreamObjectOutputStream 是用于实现对象序列化和反序列化的特殊流。它们允许你将对象的状态转换为字节流,并从字节流中重建对象。这在需要将对象写入文件、网络套接字或其他输出流时非常有用。请注意,要使用这些流,对象必须实现 Serializable 接口。

Java I/O流实际应用示例

文件复制

方法1:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class FileCopyExample {
    public static void main(String[] args) {
        Path sourcePath = Paths.get("source.txt");
        Path targetPath = Paths.get("target.txt");

        try {
            Files.copy(sourcePath, targetPath);
        } catch (IOException e) {
            e.printStackTrace();
         }
    }
}

方法2:

import java.io.*;

public class FileCopyExample {
    public static void main(String[] args) {
        String sourceFile = "source.txt";
        String targetFile = "target.txt";

        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceFile));
             BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(targetFile))) {
            int data;
            while ((data = bis.read()) != -1) {
                bos.write(data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

文本文件的读写

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class TextFileReadExample {
    public static void main(String[] args) {
        String fileName = "example.txt";

        try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

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

public class TextFileWriteExample {
    public static void main(String[] args) {
        String fileName = "example.txt";
        String[] lines = {"Line 1", "Line 2", "Line 3"};

        try (BufferedWriter bw = new BufferedWriter(new FileWriter(fileName))) {
            for (String line : lines) {
                bw.write(line);
                bw.newLine(); // Write a new line character
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

数据类型的读写

import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class DataTypeWriteExample {
    public static void main(String[] args) {
        String fileName = "data.bin";

        try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(fileName))) {
            dos.writeInt(42);
            dos.writeDouble(3.14159);
            dos.writeBoolean(true);
            dos.writeUTF("Hello, World!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class DataTypeReadExample {
    public static void main(String[] args) {
        String fileName = "data.bin";

        try (DataInputStream dis = new DataInputStream(new FileInputStream(fileName))) {
            int intValue = dis.readInt();
            double doubleValue = dis.readDouble();
            boolean booleanValue = dis.readBoolean();
            String utfValue = dis.readUTF();

            System.out.println("int: " + intValue);
            System.out.println("double: " + doubleValue);
            System.out.println("boolean: " + booleanValue);
            System.out.println("UTF: " + utfValue);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

对象序列化与反序列化

import java.io.Serializable;

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;

    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 +
                '}';
    }
}

我们为类添加了一个名为 serialVersionUID 的字段。这是一个版本控制机制,用于在反序列化过程中检查序列化的类和反序列化的类是否兼容。在生产环境中,为每个可序列化的类分配唯一的 serialVersionUID 是一个好习惯。

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializeExample {
    public static void main(String[] args) {
        String fileName = "person.ser";
        Person person = new Person("John Doe", 30);

        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName))) {
            oos.writeObject(person);
            System.out.println("Person object serialized: " + person);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class DeserializeExample {
    public static void main(String[] args) {
        String fileName = "person.ser";
        Person deserializedPerson = null;

        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName))) {
            deserializedPerson = (Person) ois.readObject();
            System.out.println("Person object deserialized: " + deserializedPerson);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

管道通信

定义发送者线程类

创建一个名为 Sender 的线程类,将数据写入 PipedOutputStream

import java.io.IOException;
import java.io.PipedOutputStream;

public class Sender extends Thread {
    private PipedOutputStream pos;

    public Sender(PipedOutputStream pos) {
        this.pos = pos;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                pos.write(("Message " + i + "\n").getBytes());
                Thread.sleep(500);
            }
            pos.close();
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

定义接收者线程类

创建一个名为 Receiver 的线程类,从 PipedInputStream 读取数据:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PipedInputStream;

public class Receiver extends Thread {
    private PipedInputStream pis;

    public Receiver(PipedInputStream pis) {
        this.pis = pis;
    }

    @Override
    public void run() {
        try (BufferedReader br = new BufferedReader(new InputStreamReader(pis))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println("Received: " + line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

创建并运行 Sender 和 Receiver 线程

import java.io.IOException;

public class PipeCommunicationExample {
    public static void main(String[] args) {
        try {
            PipedOutputStream pos = new PipedOutputStream();
            PipedInputStream pis = new PipedInputStream(pos);

            Sender sender = new Sender(pos);
            Receiver receiver = new Receiver(pis);

            sender.start();
            receiver.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

合并文件

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

public class FileMergeExample {
    public static void main(String[] args) {
        String outputFile = "mergedFile.bin";
        List<String> inputFiles = new ArrayList<>();
        inputFiles.add("file1.bin");
        inputFiles.add("file2.bin");
        inputFiles.add("file3.bin");

        mergeFiles(inputFiles, outputFile);
    }

    public static void mergeFiles(List<String> inputFiles, String outputFile) {
        List<FileInputStream> fileInputStreams = new ArrayList<>();

        try {
            for (String inputFile : inputFiles) {
                fileInputStreams.add(new FileInputStream(inputFile));
            }

            // Create an Enumeration for the FileInputStreams
            Enumeration<FileInputStream> fileInputStreamEnumeration = Collections.enumeration(fileInputStreams);

            // Create a SequenceInputStream to concatenate the FileInputStreams
            SequenceInputStream sequenceInputStream = new SequenceInputStream(fileInputStreamEnumeration);

            // Create FileOutputStream for the output file
            FileOutputStream fileOutputStream = new FileOutputStream(outputFile);

            // Read from the SequenceInputStream and write to the output file
            int data;
            while ((data = sequenceInputStream.read()) != -1) {
                fileOutputStream.write(data);
            }

            // Close all resources
            sequenceInputStream.close();
            fileOutputStream.close();
            for (FileInputStream fis : fileInputStreams) {
                fis.close();
            }

            System.out.println("Files merged successfully.");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这个示例中,我们首先创建了一个包含输入文件路径的列表。然后,我们使用 mergeFiles() 方法将这些文件合并为一个输出文件。

mergeFiles() 方法的关键部分如下:

  1. 为所有输入文件创建 FileInputStream 对象。
  2. 使用 Collections.enumeration() 创建一个 FileInputStream 对象的枚举。
  3. 使用 SequenceInputStream 类将输入文件的内容串联起来。这个类接受一个枚举作为参数,当从 SequenceInputStream 读取数据时,它会依次从每个输入流中读取数据。
  4. 使用 FileOutputStream 类创建一个输出文件。
  5. SequenceInputStream 读取数据,并将其写入输出文件。
  6. 最后,关闭所有打开的资源。

Java I/O流异常处理

Java I/O 流异常处理是编写健壮、稳定的程序的关键。处理 I/O 流异常的重要性在于以下几点:

  1. 确保程序不会意外崩溃:I/O 操作可能因为多种原因导致异常,例如文件不存在、文件无法读取、磁盘空间不足等。如果没有正确处理这些异常,程序可能会在运行时崩溃,导致数据丢失或用户体验受损。

  2. 提供有用的错误信息:通过捕获和处理异常,我们可以向用户或开发者提供有关错误原因的详细信息。这有助于快速定位和解决问题,从而改善用户体验和提高开发效率。

  3. 确保资源得到正确释放:在进行 I/O 操作时,我们通常会打开文件、网络连接等资源。如果在操作过程中出现异常,没有正确处理这些异常可能导致资源无法释放,从而导致资源泄漏。处理异常可以确保在发生错误时,资源能够被正确关闭和释放。

不处理 I/O 流异常可能导致以下后果:

  1. 程序崩溃:如果异常没有被捕获和处理,程序可能会在运行时崩溃,导致用户体验受损。

  2. 数据丢失:如果在文件写入操作中出现异常,而没有进行正确的异常处理,可能导致数据写入不完整,从而导致数据丢失。

  3. 资源泄漏:如果没有正确处理异常,可能导致文件、网络连接等资源未能关闭和释放,从而导致资源泄漏。资源泄漏可能会影响程序性能,甚至导致系统崩溃。

  4. 错误信息不明确:如果异常没有被捕获,我们无法获得关于错误原因的详细信息,这将使问题难以定位和解决。

因此,正确处理 I/O 流异常对于编写健壮、稳定的程序至关重要。通过使用 try-catch 语句或 try-with-resources 语句捕获和处理异常,我们可以确保程序在发生错误时能够正常运行,提供有用的错误信息,并避免资源泄漏。

I/O流与装饰器模式

在 Java 中,装饰器模式(Decorator Pattern)是一种结构型设计模式,允许在不修改现有类的基础上动态地扩展对象的功能。在 Java I/O 流中,装饰器模式广泛应用于 FilterInputStream、FilterOutputStream、FilterReader 和 FilterWriter 这些类。这些类充当装饰器角色,可以包装其他流对象并为它们提供额外的功能,如缓冲、数据处理等。

继承关系


InputStream                   OutputStream
├── FileInputStream           ├── FileOutputStream
├── ByteArrayInputStream      ├── ByteArrayOutputStream
├── ObjectInputStream         ├── ObjectOutputStream
├── PipedInputStream          ├── PipedOutputStream
├── SequenceInputStream       │
├── BufferedInputStream       ├── BufferedOutputStream
└── FilterInputStream         └── FilterOutputStream
    ├── DataInputStream       │   ├── DataOutputStream
    └── PushbackInputStream   │   └── PrintStream

Reader                        Writer
├── InputStreamReader         ├── OutputStreamWriter
│   └── FileReader            │   └── FileWriter
├── BufferedReader            ├── BufferedWriter
├── StringReader              ├── StringWriter
├── CharArrayReader           ├── CharArrayWriter
├── PipedReader               ├── PipedWriter
└── FilterReader              └── FilterWriter
    └── PushbackReader        │   └── PrintWriter

总结

在 Java I/O 编程中,选择合适的流非常重要。以下是一些关于如何选择正确流的指南:

  1. 数据类型:首先确定要处理的数据类型。如果要处理的是字节数据(如图像、音频、视频等),则选择字节流(如 FileInputStreamFileOutputStream 等)。如果要处理的是字符数据(如文本文件),则选择字符流(如 FileReaderFileWriter 等)。

  2. 输入还是输出:确定是需要读取数据(输入)还是写入数据(输出)。对于输入操作,选择输入流(如 FileInputStreamFileReader 等),对于输出操作,选择输出流(如 FileOutputStreamFileWriter 等)。

  3. 缓冲还是非缓冲:考虑是否需要缓冲来提高 I/O 性能。对于大量连续的 I/O 操作,使用缓冲流(如 BufferedInputStreamBufferedOutputStreamBufferedReaderBufferedWriter 等)通常会提高性能。缓冲流在内部使用缓冲区,可以减少实际的磁盘或网络访问次数,从而提高效率。

  4. 数据处理需求:根据需要处理的数据类型,选择合适的流。例如,如果需要处理基本数据类型(如整数、浮点数等),可以使用 DataInputStreamDataOutputStream。如果需要处理对象序列化,可以使用 ObjectInputStreamObjectOutputStream

  5. 特定场景:针对特定场景选择合适的流。例如,在处理多个文件合并时,可以使用 SequenceInputStream;在处理线程间通信时,可以使用管道流,如 PipedInputStreamPipedOutputStream,或 PipedReaderPipedWriter

以下是一些常见场景及建议使用的流:

  • 处理文本文件:使用 FileReaderFileWriterBufferedReaderBufferedWriter
  • 处理二进制文件:使用 FileInputStreamFileOutputStreamBufferedInputStreamBufferedOutputStream
  • 读取或写入基本数据类型:使用 DataInputStreamDataOutputStream
  • 对象序列化和反序列化:使用 ObjectInputStreamObjectOutputStream
  • 合并多个文件:使用 SequenceInputStream
  • 线程间通信:使用 PipedInputStreamPipedOutputStream,或 PipedReaderPipedWriter
;