一、输入流与输出流的定义及作用
定义
-
输入流(Input Stream):在 Java 世界里,输入流是一种机制,它负责从外部世界(像硬盘文件系统、网络连接、内存数组等数据源)读取数据到程序内部。
-
输出流(Output Stream):输出流则是将程序里的数据输出到外部世界,外部世界可以是文件、网络连接等目标位置。
作用
输入流和输出流是 Java 实现数据输入输出操作的核心,它们搭建起了程序与外部环境之间的数据交互桥梁,使得程序能够读取外部数据进行处理,也能将处理结果输出到外部。
比如说把控制台的数据读入到程序当中。或者说把程序当中的数据输出到控制台
import java.io.IOException;
public class ReadByteByByte {
public static void main(String[] args) {
try {
System.out.println("请输入一些内容:");
int input;
while ((input = System.in.read()) != -1) {
System.out.print((char) input);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
二、输入流和输出流最基础的类及方法使用
基础类
Java 中,InputStream
和 OutputStream
分别是输入流和输出流的最基础抽象类。它们以字节作为读取和写入数据的基本单元。
常用方法及使用示例
InputStream
类
-
int read()
:该方法从输入流中读取一个字节的数据,返回值是该字节对应的整数(范围为 0 - 255)。当到达流的末尾时,返回 -1。
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class InputStreamExample {
public static void main(String[] args) {
try (InputStream is = new FileInputStream("input.txt")) {
int byteData;
while ((byteData = is.read()) != -1) {
System.out.print((char) byteData);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
-
int read(byte[] b)
:尝试将最多b.length
个字节的数据读入到字节数组b
中,返回实际读取的字节数。若到达流末尾,返回 -1。
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class InputStreamReadArrayExample {
public static void main(String[] args) {
try (InputStream is = new FileInputStream("input.txt")) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
for (int i = 0; i < bytesRead; i++) {
System.out.print((char) buffer[i]);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
OutputStream
类
-
void write(int b)
:将指定的字节写入输出流。这里的参数b
虽然是int
类型,但实际上只使用低 8 位。import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; public class OutputStreamExample { public static void main(String[] args) { try (OutputStream os = new FileOutputStream("output.txt")) { String data = "Hello, World!"; for (int i = 0; i < data.length(); i++) { os.write(data.charAt(i)); } } catch (IOException e) { e.printStackTrace(); } } }
-
void write(byte[] b)
:将字节数组b
中的所有字节写入输出流。
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class OutputStreamWriteArrayExample {
public static void main(String[] args) {
try (OutputStream os = new FileOutputStream("output.txt")) {
String data = "Hello, Java!";
byte[] buffer = data.getBytes();
os.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
三、能否以其他单位进行读取
虽然 InputStream
和 OutputStream
以字节为基础单位进行读取和写入,但 Java 也提供了以其他单位进行操作的方式:
字符
可以使用字符流来以字符为单位进行读取和写入。字符流基于字节流构建,会自动处理字符的编码和解码。例如 Reader
和 Writer
是字符流的抽象基类,FileReader
和 FileWriter
是具体实现类。
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CharacterStreamExample {
public static void main(String[] args) {
try (FileReader fr = new FileReader("input.txt");
FileWriter fw = new FileWriter("output.txt")) {
int charData;
while ((charData = fr.read()) != -1) {
fw.write(charData);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
数字和对象
对于数字和对象,可以使用数据流和对象流。
-
数据流:
DataInputStream
和DataOutputStream
可以方便地读写基本数据类型(如int
、double
等)。
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) {
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.dat"));
DataInputStream dis = new DataInputStream(new FileInputStream("data.dat"))) {
dos.writeInt(123);
int num = dis.readInt();
System.out.println(num);
} catch (IOException e) {
e.printStackTrace();
}
}
}
-
对象流:
ObjectInputStream
和ObjectOutputStream
用于读写 Java 对象。对象需要实现Serializable
接口才能进行序列化和反序列化。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
public class ObjectStreamExample {
public static void main(String[] args) {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {
Person person = new Person("John", 30);
oos.writeObject(person);
Person readPerson = (Person) ois.readObject();
System.out.println("Name: " + readPerson.getName() + ", Age: " + readPerson.getAge());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
四、字符流、对象流等与字节流的关系
字符流、对象流、数据流等都与字节流密切相关,它们是在字节流的基础上构建起来的。
-
字符流:在字节流的基础上添加了字符编码协议。在读取数据时,根据编码协议读取指定数量的字节,然后进行解码得到对应的字符;在写入数据时,将字符编码为字节后通过字节流输出。
-
对象流:输入时先获取对象的序列化结果(字节流),然后使用字节流读取,再进行反序列化得到对象;输出时将对象进行序列化得到字节流,然后使用字节流将字节流传输出去。
-
数据流:同样是在字节流的基础上,提供了读写基本数据类型的功能,会将基本数据类型转换为字节进行读写。
综上所述,字节流是 Java 输入输出流体系的基础,其他类型的流都是为了满足不同的数据处理需求而在字节流基础上进行的扩展。
五、Buffered 输入流实现更快读取效率的原理
我看FIleInputStream每次读数据都要调用本地方法read0,如果碰到大文件频繁调用,可能导致效率会比较低,有什么更好的方法吗?
在 Java 中,像 BufferedInputStream
和 BufferedReader
这类带有缓冲区(Buffer)的输入流能够提升读取效率,其核心原理在于减少系统调用的次数。以下是具体的解释:
-
系统调用开销
系统调用是指程序向操作系统内核请求服务的过程,例如从磁盘、网络或控制台读取数据。每次进行系统调用都需要进行上下文切换,也就是从用户态切换到内核态,这个过程会消耗大量的时间和资源。如果每次只读取少量的数据,就会频繁地进行系统调用,从而导致效率低下。
-
缓冲区的作用
带有缓冲区的输入流在内部维护了一个缓冲区(通常是一个字节数组或字符数组)。当程序第一次调用读取方法时,它会进行一次系统调用,从数据源(如文件、网络等)中读取一大块数据到缓冲区中。之后,程序再进行读取操作时,会先从缓冲区中获取数据,而不是直接进行系统调用。只有当缓冲区中的数据被读完后,才会再次进行系统调用,读取新的数据到缓冲区。
例如,BufferedInputStream
会一次性从底层输入流(如 FileInputStream
)中读取多个字节到其内部的字节数组缓冲区,后续的读取操作就可以直接从这个缓冲区获取数据,减少了与底层输入源的交互次数,从而提高了读取效率。
Buffered 输入流的使用方法
1. BufferedInputStream
的使用(处理字节流)
BufferedInputStream
用于处理字节流,以下是一个简单的示例,展示如何使用 BufferedInputStream
读取文件:
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class BufferedInputStreamExample {
public static void main(String[] args) {
try (InputStream fis = new FileInputStream("example.txt");
BufferedInputStream bis = new BufferedInputStream(fis)) {
int byteData;
while ((byteData = bis.read()) != -1) {
System.out.print((char) byteData);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
在上述代码中,首先创建了一个 FileInputStream
用于从文件中读取字节数据,然后将其作为参数传递给 BufferedInputStream
的构造函数,创建一个带有缓冲区的输入流。之后,通过 BufferedInputStream
的 read()
方法读取数据,它会先从缓冲区中获取数据,当缓冲区为空时再进行系统调用读取新的数据。
2. BufferedReader
的使用(处理字符流)
BufferedReader
用于处理字符流,以下是一个使用 BufferedReader
读取文件的示例:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class BufferedReaderExample {
public static void main(String[] args) {
try (BufferedReader br = new BufferedReader(new FileReader("example.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
在这个示例中,创建了一个 FileReader
用于从文件中读取字符数据,然后将其传递给 BufferedReader
的构造函数。BufferedReader
会将字符数据读取到其内部的缓冲区,通过 readLine()
方法可以方便地按行读取数据,提高了读取文本文件的效率。
通过使用带有缓冲区的输入流,可以显著减少系统调用的次数,从而提高数据读取的效率。