目录
什么是Java I/O流
Java I/O流是一套用于在Java程序中处理数据输入和输出的类和接口。它们用于从各种数据源(如文件、网络等)读取数据和向这些数据源写入数据。主要分为字节流(处理字节数据)和字符流(处理文本数据)。
Java I/O流的分类
字节流与字符流
在Java中,字节流和字符流是处理数据输入和输出的两种主要方式。它们之间的主要区别在于处理的数据类型:
-
字节流(Byte Streams):字节流用于处理原始字节(8位)数据。这意味着它们可以用于处理任何类型的数据,如文本、图片、音频和视频文件等。字节流的基类是
InputStream
(用于输入操作)和OutputStream
(用于输出操作)。一些常见的字节流实现包括FileInputStream
、FileOutputStream
、ByteArrayInputStream
和ByteArrayOutputStream
。 -
字符流(Character Streams):字符流用于处理文本数据(字符)。这些流处理的是字符数据(通常是16位或更高),因此它们特别适用于处理Unicode文本。字符流的基类是
Reader
(用于输入操作)和Writer
(用于输出操作)。一些常见的字符流实现包括FileReader
、FileWriter
、StringReader
和StringWriter
。
简而言之,字节流主要用于处理二进制数据,而字符流主要用于处理文本数据。选择哪种流取决于你的数据类型和特定需求。
Java I/O流主要类
字节流类
FileInputStream & FileOutputStream
FileInputStream
和FileOutputStream
是Java I/O流中用于处理文件输入和输出的字节流实现。它们分别继承自InputStream
和OutputStream
基类,可以直接处理二进制数据。
FileInputStream
:FileInputStream
是用于从文件中读取数据的输入流。它可以用于读取任何类型的文件,如文本文件、图片、音频和视频文件等。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();
}
}
}
FileOutputStream
:FileOutputStream
是用于将数据写入文件的输出流。它可以用于创建新文件或覆盖/追加现有文件的内容。与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
DataInputStream
:DataInputStream
是用于从数据源(如文件、网络连接等)按照特定格式读取基本数据类型和字符串的输入流。它的构造函数接收一个InputStream
参数,例如FileInputStream
。DataInputStream
提供了各种方法,如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();
}
}
}
DataOutputStream
:DataOutputStream
是用于将基本数据类型和字符串按照特定格式写入目标(如文件、网络连接等)的输出流。它的构造函数接收一个OutputStream
参数,例如FileOutputStream
。DataOutputStream
提供了各种方法,如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.DataInputStream
和 DataOutputStream
可以与其他流结合使用,以便实现更复杂的数据处理需求。在创建 DataInputStream
和 DataOutputStream
对象时,需要将它们与底层的 InputStream
和 OutputStream
对象关联起来。这种关联可以实现在不同类型的流之间的转换。
例如,我们可以将 DataInputStream
和 DataOutputStream
与 BufferedInputStream
和 BufferedOutputStream
结合使用,以提高数据读写的性能。以下是一个简单的示例:
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
的方法将数据按照特定格式写入缓冲区,然后将缓冲区的内容写入文件。类似地,我们可以在读取数据时使用 FileInputStream
、BufferedInputStream
和 DataInputStream
的组合。
总之,DataInputStream
和 DataOutputStream
可以与其他流结合使用,以实现更高级的功能和更高效的数据处理。将它们与其他流组合时,可以轻松地在不同类型的流之间进行转换。
ps: 当使用 DataInputStream
读取文件时,文件中的格式和数据类型必须与读取操作相符。换句话说,文件中的数据顺序和类型应该与你在程序中使用的 DataInputStream
方法相匹配。
例如,如果你先使用 DataOutputStream
的 writeInt()
方法写入一个整数,然后使用 writeDouble()
方法写入一个双精度浮点数,那么在读取该文件时,你需要首先使用 DataInputStream
的 readInt()
方法读取整数,接着使用 readDouble()
方法读取双精度浮点数。如果你尝试使用错误的方法读取数据,可能会导致错误的结果或抛出异常。
因此,在使用 DataInputStream
和 DataOutputStream
读写文件时,确保按照相同的顺序和数据类型进行操作是很重要的。如果文件的结构非常复杂,你可能需要定义一个明确的文件格式规范,以便在读取和写入数据时遵循相同的规则。另外,一种解决方法是使用更高级的数据序列化和反序列化库(如 Google 的 Protocol Buffers 或 Java 自带的序列化机制),这些库可以自动处理数据类型和结构的匹配问题。
BufferedInputStream & BufferedOutputStream
BufferedInputStream
和 BufferedOutputStream
是 Java I/O 流中用于缓冲输入和输出操作的字节流实现。它们分别继承自 FilterInputStream
和 FilterOutputStream
,可以与其他 InputStream
和 OutputStream
结合使用,以提高数据读写的性能。
BufferedInputStream
:BufferedInputStream
是用于缓冲输入操作的字节流类。它在内部维护一个字节缓冲区,用于一次性从底层输入流中读取多个字节。这样可以减少实际的 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();
}
}
}
BufferedOutputStream
:BufferedOutputStream
是用于缓冲输出操作的字节流类。与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
ByteArrayInputStream
和ByteArrayOutputStream
是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
FileReader
和 FileWriter
是 Java I/O 流中用于处理字符输入和输出的字符流实现。它们分别继承自 InputStreamReader
和 OutputStreamWriter
,并为文件读取和写入操作提供方便的方法。
FileReader
:FileReader
是用于从文件中读取字符数据的字符流类。它的构造函数接收一个文件名或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();
}
}
}
FileWriter
:FileWriter
是用于将字符数据写入文件的字符流类。与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()
是一个好习惯。
FileReader
和 FileWriter
是处理字符输入和输出的字符流类,特别适合处理文本文件。它们提供了一种方便的方法来读取和写入字符数据。在处理文本文件时,相较于字节流,字符流可以更好地处理字符编码和解码,以确保文本数据的正确表示。
BufferedReader & BufferedWriter
BufferedReader
和 BufferedWriter
是 Java I/O 流中用于缓冲字符输入和输出操作的字符流实现。它们分别继承自 Reader
和 Writer
类,可以与其他字符流结合使用,以提高数据读写的性能。
BufferedReader
:BufferedReader
是用于缓冲字符输入操作的字符流类。它在内部维护一个字符缓冲区,用于一次性从底层输入流中读取多个字符。这样可以减少实际的 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();
}
}
}
BufferedWriter
:BufferedWriter
是用于缓冲字符输出操作的字符流类。与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
InputStreamReader
和 OutputStreamWriter
是 Java I/O 流中用于在字节流和字符流之间进行转换的桥梁。它们分别继承自 Reader
和 Writer
类,可以与其他输入/输出流结合使用,以处理字符编码和解码。
InputStreamReader
:InputStreamReader
是一个字符输入流,它将底层的字节输入流(如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();
}
}
}
OutputStreamWriter
:OutputStreamWriter
是一个字符输出流,它将字符流转换为底层的字节输出流(如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
对象(is1
和 is2
),分别关联到名为 “input1.txt” 和 “input2.txt” 的文件。然后,我们使用这两个输入流创建一个 SequenceInputStream
(sis
)。在循环中,我们使用 sis.read()
读取数据,首先读取 “input1.txt” 的内容,然后读取 “input2.txt” 的内容。这样,我们可以将两个文件的内容连续输出到控制台。
总之,SequenceInputStream
是一种特殊的输入流,允许你将多个输入流连接成一个输入流。这在需要按顺序处理多个输入源的场景中非常有用。
PipedInputStream & PipedOutputStream
PipedInputStream
和 PipedOutputStream
是 Java I/O 流中用于在两个线程之间传输数据的一对特殊字节流。它们分别继承自 InputStream
和 OutputStream
类。这两个类可以创建管道,一个线程通过 PipedOutputStream
向管道写入数据,而另一个线程通过 PipedInputStream
从管道读取数据。这种方式允许在两个线程之间实现线程安全的数据传输。
以下是一个简单的示例,演示了如何使用 PipedInputStream
和 PipedOutputStream
在两个线程之间传输数据:
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
}
}
在此示例中,我们首先创建了一个 PipedInputStream
(pis
)和一个 PipedOutputStream
(pos
),并将它们连接在一起。然后,我们创建了两个线程:一个用于写入数据(writerThread
),另一个用于读取数据(readerThread
)。writerThread
向 pos
写入 1 到 10 的整数,每次写入后暂停 1 秒。readerThread
从 pis
读取数据,并将接收到的数据输出到控制台。这个示例演示了如何在两个线程之间使用管道传输数据。
总之,PipedInputStream
和 PipedOutputStream
是一对特殊的字节流,用于在两个线程之间实现线程安全的数据传输。在需要在多线程环境中传输数据时,这些类非常有用。
ObjectInputStream & ObjectOutputStream
ObjectInputStream
和 ObjectOutputStream
是 Java I/O 流中用于实现对象序列化和反序列化的特殊流。它们分别继承自 InputStream
和 OutputStream
类。对象序列化是将对象的状态(即其属性值)转换为字节流的过程,而反序列化是从字节流重建对象的状态的过程。这些流允许你将对象写入文件、网络套接字或其他输出流,然后从输入流中重新构建对象。
在使用 ObjectInputStream
和 ObjectOutputStream
之前,请确保你的对象实现了 Serializable
接口,这表示对象可以序列化和反序列化。Serializable
接口是一个标记接口,没有任何方法需要实现。
以下是一个简单的示例,演示了如何使用 ObjectInputStream
和 ObjectOutputStream
进行对象序列化和反序列化:
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
对象,并将反序列化的对象输出到控制台。
总之,ObjectInputStream
和 ObjectOutputStream
是用于实现对象序列化和反序列化的特殊流。它们允许你将对象的状态转换为字节流,并从字节流中重建对象。这在需要将对象写入文件、网络套接字或其他输出流时非常有用。请注意,要使用这些流,对象必须实现 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()
方法的关键部分如下:
- 为所有输入文件创建
FileInputStream
对象。 - 使用
Collections.enumeration()
创建一个FileInputStream
对象的枚举。 - 使用
SequenceInputStream
类将输入文件的内容串联起来。这个类接受一个枚举作为参数,当从SequenceInputStream
读取数据时,它会依次从每个输入流中读取数据。 - 使用
FileOutputStream
类创建一个输出文件。 - 从
SequenceInputStream
读取数据,并将其写入输出文件。 - 最后,关闭所有打开的资源。
Java I/O流异常处理
Java I/O 流异常处理是编写健壮、稳定的程序的关键。处理 I/O 流异常的重要性在于以下几点:
-
确保程序不会意外崩溃:I/O 操作可能因为多种原因导致异常,例如文件不存在、文件无法读取、磁盘空间不足等。如果没有正确处理这些异常,程序可能会在运行时崩溃,导致数据丢失或用户体验受损。
-
提供有用的错误信息:通过捕获和处理异常,我们可以向用户或开发者提供有关错误原因的详细信息。这有助于快速定位和解决问题,从而改善用户体验和提高开发效率。
-
确保资源得到正确释放:在进行 I/O 操作时,我们通常会打开文件、网络连接等资源。如果在操作过程中出现异常,没有正确处理这些异常可能导致资源无法释放,从而导致资源泄漏。处理异常可以确保在发生错误时,资源能够被正确关闭和释放。
不处理 I/O 流异常可能导致以下后果:
-
程序崩溃:如果异常没有被捕获和处理,程序可能会在运行时崩溃,导致用户体验受损。
-
数据丢失:如果在文件写入操作中出现异常,而没有进行正确的异常处理,可能导致数据写入不完整,从而导致数据丢失。
-
资源泄漏:如果没有正确处理异常,可能导致文件、网络连接等资源未能关闭和释放,从而导致资源泄漏。资源泄漏可能会影响程序性能,甚至导致系统崩溃。
-
错误信息不明确:如果异常没有被捕获,我们无法获得关于错误原因的详细信息,这将使问题难以定位和解决。
因此,正确处理 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 编程中,选择合适的流非常重要。以下是一些关于如何选择正确流的指南:
-
数据类型:首先确定要处理的数据类型。如果要处理的是字节数据(如图像、音频、视频等),则选择字节流(如
FileInputStream
、FileOutputStream
等)。如果要处理的是字符数据(如文本文件),则选择字符流(如FileReader
、FileWriter
等)。 -
输入还是输出:确定是需要读取数据(输入)还是写入数据(输出)。对于输入操作,选择输入流(如
FileInputStream
、FileReader
等),对于输出操作,选择输出流(如FileOutputStream
、FileWriter
等)。 -
缓冲还是非缓冲:考虑是否需要缓冲来提高 I/O 性能。对于大量连续的 I/O 操作,使用缓冲流(如
BufferedInputStream
、BufferedOutputStream
、BufferedReader
、BufferedWriter
等)通常会提高性能。缓冲流在内部使用缓冲区,可以减少实际的磁盘或网络访问次数,从而提高效率。 -
数据处理需求:根据需要处理的数据类型,选择合适的流。例如,如果需要处理基本数据类型(如整数、浮点数等),可以使用
DataInputStream
和DataOutputStream
。如果需要处理对象序列化,可以使用ObjectInputStream
和ObjectOutputStream
。 -
特定场景:针对特定场景选择合适的流。例如,在处理多个文件合并时,可以使用
SequenceInputStream
;在处理线程间通信时,可以使用管道流,如PipedInputStream
和PipedOutputStream
,或PipedReader
和PipedWriter
。
以下是一些常见场景及建议使用的流:
- 处理文本文件:使用
FileReader
、FileWriter
、BufferedReader
和BufferedWriter
- 处理二进制文件:使用
FileInputStream
、FileOutputStream
、BufferedInputStream
和BufferedOutputStream
- 读取或写入基本数据类型:使用
DataInputStream
和DataOutputStream
- 对象序列化和反序列化:使用
ObjectInputStream
和ObjectOutputStream
- 合并多个文件:使用
SequenceInputStream
- 线程间通信:使用
PipedInputStream
和PipedOutputStream
,或PipedReader
和PipedWriter