Java特别篇之IO流合辑
1️⃣ 概念
在Java中,IO(输入输出)操作是进行数据交互的重要方式之一。本文将详细介绍Java中的IO流的各个方面,包括输入流和输出流、字节流和字符流、文件读写、缓冲、序列化以及常见的IO异常处理等知识点。
![](https://i-blog.csdnimg.cn/blog_migrate/ed182273d5b7eb38eb90818effbeac18.png)
1.1 输入流和输出流
按照数据流向,Java中的IO流可分为两种类型:输入流(InputStream)和输出流(OutputStream)。输入流用于从外部获取数据,而输出流则用于将数据发送到外部。
- 输入流(InputStream):它用于读取数据,比如读取文件或网络数据。常用的Java类包括
FileInputStream
、BufferedInputStream
和DataInputStream
等。 - 输出流(OutputStream):它用于写入数据,例如将数据写入文件或网络。常用的Java类包括
FileOutputStream
、BufferedOutputStream
和DataOutputStream
等。
1.2 字节流和字符流
Java IO流还可以根据所处理的数据操作单元类型来区分为字节流和字符流。
- 字节流:字节流逐字节地处理数据。其中,字节输入流(
ByteInputStream
)和字节输出流(ByteOutputStream
)主要用于处理二进制数据。 - 字符流:字符流逐字符地处理数据。Java中的字符流是基于字节流的,通过使用字符编码(如UTF-8)来处理Unicode字符。
2️⃣ 优势和缺点
IO流(输入/输出流)能够将数据从一个地方(例如文件、网络、内存等)传输到另一个地方,是与外部资源进行交互的重要方式。下面是IO流的优缺点:
优点:
- 灵活性:IO流提供了各种类型的流,如字节流、字符流、缓冲流等,以适应不同类型数据的读写需求;
- 通用性:IO流具有与设备和底层实现无关的特点,这使得程序可以通过相同的代码在不同的平台上执行;
- 管道性:可以将多个流串联起来,形成一个数据处理管道,方便数据的传输和处理;
- 可扩展性:通过继承和实现接口,可以创建自定义的流类型,满足特定的业务需求;
- 易于使用:IO流提供了简单直观的操作方法,易于学习和使用。
缺点:
- 性能开销:IO流涉及到对外部资源的读写,因此在大量数据处理时,会产生一定的性能开销,尤其是对于频繁的磁盘或网络IO操作;
- 阻塞:某些IO操作可能会导致程序的阻塞,即程序在等待IO操作完成时无法继续执行其他任务;
- 易出错:IO流的使用需要开发人员手动管理资源的打开和关闭,处理好异常情况。若没有正确地释放资源,可能会导致资源泄漏或程序崩溃;
- 局限性:虽然IO流提供了对文件、网络等各种资源的读写能力,但它并不适用于所有类型的数据源,如数据库等,这些数据源通常需要特定的API来进行交互。
综上所述,尽管IO流在数据读写方面具有灵活性和可扩展性等优点,但它也存在一些不足之处,如性能开销、阻塞和易出错等问题。因此,在实际应用中,需要根据具体需求选择合适的IO流,并采取相应的优化策略,以获得更好的性能和稳定性。
3️⃣ 使用
3.1 File 类
File
类是Java标准库中用于表示文件和目录路径的抽象类。它不仅可以表示实际存在的文件或目录,还可以表示不存在的路径。File
类提供了一些方法来操作文件和目录,例如创建、重命名、删除等。
以下是 File
类的一些常见方法:
exists()
:检查文件或目录是否存在;createNewFile()
:创建一个新的空文件;mkdir()
:创建一个目录;renameTo(File file)
:将文件或目录重命名为指定的名称;delete()
:删除文件或目录。
下面是一个简单的示例,展示了如何定义一个File
类对象以及调用其方法:
import java.io.File;
import java.io.IOException;
public class UseDemo {
public static void main(String[] args) {
File file = new File("D://file.txt");
// 判断文件是否存在
if (file.exists()) {
System.out.println("File exists!");
} else {
System.out.println("File does not exist.");
}
// 创建新文件
try {
boolean flag = file.createNewFile();
if (flag) {
System.out.println("File created successfully!");
} else {
System.out.println("File creation failed.");
}
} catch (IOException e) {
e.printStackTrace();
}
// 删除文件
if (file.delete()) {
System.out.println("File deleted successfully!");
} else {
System.out.println("File deletion failed.");
}
// 创建新目录
File dir = new File("D://xiaoshan");
if (dir.mkdir()) {
System.out.println("dir created successfully!");
} else {
System.out.println("dir creation failed.");
}
// 重命名目录
if (dir.renameTo(new File("D://new"))) {
System.out.println("dir rename successfully!");
} else {
System.out.println("dir rename failed.");
}
}
}
运行结果:
File does not exist.
File created successfully!
File deleted successfully!
dir creation failed.
dir rename successfully!
3.2 RandomAccessFile 类
RandomAccessFile
类是Java中用于随机访问文件的类。相比于 FileInputStream
和 FileOutputStream
类,它提供了对文件进行读写操作的能力,并且可以随机访问文件中的任意位置。
它提供了诸如 read()
, write()
, seek()
等方法来实现这些功能。此外,RandomAccessFile
类支持以只读模式、读写模式或追加模式打开文件。
以下是 RandomAccessFile
类的一些常见方法:
read()
:从文件中读取一个字节。write(int b)
:将一个字节写入文件。seek(long pos)
:定位到文件的指定位置。length()
:返回文件的长度。close()
:关闭文件流。
下面是一个简单的示例,展示了如何定义一个RandomAccessFile
类对象以及调用其方法:
import java.io.IOException;
import java.io.RandomAccessFile;
public class UseDemo {
public static void main(String[] args) {
String fileName = "D://file.txt";
try (RandomAccessFile file = new RandomAccessFile(fileName, "rw")) {
// 写入数据
file.writeBytes("Hello, World!");
// 定位到文件开头
file.seek(0);
// 读取数据
System.out.println("File content: " + file.readLine());
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
File content: Hello, World!
3.3 字节流
3.3.1 文件字节流
FileInputStream
和FileOutputStream
是Java IO库中用于处理文件字节流的两个类。它们可以分别用于从文件读取数据和向文件写入数据。
🍊 FileInputStream(文件输入字节流)
FileInputStream
类用于从文件中读取数据的字节流。使用该类,你可以打开一个已经存在的文件,并可以按字节顺序读取文件中的数据。以下是一些FileInputStream
类的主要方法和用法:
FileInputStream(String name)
:创建一个与指定文件名相对应的FileInputStream
对象;int read()
:从输入流中读取一个字节的数据,并将其作为整数返回。如果到达文件末尾,返回-1;int read(byte[] b)
:从输入流中读取最多b.length
字节的数据,并放入字节数组b
中,返回实际读取的字节数。如果到达文件末尾,返回-1;void close()
:关闭流并释放系统资源。
🍒 FileOutputStream(文件输出字节流)
FileOutputStream
类用于向文件中写入数据的字节流。使用该类,你可以创建一个新文件或覆盖已经存在的文件,并可以按字节顺序写入数据。以下是一些FileOutputStream
类的主要方法和用法:
FileOutputStream(String name)
:创建一个与指定文件名相对应的FileOutputStream
对象;void write(int b)
:将给定的字节写入输出流;void write(byte[] b)
:将字节数组b
中的所有字节写入输出流;void close()
:关闭流并释放系统资源。
下面是一个使用文件字节流 FileInputStream
和 FileOutputStream
的Java案例程序:
package com.xiaoshan.demo3;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
public class FileByteStreamExample {
public static void main(String[] args) {
File sourceFile = new File( "D://source.txt");
File targetFile = new File( "D://target.txt");;
// 若文件还不存在,则先创建文件,并写入数据
if (!sourceFile.exists()){
try {
sourceFile.createNewFile();
Files.write(sourceFile.toPath(),"Hello World!".getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
}
}
try {
// 创建输入流对象
FileInputStream fis = new FileInputStream(sourceFile);
// 创建输出流对象
FileOutputStream fos = new FileOutputStream(targetFile);
int byteData;
// 从输入流读取数据,并写入到输出流
while ((byteData = fis.read()) != -1) {
fos.write(byteData);
}
System.out.println("文件复制成功!");
// 关闭流
fis.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
以上代码演示了如何使用 FileInputStream
和 FileOutputStream
在两个文件之间进行复制。首先,我们创建一个 FileInputStream
对象用于从源文件读取数据,然后创建一个 FileOutputStream
对象用于将数据写入目标文件。
通过循环读取源文件的数据(使用 read()
方法每次读取一个字节),并使用 write()
方法将数据写入目标文件。直到 read()
返回 -1 表示已经读取完整个文件。
最后,关闭输入流和输出流以释放资源,完成文件复制操作。如果出现任何异常,会将异常信息打印出来。在成功执行时,输出"文件复制成功!"的提示信息。
运行结果:
文件复制成功!
去D盘下查看对应的源文件和目标文件,如图:
3.3.2 缓冲字节流
BufferedInputStream
和BufferedOutputStream
是Java IO库中的两个类,它们提供了缓冲字节流的功能,用于在处理IO操作时提高性能和效率。
🍅 BufferedInputStream(缓冲输入字节流)
BufferedInputStream
继承自FilterInputStream
类,它包装在其他输入流之上,并添加了缓冲区的功能。当从BufferedInputStream
中读取字节数据时,它会一次读取一块数据到内部缓冲区,并逐个地返回给调用者,直到缓冲区被耗尽。这样可以减少实际的IO读取次数,从而提高读取性能。
以下是BufferedInputStream
的一些常用方法:
int read()
: 从输入流中读取下一个字节的数据;int read(byte[] b, int offset, int length)
: 将输入流中的数据读入字节数组b
,从偏移量offset
开始,并最多读取length
个字节的数据;void reset()
: 重置输入流的位置;void close()
: 关闭输入流。
🌽 BufferedOutputStream(缓冲输出字节流)
BufferedOutputStream
继承自FilterOutputStream
类,同样也是包装在其他输出流之上,并添加了缓冲区的功能。当我们向BufferedOutputStream
写入字节数据时,它会先将这些数据缓存在内部的缓冲区中,当缓冲区被填充满或者我们调用其 flush()
方法时,缓冲区中的数据会被一次性地写入底层输出流。
以下是BufferedOutputStream
的一些常用方法:
void write(int b)
: 向输出流中写入一个字节的数据;void write(byte[] b, int offset, int length)
: 将字节数组b
中从偏移量offset
开始的length
个字节的数据写入输出流;void flush()
: 刷新输出流,将缓冲区中的数据强制写入底层输出流;void close()
: 关闭输出流。
使用这两个类可以提高IO操作的效率,尤其是对于大文件或网络传输等场景。通过减少实际的IO读写次数以及使用缓冲区,可以显著降低时间开销并提高整体性能。
以下是一个Java使用案例程序,演示如何使用缓冲字节流BufferedInputStream
和BufferedOutputStream
来进行文件的读取和写入操作:
import java.io.*;
public class BufferedStreamExample {
public static void main(String[] args) {
String sourceFile = "D://source.txt";
String destFile = "D://destination.txt";
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourceFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile))) {
byte[] buffer = new byte[1024];
int bytesRead;
// 从源文件读取数据,并将其写入目标文件
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
System.out.println("文件复制成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
这个程序会将source.txt
文件的内容复制到destination.txt
文件中。首先,使用BufferedInputStream
来提高从源文件读取数据的效率。然后,使用BufferedOutputStream
来提高向目标文件写入数据的效率。
在try
语句块中,我创建了一个BufferedInputStream
对象和一个BufferedOutputStream
对象,并指定要打开的源文件和目标文件。然后,我使用一个循环来不断从源文件读取数据,并将读取到的数据写入目标文件中。读取的数据存储在一个缓冲区buffer
中,每次读取的最大字节数为1024。通过bis.read(buffer)
方法返回值来确定实际读取到的字节数。
最后,在finally
块中关闭了输入流和输出流,确保资源的正确释放。
运行结果:
文件复制成功!
去D盘下查看对应的源文件和目标文件,如图:
3.3.3 基本数据类型字节流
DataInputStream
和DataOutputStream
是Java中的两个类,用于处理基本数据类型的字节流。它们提供了一种读写数据的方式,可以方便地将数据转换成字节并写入流中,或者从流中读取字节并将其转换回相应的数据类型。
🍇 DataInputStream
DataInputStream
类允许你从输入流中读取不同类型的数据。它提供了各种方法来读取不同类型的基本数据类型,例如int
、long
、float
、double
和boolean
等。此外,它还提供了读取字符串和字节数组的方法。
以下是几个常用的DataInputStream
方法:
readInt()
:读取一个int
值;readLong()
:读取一个long
值;readFloat()
:读取一个float
值;readDouble()
:读取一个double
值;readBoolean()
:读取一个boolean
值;readUTF()
:读取一个以UTF-8编码的字符串;read(byte[] b)
:读取字节数组。
🍉 DataOutputStream
DataOutputStream
类用于将基本数据类型的数据写入输出流。它提供了各种方法来写入不同类型的基本数据类型,例如int
、long
、float
、double
和boolean
等。此外,它还提供了写入字符串和字节数组的方法。
以下是几个常用的DataOutputStream
方法:
writeInt(int v)
:写入一个int
值;writeLong(long v)
:写入一个long
值;writeFloat(float v)
:写入一个float
值;writeDouble(double v)
:写入一个double
值;writeBoolean(boolean v)
:写入一个boolean
值;writeUTF(String str)
:写入一个以UTF-8编码的字符串;write(byte[] b)
:写入字节数组。
使用DataInputStream
和DataOutputStream
可以方便地进行数据的读写操作,并且保证数据类型的正确性。这两个类通常与其他输入输出流(例如FileInputStream
和FileOutputStream
)结合使用,用于处理二进制文件或网络通信中的数据传输。
下面是一个使用基本数据类型字节流 DataInputStream
和 DataOutputStream
的Java使用案例程序:
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class DataStreamExample {
public static void main(String[] args) {
String fileName = "datastream_example.txt";
// 写入数据
try (DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(fileName))) {
int intValue = 42;
double doubleValue = 3.14159;
String stringValue = "Hello, World!";
outputStream.writeInt(intValue);
outputStream.writeDouble(doubleValue);
outputStream.writeUTF(stringValue);
System.out.println("数据写入完成。");
} catch (IOException e) {
e.printStackTrace();
}
// 读取数据
try (DataInputStream inputStream = new DataInputStream(new FileInputStream(fileName))) {
int intValue = inputStream.readInt();
double doubleValue = inputStream.readDouble();
String stringValue = inputStream.readUTF();
System.out.println("从文件中读取的数据:");
System.out.println("整数值:" + intValue);
System.out.println("浮点数值:" + doubleValue);
System.out.println("字符串值:" + stringValue);
} catch (IOException e) {
e.printStackTrace();
}
}
}
这个程序会将一个整数值、一个浮点数值和一个字符串值写入到文件 datastream_example.txt
中,然后再从文件中读取这些值,并打印输出到控制台。
需要注意的是,在使用完字节流后要及时关闭资源,这里我使用了 try-with-resources
块来自动关闭流。另外,应格外注意文件的路径和文件名是否正确,以免出现文件找不到等错误。
运行结果:
数据写入完成。
从文件中读取的数据:
整数值:42
浮点数值:3.14159
字符串值:Hello, World!
3.3.4 打印流
PrintStream (打印流)是Java中用于输出字符数据的类。它是OutputStream
类的子类,提供了方便的方法来输出不同类型的数据。
PrintStream
类可以直接连接到各种输出流(如文件输出流、网络输出流、标准输出流等),并提供了丰富的打印方法,使得输出变得简单而灵活。
下面是一些PrintStream
类常用的方法:
print()
:将参数打印到输出流中,不添加换行符;println()
:将参数打印到输出流中,并在结尾处添加换行符;printf()
:使用类似于C语言中的格式化字符串的方式将参数打印到输出流中;append()
:将指定的字符序列追加到输出流中;flush()
:刷新输出流,将缓冲区中的数据立即写入到目的地;close()
:关闭输出流,释放相关资源。
PrintStream类的优点在于其简洁和便利性。它提供了各种重载的print和println方法,可以轻松地打印不同类型的数据,例如整数、浮点数、布尔值等。此外,它还提供了格式化输出的功能,使得输出结果更加灵活和美观。
下面是一个示例,展示了如何使用PrintStream类向控制台输出信息:
import java.io.PrintStream;
public class PrintStreamExample {
public static void main(String[] args) {
PrintStream printStream = System.out;
printStream.print("Hello "); // 打印字符串
printStream.println("World!"); // 打印带换行的字符串
printStream.printf("The value of PI is %.2f", Math.PI); // 格式化输出
printStream.close();
}
}
在这个示例中,通过System.out
获得了一个PrintStream对象,并使用它打印了一些信息。通过调用不同的方法,可以灵活地控制输出结果。
总之,PrintStream类提供了一种方便的机制来输出数据到不同类型的流。它使得打印文本信息变得简单和有效,是Java中常用的输出工具之一。
运行结果:
Hello World!
The value of PI is 3.14
3.3.5 对象序列化流
ObjectInputStream 和 ObjectOutputStream( 对象序列化流)是Java中用于将对象持久化存储或在网络间传输的类。它们提供了对对象的序列化和反序列化功能,允许开发人员将对象转换为字节流,然后再将字节流转换回原始对象。
-
ObjectOutputStream
:是一个输出流,用于将对象序列化为字节流。使用writeObject()
方法可以将对象写入到输出流中。当需要将一个对象进行持久化存储或者在网络上进行传输时,可以使用ObjectOutputStream
将其序列化为字节流。 -
ObjectInputStream
:是一个输入流,用于从字节流中反序列化对象。使用readObject()
方法可以从输入流中读取字节流并将其转换为原始对象。当需要从持久化存储或网络接收的字节流中恢复对象时,可以使用ObjectInputStream
进行反序列化。
对象序列化流的工作原理是将对象以二进制形式序列化,并将序列化数据写入流中,以便在需要时重新构建该对象。这使得对象能够跨越应用程序、操作系统和网络传输,而无需重新编写复杂的数据映射逻辑。
以下是使用对象序列化流的一些注意事项:
- 类的序列化:要将对象进行序列化,相应的类必须实现
Serializable
接口。这是一个标记接口,没有定义任何方法,仅用于表示该类可以被序列化; - 字段的序列化:要将字段序列化为字节流,它们必须是可序列化的。可序列化的字段是指原始类型、String、数组和实现了
Serializable
接口的对象; transient
修饰符:使用transient
修饰字段时,这些字段将不会被序列化。它们在反序列化过程中将保持默认值;- 版本控制:当序列化类发生更改时,可能需要进行版本控制以确保兼容性。可以通过在类中定义一个名为
serialVersionUID
的静态常量来实现版本控制。
🔍 什么是Java序列化?何时需要序列化
Java序列化是将Java对象转换为字节序列的过程,反序列化是将字节序列转换成Java对象的过程。
Java序列化以便可以将其存储在文件中、在网络上传输或者在进程之间传递,可以将对象的状态保存到硬盘或者内存中,同时又能够方便地将对象恢复到其原始状态。
需要序列化的情况通常包括:
- 将对象存储到文件中,以便在以后的某个时间点检索它;
- 将对象通过网络传输,例如在客户端和服务器之间传输数据;
- 在进程之间传递对象。
需要注意的是,不是所有的对象都需要或能够序列化。例如下面这些情况:
- 包含非序列化字段的类对象,比如 Thread 类、InputStream 类等;
- 包含对不可序列化实例化对象的引用的类对象,因为这个引用指向的对象不可序列化,那么这个类对象也不可序列化;
- 包含循环引用的类对象,即一个对象引用了另一个对象,而后者又引用了前者,这种情况会导致无限递归,无法序列化;
- 包含被 static修饰的静态变量的类对象,因为静态变量不属于任何一个实例化对象,无法通过实例化对象进行序列化;
- 包含匿名内部类、局部内部类和 lambda 表达式的类对象,因为这些类没有名字,无法被序列化;
- 包含被 transient 修饰的变量的类对象,因为 transient 可以用来修饰不需要序列化的变量,这些变量的值在序列化和反序列化时会被忽略。
总而言之,对象序列化流ObjectInputStream
和ObjectOutputStream
提供了方便的机制,使得开发人员能够轻松地将对象转换为字节流并恢复原始对象。这对于数据持久化、跨应用程序通信和网络传输都非常有用。
下面是一个使用对象序列化流 ObjectInputStream
和 ObjectOutputStream
的 Java 示例程序:
import java.io.*;
public class ObjectStreamExample{
public static void main(String[] args) {
// 创建一个对象
Person person = new Person("John", 30);
// 将对象序列化到文件中
String filename = "person.ser";
serializeObject(person, filename);
System.out.println("Person对象已序列化到文件:" + filename);
// 从文件中反序列化对象
Person deserializedPerson = deserializeObject(filename);
System.out.println("从文件" + filename + "中反序列化出的Person对象:");
System.out.println(deserializedPerson);
}
// 序列化对象并保存到文件
private static void serializeObject(Object object, String filename) {
try (FileOutputStream fileOutputStream = new FileOutputStream(filename);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) {
objectOutputStream.writeObject(object);
} catch (IOException e) {
e.printStackTrace();
}
}
// 从文件中反序列化对象
private static Person deserializeObject(String filename) {
Person person = null;
try (FileInputStream fileInputStream = new FileInputStream(filename);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) {
person = (Person) objectInputStream.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return person;
}
}
// 要序列化的对象类需要实现 Serializable 接口
class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
上面这个示例程序创建了一个 Person
类,该类实现了 Serializable
接口,因此它可以被序列化。在 main()
方法中,首先创建了一个 Person
对象并将其序列化到文件 person.ser
中。然后通过使用相同的文件名,从文件中反序列化出一个新的 Person
对象,并打印输出该对象的内容。
运行结果:
Person对象已序列化到文件:person.ser
从文件person.ser中反序列化出的Person对象:Person [name=John, age=30]
3.3.6 字节数组流
🍓 ByteArrayInputStream(字节数组输入流)
ByteArrayInputStream
是 Java.io
包中的一个类,它允许我们从内存中的字节数组中读取数据。它以字节的形式从相应的字节数组中读取,并将其提供给程序。
要使用 ByteArrayInputStream
,首先需要创建一个字节数组作为输入源,然后可以使用该类的方法来读取字节数组中的数据。这个类提供了一系列的读取方法,如读取单个字节、读取部分字节数组或从指定位置开始读取数据。
下面是它的常用方法:
ByteArrayInputStream(byte[] buf)
:根据给定的字节数组创建一个新的ByteArrayInputStream
实例;int read()
:从输入流中读取下一个字节的数据,并返回读取的字节值。如果已经达到流的末尾,则返回-1;int read(byte[] b, int off, int len)
:从输入流中读取多个字节的数据,并将其存储在指定的字节数组b
中,从偏移量off
开始,最多读取len
个字节;long skip(long n)
:跳过并丢弃输入流中的n
个字节的数据,返回实际跳过的字节数;int available()
:返回可从输入流中读取而不受阻塞的字节数;void reset()
:将流重置为起始位置,即将读取位置设置为初始位置;void close()
:关闭该输入流并释放与之关联的资源。
🍑 ByteArrayOutputStream(字节数组输出流)
ByteArrayOutputStream
是 Java.io
包中的一个类,它允许我们将数据写入内存中的字节数组中。它以字节的形式将数据提供给程序,并将其写入到相应的字节数组中。
要使用 ByteArrayOutputStream
,首先需要创建一个空的字节数组作为输出目标,然后可以使用该类的方法来向字节数组中写入数据。这个类提供了一系列的写入方法,如写入单个字节、将部分字节数组写入或在指定位置开始写入数据。
下面是它的常用方法:
void write(int b)
:将指定的字节写入输出流中;void write(byte[] b, int off, int len)
:将指定字节数组中从偏移量off
开始的len
个字节写入输出流中;void writeTo(OutputStream out)
:将此输出流的全部内容写入指定的输出流out
;byte[] toByteArray()
:创建一个新分配的字节数组,并将当前输出流的内容复制到该数组中;int size()
:返回当前输出流中的字节数;void reset()
:将流重置为空,即丢弃所有存储的数据;void close()
:关闭该输出流并释放与之关联的资源。
以下是一个使用字节数组流 ByteArrayInputStream
和 ByteArrayOutputStream
的 Java 示例程序:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class ByteArrayStreamExample {
public static void main(String[] args) {
String message = "Hello, world!";
// 使用 ByteArrayOutputStream 将字符串写入字节数组输出流
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
outputStream.write(message.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
// 从字节数组输出流中获取字节数组
byte[] byteArray = outputStream.toByteArray();
// 使用 ByteArrayInputStream 读取字节数组并将其转换为字符串
ByteArrayInputStream inputStream = new ByteArrayInputStream(byteArray);
StringBuilder stringBuilder = new StringBuilder();
int byteRead;
while ((byteRead = inputStream.read()) != -1) {
char character = (char) byteRead;
stringBuilder.append(character);
}
String result = stringBuilder.toString();
System.out.println("Original Message: " + message);
System.out.println("Result: " + result);
}
}
上述程序实现了将字符串写入 ByteArrayOutputStream
中。这里使用 write()
方法将字符串的字节表示写入到字节数组输出流中。
然后使用 toByteArray()
方法从字节数组输出流中获取字节数组,利用获取的字节数组创建 ByteArrayInputStream
对象,并使用 read()
方法读取字节数组中的每个字节,并将其转换成字符,最后将字符拼接成一个字符串。
在此示例中,使用字节数组流来在内存中读写数据而不涉及磁盘或网络。这对于处理较小的数据量非常方便。
运行结果:
Original Message: Hello, world!
Result: Hello, world!
3.4 字符流
3.4.1 缓冲字符流
🍐 BufferedReader(缓冲输入字符流)
BufferedReader
是 Java 中的一个字符流类,它继承自 Reader
类。它的主要作用是提供输入缓冲区,以便更高效地从字符输入流中读取文本数据。
通过使用 BufferedReader
,可以减少与底层资源(如磁盘和网络)的交互次数,从而提高读取文本数据的性能。
下面是一些 BufferedReader
的常用方法:
readLine()
:从输入流中读取一行文本,并返回一个包含该行内容的字符串。如果已到达文件的末尾,则返回值为null
。read()
:从输入流中读取一个字符,并返回其对应的整数值。如果已到达文件的末尾,则返回值为-1
。close()
:关闭输入流并释放相关资源。
🍍 BufferedWriter(缓冲输出字符流)
BufferedWriter
是 Java 中的一个字符流类,它继承自 Writer
类。它的主要作用是提供输出缓冲区,以便更高效地将文本数据写入字符输出流中。
通过使用 BufferedWriter
,可以减少与底层资源(如磁盘和网络)的交互次数,提高写入文本数据的性能。
以下是一些 BufferedWriter
的常用方法:
write(String str)
:将给定字符串写入输出流。newLine()
:写入一个平台相关的换行符。flush()
:刷新输出流,将缓冲区的内容立即写入目标中。
通过使用 BufferedReader
和 BufferedWriter
,可以在读取或写入大量文本数据时提高性能,并且具有方便的操作接口。
好的,下面是一个使用缓冲字符流 BufferedReader
和 BufferedWriter
的Java案例程序:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedIOExample {
public static void main(String[] args) {
String inputFile = "input.txt";
String outputFile = "output.txt";
try {
// 使用 BufferedReader 读取文件
BufferedReader reader = new BufferedReader(new FileReader(inputFile));
// 使用 BufferedWriter 写入文件
BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile));
String line;
while ((line = reader.readLine()) != null) {
// 对读取的每一行进行处理(这里只是简单输出)
System.out.println("读取行: " + line);
// 将处理后的内容写入到文件中
writer.write(line);
writer.newLine(); // 换行
}
// 关闭输入和输出流
reader.close();
writer.close();
System.out.println("写入完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
上面的程序会从名为 input.txt
的输入文件中读取内容,并将每一行逐行写入到名为 output.txt
的输出文件中。读者可以根据需要修改文件名称和处理逻辑。
同时应确保在运行程序之前先创建 input.txt
文件,并在其中添加一些文本内容。运行程序后,它将读取文件的文本内容,并在控制台打印出来,同时写入到 output.txt
文件中。
注意在使用缓冲字符流时,需要在程序结束后调用 close()
方法关闭对应的流对象,以释放资源和确保数据被正确写入。
运行结果:
读取行: Hello, World! xiaoshan !
写入完成!
查看文件:
3.4.2 文件字符流
FileReader
和FileWriter
是Java中用于处理文件字符流的类。它们可以用来读取和写入文本文件。
🍔 FileReader(文件读取字符流)
FileReader
类用于从文件中读取字符数据。它继承自InputStreamReader
类,提供了一种方便的方式来读取文件中的字符并将其转化为Java字符串。
以下是FileReader
的一些重要方法:
int read()
: 读取下一个字符,并返回读取的字符的Unicode值。如果已经到达流的末尾,则返回-1。int read(char[] cbuf)
: 将字符读入到给定的字符数组中,并返回实际读取的字符数。如果已经到达流的末尾,则返回-1。void close()
: 关闭该流。
🍝 FileWriter(文件输出字符流)
FileWriter
类用于向文件中写入字符数据。它继承自OutputStreamWriter
类,提供了一种方便的方式将Java字符转换为字节,并将其写入文件。
以下是FileWriter
的一些重要方法:
void write(int c)
: 将给定的字符写入文件。void write(char[] cbuf)
: 将字符数组的内容写入文件。void write(String str)
: 将字符串写入文件。void flush()
: 刷新流,将所有缓冲的输出字符写入文件。void close()
: 关闭该流。
注意,在使用FileReader
和FileWriter
时,为了正确释放资源,应该在 try
块中使用 try-with-resources
语句或者在finally
块中调用close()
方法来关闭流。这有助于避免资源泄漏和确保正确的流处理。
下面是一个使用字符流 FileReader
和 FileWriter
的Java案例程序:
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class FileIOExample{
public static void main(String[] args) {
try {
// 创建一个输入文件读取器
FileReader reader = new FileReader("input.txt");
// 创建一个输出文件写入器
FileWriter writer = new FileWriter("output.txt");
int character;
// 读取输入文件中的字符,并将其写入输出文件
while ((character = reader.read()) != -1) {
writer.write(character);
}
System.out.println("文件复制完成!");
// 关闭读取器和写入器
reader.close();
writer.close();
} catch (IOException e) {
System.out.println("文件复制失败:" + e.getMessage());
}
}
}
上述代码通过创建一个 FileReader
对象来读取输入文件,创建一个 FileWriter
对象来写入输出文件。然后使用 read()
方法逐个字符地从输入文件中读取内容,并使用 write()
方法将字符写入输出文件中。最后,关闭读取器和写入器来确保资源被正确释放。
注意确保在运行该程序之前存在名为 input.txt
的输入文件。在程序成功执行后,会在项目根目录下创建一个名为 output.txt
的输出文件,并将输入文件的内容复制到此输出文件中。如果发生任何异常,将会打印相应的错误信息。
运行结果:
文件复制完成!
查看文件:
3.4.3 转换流
InputStreamReader
和OutputStreamWriter
是Java中的转换流类,它们提供了字符流和字节流之间的桥梁。
🍣 InputStreamReader
InputStreamReader
将字节流转换为字符流。它实现了Reader
接口,并使用指定的字符编码将输入的字节流解码为字符。
InputStream inputStream = new FileInputStream("file.txt");
Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
在上面的例子中,将创建一个FileInputStream
对象来读取名为“file.txt”的文件的字节流。然后,使用InputStreamReader
将字节流转换为字符流,同时指定了UTF-8作为字符编码。
🍙 OutputStreamWriter
OutputStreamWriter
则将字符流转换为字节流。它实现了Writer
接口,并使用指定的字符编码将输出的字符流编码为字节流。
OutputStream outputStream = new FileOutputStream("file.txt");
Writer writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
在上面的例子中,将创建一个FileOutputStream
对象来写入名为“file.txt”的文件的字节流。然后,使用OutputStreamWriter
将字符流转换为字节流,同时指定了UTF-8作为字符编码。
通过使用这些转换流,我们可以在处理数据时轻松地在字符流和字节流之间进行转换。这对于处理不同编码的文本文件非常有用,以确保正确地读取和写入字符流。
下面是一个使用转换流 InputStreamReader
和 OutputStreamWriter
的Java案例程序:
import java.io.*;
public class ConvertIOExample{
public static void main(String[] args) {
// 定义文件路径
String inputFile = "input.txt";
String outputFile = "output.txt";
try {
// 创建输入流和输出流对象
FileInputStream fis = new FileInputStream(inputFile);
FileOutputStream fos = new FileOutputStream(outputFile);
// 创建转换流对象
InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
// 读取并写入数据
int data;
while ((data = isr.read()) != -1) {
osw.write(data);
}
// 关闭流
isr.close();
osw.close();
System.out.println("文件已成功转换并写入!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
这个示例程序将从 input.txt
文件中的文本读取,并将其转换为UTF-8编码后写入到 output.txt
文件中。如果你想要处理其他类型文件或者自定义编码,只需要相应地修改输入和输出的文件名以及编码即可。
请注意,在读取和写入文件时,我使用了 FileInputStream
和 FileOutputStream
来创建字节流。然后,我们利用 InputStreamReader
和 OutputStreamWriter
将字节流转换为字符流,并指定所需的编码(例如UTF-8)。最后,通过迭代读取 InputStreamReader
中的数据,并使用 OutputStreamWriter
的 write()
方法写入数据。最后,关闭流并打印成功消息。
记住要处理可能抛出的 IOException
异常。
运行结果:
文件已成功转换并写入!
查看文件:
3.4.4 格式化输出流
PrintWriter 是Java中的一个输出流类,它继承自Writer
类。PrintWriter
提供了方便的方法来将数据以文本格式写入到输出目标,例如文件、控制台或网络连接。
使用PrintWriter
时,可以直接指定输出目标,也可以首先创建一个Writer
对象,然后再通过构造函数创建PrintWriter
对象,将该Writer
对象作为参数传入。
下面是一些PrintWriter
常用的方法:
print()
:将数据以文本形式输出,不会自动换行。println()
:将数据以文本形式输出,并在结尾添加换行符。write()
:将字符数据写入输出流。printf()
:使用指定的格式字符串进行格式化输出。
除了上述方法外,PrintWriter
还具有其他一些方法用于刷新缓冲区、关闭流等操作。同时,还提供了自动刷新功能,即当使用println()
方法输出数据时,如果设置了自动刷新,就会立即刷新输出缓冲区,确保数据能够及时显示出来。
以下是使用PrintWriter
将文本内容输出到文件的示例代码:
import java.io.*;
public class PrintWriterExample {
public static void main(String[] args) {
try {
PrintWriter writer = new PrintWriter(new FileWriter("output.txt"));
writer.println("Hello, PrintWriter!");
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
上述代码会将字符串"Hello, PrintWriter!"写入到名为"output.txt"的文件中。
查看文件:
3.4.5 字符数组流
CharArrayWriter
和CharArrayReader
是Java中的字符数组流类,用于读取和写入字符数组。
🍞 CharArrayWriter(字符数组输出流)
CharArrayWriter
是一个字符输出流,它继承自Writer
类。它允许将字符写入内存中的字符数组。基本上,它是一个在内存中操作字符数据的容器。
以下是CharArrayWriter
的一些重要方法和特点:
write(char[] cbuf, int off, int len)
:将字符数组的一部分写入到字符数组输出流中。toCharArray()
:将字符数组输出流中的内容转换为字符数组。toString()
:将字符数组输出流中的内容转换为字符串。flush()
:刷新输出流。close()
:关闭输出流。
🍦 CharArrayReader(字符数组输入流)
CharArrayReader
是一个字符输入流,它继承自Reader
类。它从内存中的字符数组读取字符。
以下是CharArrayReader
的一些重要方法和特点:
read(char[] cbuf, int off, int len)
:从字符数组输入流中读取字符并将其存储在给定的字符数组中。skip(long n)
:跳过n个字符。close()
:关闭输入流。
这些字符数组流类通常与其他字符流类(如FileReader
和FileWriter
)一起使用,以实现更高级的操作,例如文件复制和转换数据类型等。它们对于处理字符数据在内存中的场景非常有用,而不需要使用磁盘或网络进行读写操作。
下面是一个使用字符数组流 CharArrayWriter
和 CharArrayReader
的 Java 案例程序:
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.IOException;
public class CharArrayIOExample {
public static void main(String[] args) {
String data = "Hello, World!";
try {
// 创建一个字符数组输出流
CharArrayWriter charArrayWriter = new CharArrayWriter();
// 将字符串写入字符数组输出流
charArrayWriter.write(data);
// 将字符数组输出流中的内容转换为字符数组
char[] charArray = charArrayWriter.toCharArray();
// 创建一个字符数组输入流,并使用字符数组作为数据源
CharArrayReader charArrayReader = new CharArrayReader(charArray);
// 读取字符数组并打印到控制台
int charRead;
while ((charRead = charArrayReader.read()) != -1) {
System.out.print((char) charRead);
}
// 关闭字符数组输入流
charArrayReader.close();
// 关闭字符数组输出流
charArrayWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
程序演示了如何使用字符数组流进行字符数据的读取和写入。首先,我使用 CharArrayWriter
将字符串数据写入字符数组输出流。然后,将字符数组输出流中的内容转换为字符数组,并通过创建一个 CharArrayReader
使用字符数组作为数据源来读取字符数组中的数据。最后,将读取的字符打印到控制台。
运行结果:
Hello, World!
4️⃣ 应用场景
字节流和字符流是在I/O操作中常用的两种数据流,它们有各自的应用场景。
字节流的应用场景:
- 文件传输和处理:字节流最适合于处理二进制文件,例如图片、视频和音频文件等。通过使用字节流,可以准确地读写和传输文件的内容,无论文件类型如何都能保持完整性。
- 网络通信:在网络通信中,字节流主要用于发送和接收原始字节数据。这包括处理套接字连接、实现服务器和客户端之间的数据交换等。
- 序列化和反序列化:当将对象转换为字节表示时,字节流用于执行序列化操作。这在分布式系统中非常常见,可以将对象进行打包并通过网络传输或者进行持久化存储。
- 图像处理和压缩:通过字节流,可以直接读取和修改图片文件的像素数据,执行图像处理操作,也能进行压缩和解压缩等相关操作。
字符流的应用场景:
- 文本文件处理:字符流适用于处理文本文件,如文档、配置文件等。字符流能够按照字符的方式读取和写入数据,使得处理文本数据更加方便和高效。
- 字符编码转换:字符流提供了字符集编码和解码功能,可以轻松地将字节数据根据指定的字符编码转换为实际的字符。这在多语种文本处理中非常有用。
- 控制台输入和输出:字符流主要用于处理控制台输入和输出操作。通过使用字符流,可以方便地从用户输入获取字符数据,并以可读的格式将数据输出到控制台上。
虽然字节流和字符流用于不同的场景,但它们通常可以相互转换。在实际应用中,选择使用字节流还是字符流取决于你需要处理的数据类型和操作需求。
🌾 总结
Java的IO流(Input/Output streams)用于处理输入和输出操作。它们提供了一种与外部设备(如文件、网络连接等)进行交互的方法。Java的IO流按照数据的流向分为输入流(InputStream)和输出流(OutputStream)。输入流用于从外部设备读取数据到程序中,输出流用于将程序中的数据写入外部设备。
Java的IO流拥有通用性、支持多种类型的数据、灵活性和可扩展性等优点,因而可被用于文件操作、网络编程、数据库操作以及 性能分析与日志记录等场景。但同时在实际运用中也需要考虑到性能和复杂性问题。
📑 本文源码下载地址
Java的IO流讲解代码: File 类、RandomAccessFile 类、字节流(文件字节流、缓冲字节流、基本数据类型