简介:Java输入输出是编程中不可或缺的部分,涵盖文件操作、网络通信等数据交互。本教程深入探讨Java的输入输出命令,包括流的概念、基本输入输出类、缓冲流、对象序列化与反序列化、文件操作、文件复制、文件读写模式、随机访问文件、文件过滤器流和NIO。通过实例代码,帮助开发者掌握各种I/O任务处理方法。 
1. Java I/O基础概念
Java I/O(输入/输出)是所有编程语言中处理数据传输的核心组件之一。在Java中,I/O操作是通过流(Streams)来实现的,流代表了有序的数据序列,可以是输入,也可以是输出。通过使用流,Java应用程序可以读取存储设备上的数据,或者将数据写入存储设备。
I/O在Java中扮演着至关重要的角色,它不仅支持简单的文件读写操作,还支持更复杂的数据源和数据目的地,比如网络连接和内存数组。理解Java I/O的基础概念,对于编写高效和可维护的Java应用程序是不可或缺的。
接下来的章节将深入探讨Java I/O的不同层面,从简单的数据流分类和使用开始,一直到高级特性的讨论,如序列化和反序列化,这些内容将帮助你更全面地掌握Java I/O的精髓。
2. 输入输出流的分类与使用
2.1 Java流的分类
2.1.1 根据数据类型分类
在Java中,I/O流根据处理的数据类型可以划分为字节流和字符流。
字节流
字节流是处理字节和字节数组的流。字节流主要用在处理二进制数据,例如图片、音频、视频等文件。字节流的两个主要类是 InputStream 和 OutputStream ,它们是所有字节输入流和输出流的父类。
字符流
字符流是处理字符和字符串的流。它适用于文本数据的处理。字符流中的主要类是 Reader 和 Writer 。它们是所有字符输入流和输出流的父类。字符流使用字符集编码将字节转换为字符,使得处理文本数据变得更加方便。
2.1.2 根据处理方式分类
根据流的处理方式,Java I/O流可以分为输入流和输出流。
输入流
输入流是从外部读取数据到程序内部的通道。在Java中,所有继承自 InputStream 或 Reader 的类都被称为输入流。输入流读取的数据可以是字节数据或字符数据。
输出流
输出流是将程序内部的数据写入到外部存储或设备的通道。与输入流类似,所有继承自 OutputStream 或 Writer 的类都是输出流。输出流负责将数据从程序内部发送到外部世界。
2.2 流的常用类和接口
2.2.1 输入流类和接口
InputStream类
InputStream 是所有字节输入流的父类,提供了从不同数据源读取字节的基本方法,如 read() 。 FileInputStream 、 ByteArrayInputStream 和 ObjectInputStream 都是 InputStream 的子类,各自具有特定的用途。
Reader类
Reader 是所有字符输入流的父类,提供了读取字符的基本方法。 FileReader 、 StringReader 和 BufferedReader 都是 Reader 的子类,用于从不同的数据源读取字符数据。
2.2.2 输出流类和接口
OutputStream类
OutputStream 是所有字节输出流的父类,提供了将数据写入输出流的方法。 FileOutputStream 、 ByteArrayOutputStream 和 ObjectOutputStream 都是 OutputStream 的子类,用于将数据写入文件、字节数组或通过网络发送。
Writer类
Writer 是所有字符输出流的父类,提供了向输出流写入字符的方法。 FileWriter 、 StringWriter 和 BufferedWriter 都是 Writer 的子类,常用于写入文本数据。
在下一章节中,我们将深入了解字节流和字符流的具体操作,以及如何在实际应用中进行高效使用。
3. 字节流与字符流的处理
3.1 字节流与字符流的区别
3.1.1 数据处理单位的区别
字节流主要以字节为单位来处理数据,适用于处理二进制文件或非文本数据。字节流可以细分为两个主要的类: InputStream 和 OutputStream ,分别用于读取和写入字节数据。这使得它们非常适合于处理如图片、音频、视频等二进制文件。
字符流则主要以字符为单位处理数据,适用于处理文本文件。它使用了 Reader 和 Writer 类作为抽象基类。字符流内部使用Unicode编码,能够更好地处理文本文件中的字符数据,尤其是在涉及到多种语言和特殊符号时。
3.1.2 适用场景的不同
字节流在处理需要保持字节数据完整性的场合(如二进制文件)时更为适用。例如,当你需要保存一个图片文件或者读写一个PDF文档时,字节流可以保证数据不会因为字符编码问题而被错误解释或修改。
字符流则在处理文本文件时更为方便。由于字符流内部是使用字符编码来处理数据,因此,使用字符流可以正确地处理文件中的字符,特别是当需要处理多字节编码字符时,比如UTF-8编码的中文字符。此外,字符流还能够处理文本文件的行分隔符等文本特性,这在字节流中是不被识别的。
3.2 字节流的操作实践
3.2.1 FileInputStream与FileOutputStream的使用
FileInputStream 和 FileOutputStream 是进行文件读写的最基本的字节流类。 FileInputStream 用于从文件中读取字节,而 FileOutputStream 用于向文件写入字节。
下面展示了如何使用 FileInputStream 来读取文件内容:
import java.io.FileInputStream;
import java.io.IOException;
public class FileInputStreamExample {
public static void main(String[] args) {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream("example.txt");
int data = fileInputStream.read();
while (data != -1) {
// 处理读取的数据,比如打印出来
System.out.print((char) data);
data = fileInputStream.read();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
在这段代码中,我们创建了一个 FileInputStream 实例来读取一个名为"example.txt"的文件。通过循环调用 read() 方法来逐字节读取文件内容,直到返回-1表示没有更多数据可读。在 finally 块中确保了流的关闭操作。
3.2.2 缓冲字节流BufferedInputStream与BufferedOutputStream的使用
为了提高字节流操作的效率,Java提供了缓冲流的实现,即 BufferedInputStream 和 BufferedOutputStream 。这些缓冲流为 InputStream 和 OutputStream 提供了内部缓冲机制,能够减少对磁盘的读写次数,从而提高性能。
下面的代码示例演示了如何结合使用 FileInputStream 和 BufferedInputStream :
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class BufferedStreamExample {
public static void main(String[] args) {
FileInputStream fileIn = null;
BufferedInputStream bufIn = null;
FileOutputStream fileOut = null;
BufferedOutputStream bufOut = null;
try {
fileIn = new FileInputStream("input.txt");
bufIn = new BufferedInputStream(fileIn);
fileOut = new FileOutputStream("output.txt");
bufOut = new BufferedOutputStream(fileOut);
int data;
while ((data = bufIn.read()) != -1) {
bufOut.write(data);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bufOut != null) bufOut.close();
if (fileOut != null) fileOut.close();
if (bufIn != null) bufIn.close();
if (fileIn != null) fileIn.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
在这个示例中,我们使用 BufferedInputStream 来包装 FileInputStream ,并用 BufferedOutputStream 来包装 FileOutputStream 。这样,读写操作就可以利用内部缓冲区,减少磁盘I/O次数。
3.3 字符流的操作实践
3.3.1 FileReader与FileWriter的使用
FileReader 和 FileWriter 类分别用于以字符为单位读取和写入文件。这两个类都是基于 Reader 和 Writer 类实现的,它们提供了字符流的标准操作接口。
下面的代码片段展示了如何使用 FileReader 和 FileWriter 读取和写入文本文件:
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class FileReaderWriterExample {
public static void main(String[] args) {
FileReader fileReader = null;
FileWriter fileWriter = null;
try {
fileReader = new FileReader("input.txt");
fileWriter = new FileWriter("output.txt");
int c;
while ((c = fileReader.read()) != -1) {
fileWriter.write(c);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fileReader != null) {
try {
fileReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileWriter != null) {
try {
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
在这个例子中,我们使用 FileReader 来读取一个文本文件,并使用 FileWriter 将其内容写入到另一个文件。需要注意的是,字符流对字符编码的处理可能会导致在某些情况下数据不一致,特别是当源文件和目标文件使用不同的字符集编码时。
3.3.2 缓冲字符流BufferedReader与BufferedWriter的使用
为了提高字符流操作的性能,可以使用 BufferedReader 和 BufferedWriter 类。这两个类提供了一个缓冲区,以减少实际的读写次数。
下面是一个使用 BufferedReader 读取和 BufferedWriter 写入文本文件的例子:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedReaderWriterExample {
public static void main(String[] args) {
BufferedReader bufferedReader = null;
BufferedWriter bufferedWriter = null;
try {
bufferedReader = new BufferedReader(new FileReader("input.txt"));
bufferedWriter = new BufferedWriter(new FileWriter("output.txt"));
String line;
while ((line = bufferedReader.readLine()) != null) {
bufferedWriter.write(line);
bufferedWriter.newLine(); // 添加换行符
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedWriter != null) {
try {
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
在这个例子中,我们首先创建了一个 BufferedReader 实例,它使用 FileReader 来读取文件。同样地,我们也使用了 BufferedWriter 来包装 FileWriter 以写入文件。通过 readLine() 和 write() 方法,我们逐行读取输入文件,并逐行写入输出文件。
这种方法不仅提高了文件处理的效率,还允许我们使用 BufferedReader 的 readLine() 方法直接按行读取文件,这比逐个字符地读取要高效得多。同样, BufferedWriter 提供的 newLine() 方法可以确保在不同操作系统间跨平台的换行符兼容性。
4. System.in、System.out、System.err的使用
4.1 System.in的基本使用
4.1.1 输入流的来源与用途
System.in 是Java标准输入流的接口,它默认与键盘输入相关联,是通过 InputStream 类实现的。它的主要用途是从标准输入设备(通常是键盘)读取数据。它是 InputStream 类的一个静态成员变量,因此不需要显式地创建 InputStream 的实例即可使用。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class ReadInput {
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String inputLine;
while ((inputLine = reader.readLine()) != null) {
System.out.println("Read from System.in: " + inputLine);
}
reader.close();
}
}
4.1.2 与Scanner类的结合使用
Scanner 类是Java提供的一个便捷的文本扫描工具,它可以解析基本类型和字符串的原始值。使用 Scanner 类与 System.in 结合,可以创建一个方便的接口来读取用户的输入。
import java.util.Scanner;
public class ScanInput {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("Enter a string: ");
String input = scanner.nextLine();
System.out.println("You entered: " + input);
scanner.close();
}
}
4.2 System.out与System.err的特性
4.2.1 标准输出流与标准错误流的区别
System.out 和 System.err 都是输出流,但有以下区别: - System.out 用于输出信息到控制台。 - System.err 用于输出错误信息到控制台。
尽管它们都输出到控制台,但 System.err 的输出是带颜色的(通常是红色),以便用户可以更容易地从常规输出中区分错误信息。这是由JVM在打印时添加的特殊处理,主要用于提高错误信息的可见性。
4.2.2 控制台输出的格式化和重定向
控制台输出可以被格式化,例如设置文本颜色、字体样式等。Java中可以使用ANSI转义序列来实现简单的格式化。此外,还可以通过 System.setOut() 和 System.setErr() 方法重定向输出流到不同的目的地,例如文件或另一个流。
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
public class RedirectOutput {
public static void main(String[] args) throws FileNotFoundException {
PrintStream fileOut = new PrintStream(new File("output.txt"));
PrintStream fileErr = new PrintStream(new File("errorOutput.txt"));
// 保存旧的System.out和System.err
PrintStream originalOut = System.out;
PrintStream originalErr = System.err;
// 重定向System.out到文件
System.setOut(fileOut);
System.out.println("This is being written to file.");
// 重定向System.err到文件
System.setErr(fileErr);
System.err.println("This is an error message being written to a file.");
// 恢复System.out和System.err到原来的状态
System.setOut(originalOut);
System.setErr(originalErr);
System.out.println("Back to console output.");
System.err.println("Back to console error output.");
}
}
该代码段演示了如何将标准输出和标准错误重定向到文件,同时也展示了如何恢复到原始状态。通过这种方式,我们可以控制输出的流向,例如将日志写入文件,或者将信息重定向到网络服务。
5. 缓冲流提高I/O效率的方法
5.1 缓冲流的作用和优势
5.1.1 缓冲机制的原理
缓冲流是构建在基础字节流和字符流之上的高级流,它们通过引入内存缓冲区来提升I/O操作的效率。缓冲机制的工作原理是将数据暂时存储在内存中,当缓冲区填满时,数据会被批量写入或读取到目的地。这个过程减少了底层系统调用的次数,因为每一次系统调用都会伴随着开销,而缓冲流通过减少这些调用,显著提高了读写速度。
5.1.2 缓冲流与非缓冲流的性能对比
在没有缓冲机制的情况下,每次调用I/O操作时,例如 InputStream.read() 或 OutputStream.write() ,都会直接与文件系统或网络进行交互。这种方式效率低下,尤其是对于小数据量的频繁I/O操作。相比之下,缓冲流通过内部维护的缓冲区来减少实际的物理I/O调用次数,从而提高性能。非缓冲流每次调用都会执行一次物理I/O,而缓冲流会等待缓冲区满了或者显式调用flush()方法时才执行物理I/O。
// 示例:字节流与缓冲字节流的读写性能对比
import java.io.*;
public class PerformanceComparison {
private static final int DATA_SIZE = 1024; // 设置数据大小为1KB
public static void main(String[] args) throws IOException {
// 创建文件输入输出流
FileInputStream fis = new FileInputStream("src/example.dat");
FileOutputStream fos = new FileOutputStream("src/example.dat");
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
long startTime = System.currentTimeMillis();
// 读写操作
byte[] buffer = new byte[DATA_SIZE];
int read;
while ((read = fis.read(buffer)) != -1) {
// 写入
fos.write(buffer, 0, read);
}
long endTime = System.currentTimeMillis();
System.out.println("非缓冲流耗时:" + (endTime - startTime) + "毫秒");
// 重置文件指针
fis = new FileInputStream("src/example.dat");
fos = new FileOutputStream("src/example.dat");
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
startTime = System.currentTimeMillis();
// 缓冲流读写操作
while ((read = bis.read(buffer)) != -1) {
// 写入
bos.write(buffer, 0, read);
}
endTime = System.currentTimeMillis();
System.out.println("缓冲流耗时:" + (endTime - startTime) + "毫秒");
}
}
在上述代码中,我们用两种方式对同一文件执行读写操作:一种是非缓冲流,另一种是缓冲流。通常情况下,缓冲流的执行时间会更短,因为它减少了I/O操作的次数。
5.2 缓冲流的使用示例
5.2.1 字节缓冲流BufferedInputStream与BufferedOutputStream的应用
字节缓冲流 BufferedInputStream 和 BufferedOutputStream 用于优化字节数据的读写操作。例如,从文件中读取数据时, BufferedInputStream 会从文件中读取比请求更多的数据,并将它们存储在内部缓冲区中,只有当缓冲区满了或者显式调用 flush() 方法时,才会将缓冲区的内容写入到文件中。
import java.io.*;
public class BufferedStreamsExample {
public static void main(String[] args) throws IOException {
// 文件路径
String sourceFile = "source.dat";
String targetFile = "target.dat";
// 创建源文件和目标文件的字节缓冲输入输出流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(targetFile));
// 读取数据并写入目标文件
int data;
while ((data = bis.read()) != -1) {
bos.write(data);
}
// 刷新流并关闭资源
bos.flush();
bis.close();
bos.close();
}
}
5.2.2 字符缓冲流BufferedReader与BufferedWriter的应用
字符缓冲流 BufferedReader 和 BufferedWriter 提供了对字符数据的缓冲处理。 BufferedReader 可以使用字符数组作为缓冲区,从而提高读取文本文件的效率。它提供了 readLine() 方法,可以按行读取文本数据。 BufferedWriter 也提供了一个字符数组缓冲区,可以存储字符数据,在缓冲区满或调用 flush() 方法时将数据写入底层输出流。
import java.io.*;
public class CharBufferedStreamsExample {
public static void main(String[] args) throws IOException {
// 文件路径
String sourceFile = "source.txt";
String targetFile = "target.txt";
// 创建字符缓冲读取器和写入器
BufferedReader reader = new BufferedReader(new FileReader(sourceFile));
BufferedWriter writer = new BufferedWriter(new FileWriter(targetFile));
String line;
// 按行读取并写入目标文件
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine(); // 写入换行符
}
// 刷新流并关闭资源
writer.flush();
reader.close();
writer.close();
}
}
总结
缓冲流对于提高I/O效率有明显的效果,它们通过减少物理I/O调用的次数来优化数据的读写过程。在实际应用中,缓冲流非常适合处理大量数据和频繁I/O操作的场景。无论是字节流还是字符流,使用缓冲流都能提升程序性能。开发者应当熟练掌握缓冲流的使用方法,并根据实际情况选择合适的缓冲流类型。
6. 对象序列化与反序列化过程
6.1 序列化的概念和用途
序列化是指将对象状态转换为可以存储或传输的形式的过程,即将对象转换为字节流的过程。反序列化则是序列化的逆过程,即将字节流重新转换为对象的过程。
6.1.1 对象状态的保存与恢复
在Java中,序列化主要用于对象的持久化,以便可以将对象状态保存在文件中或通过网络传输到另一个系统或应用程序中。序列化后的对象可以被保存在磁盘上,并且可以随时重新加载到内存中,而不需要重新创建对象。
6.1.2 序列化在数据持久化中的应用
在数据持久化方面,序列化提供了一种简单的方式存储对象的状态,这对于需要长期存储复杂数据结构的应用程序特别有用。例如,你可以将用户信息、会话状态或其他需要持久化存储的数据对象进行序列化,并在需要时重新读取这些数据。
代码示例
下面的代码展示了如何使用 ObjectOutputStream 进行序列化操作,以及如何使用 ObjectInputStream 进行反序列化操作。
import java.io.*;
public class SerializationExample {
public static void main(String[] args) {
// 创建一个对象实例
User user = new User("John Doe", 30, "john.***");
// 序列化对象到文件
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"))) {
oos.writeObject(user);
System.out.println("Serialization completed");
} catch (IOException e) {
e.printStackTrace();
}
// 从文件反序列化对象
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"))) {
User deserializedUser = (User) ois.readObject();
System.out.println("Deserialization completed: " + deserializedUser);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class User implements Serializable {
private String name;
private transient int age; // transient 关键字表示不序列化该字段
private String email;
// 构造函数、getter和setter省略...
}
6.2 序列化与反序列化的实现
6.2.1 ObjectInputStream与ObjectOutputStream的使用
ObjectInputStream 和 ObjectOutputStream 是Java中用于对象序列化和反序列化的主要类。 ObjectOutputStream 用于将对象写入到输出流中,而 ObjectInputStream 则用于从输入流中读取对象。
6.2.2 序列化过程中的注意事项
在序列化对象时,需要注意以下几点: - 实现 Serializable 接口:序列化的类必须实现 java.io.Serializable 接口。 - transient 关键字:使用 transient 关键字声明的字段将不会被序列化。 - static 字段:静态字段也不参与序列化过程。 - 序列化版本兼容性:序列化对象的类需要有一个唯一的序列化版本ID,以保证不同版本的对象能够兼容。
6.3 文件读写模式的区分与应用
6.3.1 文件读模式下的操作
在文件读模式下, ObjectInputStream 用于从文件中读取之前序列化并存储的对象。该模式下,确保文件系统中存在相应的文件,并且该文件包含可识别的序列化数据。
6.3.2 文件写模式下的操作
在文件写模式下, ObjectOutputStream 用于将对象序列化并写入到文件中。在这个过程中,确保文件没有被其他程序使用,并且有足够的权限来创建和写入文件。
6.3.3 文件追加模式下的操作
文件追加模式通常用于文本文件的处理,但在对象序列化的上下文中,它意味着在文件中追加序列化的对象。然而, ObjectOutputStream 不支持追加写入,它总是覆盖现有的文件。如果需要追加模式,则需要自定义处理逻辑,例如在对象序列化前先读取整个文件内容,然后追加新对象后再写回文件。
通过本章节的介绍,您应该已经掌握了对象序列化和反序列化的基础知识,包括它们的概念、用途、实现方式以及在文件读写中的应用。接下来的章节中,我们将继续探讨更高级的I/O特性,如使用Java NIO进行非阻塞I/O操作。
简介:Java输入输出是编程中不可或缺的部分,涵盖文件操作、网络通信等数据交互。本教程深入探讨Java的输入输出命令,包括流的概念、基本输入输出类、缓冲流、对象序列化与反序列化、文件操作、文件复制、文件读写模式、随机访问文件、文件过滤器流和NIO。通过实例代码,帮助开发者掌握各种I/O任务处理方法。

609

被折叠的 条评论
为什么被折叠?



