IO流
IO(Input/Output)流是Java中用于处理输入和输出的机制。Java提供了丰富的IO类,用于从不同数据源读取数据或将数据写入不同目标。IO操作在Java中非常重要,常用于文件操作、网络通信、数据持久化等场景。
IO流的概念
什么是IO流?
IO流是一种数据传输的抽象,它将数据的传输看作是一种流,数据可以像水流一样从一个地方流向另一个地方。在Java中,IO流可以处理各种类型的数据,包括字节和字符。
IO流的分类
Java的IO流可以按照不同的维度进行分类:
按数据流向分类:
- 输入流:从数据源读取数据到程序中
- 输出流:将程序中的数据写入到目标中
按数据类型分类:
- 字节流:处理字节数据,适用于所有类型的文件
- 字符流:处理字符数据,适用于文本文件
按功能分类:
- 节点流:直接与数据源或目标相连的流
- 处理流:在节点流的基础上提供额外功能的流
IO流的体系结构
Java的IO流体系由四个基类组成:
- 字节输入流:
InputStream(抽象类) - 字节输出流:
OutputStream(抽象类) - 字符输入流:
Reader(抽象类) - 字符输出流:
Writer(抽象类)
其他所有的IO流类都是这四个基类的子类或实现。
字节流
字节流用于处理字节数据,可以处理任何类型的文件,如文本文件、图像文件、音频文件等。
InputStream(字节输入流)
InputStream是所有字节输入流的抽象基类,它定义了从数据源读取字节数据的基本方法。
InputStream的主要方法
// 读取一个字节,返回读取的字节值(0-255),如果到达流末尾则返回-1
int read() throws IOException;
// 读取多个字节到字节数组中,返回实际读取的字节数
int read(byte[] b) throws IOException;
// 读取指定长度的字节到字节数组的指定位置
int read(byte[] b, int off, int len) throws IOException;
// 跳过指定数量的字节
long skip(long n) throws IOException;
// 返回可以从输入流中读取(或跳过)的字节数的估计值
int available() throws IOException;
// 关闭输入流并释放相关资源
void close() throws IOException;
// 标记当前位置,之后可以通过reset方法回到该位置
void mark(int readlimit) throws IOException;
// 判断此输入流是否支持mark和reset操作
boolean markSupported();
// 重置到此输入流最近的标记位置
void reset() throws IOException;InputStream的主要子类
- FileInputStream:从文件中读取字节数据
- ByteArrayInputStream:从字节数组中读取数据
- DataInputStream:从其他输入流中读取Java基本类型数据
- BufferedInputStream:带有缓冲区的输入流,提高读取效率
OutputStream(字节输出流)
OutputStream是所有字节输出流的抽象基类,它定义了将字节数据写入目标的基本方法。
OutputStream的主要方法
// 写入一个字节
void write(int b) throws IOException;
// 写入一个字节数组
void write(byte[] b) throws IOException;
// 写入字节数组的指定部分
void write(byte[] b, int off, int len) throws IOException;
// 刷新此输出流,确保所有缓冲的输出字节被写出
void flush() throws IOException;
// 关闭此输出流并释放相关资源
void close() throws IOException;OutputStream的主要子类
- FileOutputStream:将字节数据写入文件
- ByteArrayOutputStream:将数据写入字节数组
- DataOutputStream:将Java基本类型数据写入其他输出流
- BufferedOutputStream:带有缓冲区的输出流,提高写入效率
字节流的使用示例
FileInputStream和FileOutputStream示例
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileStreamExample {
public static void main(String[] args) {
String sourceFile = "source.txt";
String targetFile = "target.txt";
try (FileInputStream fis = new FileInputStream(sourceFile);
FileOutputStream fos = new FileOutputStream(targetFile)) {
int data;
// 每次读取一个字节并写入
while ((data = fis.read()) != -1) {
fos.write(data);
}
System.out.println("文件复制成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}使用缓冲区提高效率
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class BufferedStreamExample {
public static void main(String[] args) {
String sourceFile = "source.txt";
String targetFile = "target.txt";
try (FileInputStream fis = new FileInputStream(sourceFile);
BufferedInputStream bis = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream(targetFile);
BufferedOutputStream bos = new BufferedOutputStream(fos)) {
byte[] buffer = new byte[1024]; // 1KB的缓冲区
int bytesRead;
// 每次读取一个缓冲区的数据
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
// 刷新缓冲区,确保所有数据都被写入
bos.flush();
System.out.println("文件复制成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}字符流
字符流用于处理字符数据,特别适用于文本文件的读写操作。字符流会自动处理字符编码问题。
Reader(字符输入流)
Reader是所有字符输入流的抽象基类,它定义了从数据源读取字符数据的基本方法。
Reader的主要方法
// 读取一个字符,返回读取的字符值(0-65535),如果到达流末尾则返回-1
int read() throws IOException;
// 读取多个字符到字符数组中,返回实际读取的字符数
int read(char[] cbuf) throws IOException;
// 读取指定长度的字符到字符数组的指定位置
int read(char[] cbuf, int off, int len) throws IOException;
// 跳过指定数量的字符
long skip(long n) throws IOException;
// 关闭字符输入流并释放相关资源
void close() throws IOException;
// 标记当前位置,之后可以通过reset方法回到该位置
void mark(int readAheadLimit) throws IOException;
// 判断此输入流是否支持mark和reset操作
boolean markSupported();
// 重置到此输入流最近的标记位置
void reset() throws IOException;
// 告知此流是否已准备好被读取
boolean ready() throws IOException;Reader的主要子类
- FileReader:从文件中读取字符数据
- CharArrayReader:从字符数组中读取数据
- StringReader:从字符串中读取数据
- BufferedReader:带有缓冲区的字符输入流,提供了readLine()方法读取整行文本
- InputStreamReader:将字节流转换为字符流,可以指定字符编码
Writer(字符输出流)
Writer是所有字符输出流的抽象基类,它定义了将字符数据写入目标的基本方法。
Writer的主要方法
// 写入一个字符
void write(int c) throws IOException;
// 写入一个字符数组
void write(char[] cbuf) throws IOException;
// 写入字符数组的指定部分
void write(char[] cbuf, int off, int len) throws IOException;
// 写入字符串
void write(String str) throws IOException;
// 写入字符串的指定部分
void write(String str, int off, int len) throws IOException;
// 将指定的字符序列写入此writer
Writer append(CharSequence csq) throws IOException;
// 将指定字符序列的子序列写入此writer
Writer append(CharSequence csq, int start, int end) throws IOException;
// 将指定字符追加到此writer
Writer append(char c) throws IOException;
// 刷新此输出流,确保所有缓冲的输出字符被写出
void flush() throws IOException;
// 关闭此输出流并释放相关资源
void close() throws IOException;Writer的主要子类
- FileWriter:将字符数据写入文件
- CharArrayWriter:将数据写入字符数组
- StringWriter:将数据写入字符串缓冲区
- BufferedWriter:带有缓冲区的字符输出流,提供了newLine()方法写入换行符
- OutputStreamWriter:将字符流转换为字节流,可以指定字符编码
字符流的使用示例
FileReader和FileWriter示例
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class FileReaderWriterExample {
public static void main(String[] args) {
String sourceFile = "text.txt";
String targetFile = "copy.txt";
try (FileReader fr = new FileReader(sourceFile);
FileWriter fw = new FileWriter(targetFile)) {
int data;
// 每次读取一个字符并写入
while ((data = fr.read()) != -1) {
fw.write(data);
}
System.out.println("文本文件复制成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}使用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) {
String sourceFile = "text.txt";
String targetFile = "copy.txt";
try (BufferedReader br = new BufferedReader(new FileReader(sourceFile));
BufferedWriter bw = new BufferedWriter(new FileWriter(targetFile))) {
String line;
// 按行读取和写入
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine(); // 写入换行符
}
System.out.println("文本文件复制成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}使用InputStreamReader和OutputStreamWriter指定字符编码
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class EncodingExample {
public static void main(String[] args) {
String sourceFile = "utf8.txt";
String targetFile = "gbk.txt";
try (// 使用UTF-8编码读取文件
BufferedReader br = new BufferedReader(new InputStreamReader(
new FileInputStream(sourceFile), "UTF-8"));
// 使用GBK编码写入文件
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(targetFile), "GBK"))) {
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
}
System.out.println("编码转换成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}转换流
转换流是连接字节流和字符流的桥梁,可以将字节流转换为字符流,或者将字符流转换为字节流。
InputStreamReader
InputStreamReader是字节流到字符流的桥梁,它读取字节并将其解码为字符。它使用指定的字符编码或默认编码将字节转换为字符。
// 创建一个使用默认字符编码的InputStreamReader
InputStreamReader isr = new InputStreamReader(new FileInputStream("file.txt"));
// 创建一个使用指定字符编码的InputStreamReader
InputStreamReader isrUtf8 = new InputStreamReader(
new FileInputStream("file.txt"), "UTF-8");OutputStreamWriter
OutputStreamWriter是字符流到字节流的桥梁,它将字符编码为字节。它使用指定的字符编码或默认编码将字符转换为字节。
// 创建一个使用默认字符编码的OutputStreamWriter
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("file.txt"));
// 创建一个使用指定字符编码的OutputStreamWriter
OutputStreamWriter oswUtf8 = new OutputStreamWriter(
new FileOutputStream("file.txt"), "UTF-8");缓冲流
缓冲流是一种处理流,它通过缓冲区提高IO操作的效率。缓冲流可以减少实际读写硬盘的次数,从而提高程序的运行效率。
BufferedInputStream
BufferedInputStream是带有缓冲区的字节输入流,它可以提高读取字节的效率。
// 创建一个使用默认缓冲区大小的BufferedInputStream
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("file.txt"));
// 创建一个使用指定缓冲区大小的BufferedInputStream
BufferedInputStream bisCustom = new BufferedInputStream(
new FileInputStream("file.txt"), 8192); // 8KB缓冲区BufferedOutputStream
BufferedOutputStream是带有缓冲区的字节输出流,它可以提高写入字节的效率。
// 创建一个使用默认缓冲区大小的BufferedOutputStream
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("file.txt"));
// 创建一个使用指定缓冲区大小的BufferedOutputStream
BufferedOutputStream bosCustom = new BufferedOutputStream(
new FileOutputStream("file.txt"), 8192); // 8KB缓冲区BufferedReader
BufferedReader是带有缓冲区的字符输入流,它提供了readLine()方法来读取整行文本。
// 创建一个使用默认缓冲区大小的BufferedReader
BufferedReader br = new BufferedReader(new FileReader("file.txt"));
// 创建一个使用指定缓冲区大小的BufferedReader
BufferedReader brCustom = new BufferedReader(
new FileReader("file.txt"), 8192); // 8KB缓冲区BufferedWriter
BufferedWriter是带有缓冲区的字符输出流,它提供了newLine()方法来写入平台特定的换行符。
// 创建一个使用默认缓冲区大小的BufferedWriter
BufferedWriter bw = new BufferedWriter(new FileWriter("file.txt"));
// 创建一个使用指定缓冲区大小的BufferedWriter
BufferedWriter bwCustom = new BufferedWriter(
new FileWriter("file.txt"), 8192); // 8KB缓冲区数据流
数据流用于读写Java基本数据类型。数据流可以将Java的基本数据类型(如int、float、double等)写入到输出流中,或者从输入流中读取Java的基本数据类型。
DataInputStream
DataInputStream可以从其他输入流中读取Java基本类型数据。
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class DataInputStreamExample {
public static void main(String[] args) {
try (DataInputStream dis = new DataInputStream(new FileInputStream("data.bin"))) {
// 按照写入的顺序读取数据
int intValue = dis.readInt();
double doubleValue = dis.readDouble();
boolean booleanValue = dis.readBoolean();
String stringValue = dis.readUTF();
System.out.println("int: " + intValue);
System.out.println("double: " + doubleValue);
System.out.println("boolean: " + booleanValue);
System.out.println("string: " + stringValue);
} catch (IOException e) {
e.printStackTrace();
}
}
}DataOutputStream
DataOutputStream可以将Java基本类型数据写入到其他输出流中。
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class DataOutputStreamExample {
public static void main(String[] args) {
try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.bin"))) {
// 写入不同类型的数据
dos.writeInt(42);
dos.writeDouble(3.14159);
dos.writeBoolean(true);
dos.writeUTF("Hello, Data Streams!");
System.out.println("数据写入成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}对象流
对象流用于读写Java对象。使用对象流可以将Java对象序列化后写入到输出流中,或者从输入流中读取序列化的数据并反序列化为Java对象。
ObjectInputStream
ObjectInputStream用于从输入流中读取序列化的对象。
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class ObjectInputStreamExample {
public static void main(String[] args) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.bin"))) {
// 读取对象
Person person = (Person) ois.readObject();
System.out.println("读取到的对象: " + person);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}ObjectOutputStream
ObjectOutputStream用于将对象序列化后写入到输出流中。
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
// 要序列化的类必须实现Serializable接口
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 + "}";
}
}
public class ObjectOutputStreamExample {
public static void main(String[] args) {
Person person = new Person("Alice", 30);
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.bin"))) {
// 写入对象
oos.writeObject(person);
System.out.println("对象写入成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}序列化的注意事项
- 要序列化的类必须实现
Serializable接口 - 可以使用
serialVersionUID来控制版本兼容性 - 被
transient关键字修饰的字段不会被序列化 - 静态字段不会被序列化
- 如果一个类的父类实现了
Serializable接口,那么该类默认也可以序列化
NIO
Java NIO(New IO)是Java 1.4引入的一组新的IO API,它提供了更高效的IO操作方式。NIO与传统IO的主要区别在于:
- NIO使用基于缓冲区(Buffer)的方式进行IO操作
- NIO提供了非阻塞IO操作
- NIO引入了通道(Channel)的概念
- NIO提供了选择器(Selector)用于多路复用
NIO的核心组件
- Buffer:数据容器,用于存储数据
- Channel:数据传输的通道
- Selector:用于监听多个通道的事件
- Charset:用于字符编码和解码
- Path:表示文件系统中的路径
- Files:提供文件操作的工具方法
NIO 2
Java 7引入了NIO 2(也称JSR 203),它扩展了NIO,提供了更强大的文件操作功能:
- Path:表示文件系统路径
- Files:提供了丰富的文件操作方法
- FileVisitor:用于遍历文件树
- WatchService:用于监听文件系统变化
NIO 2的使用示例
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.List;
public class NIO2Example {
public static void main(String[] args) {
try {
// 创建文件
Path newFile = Paths.get("newFile.txt");
Files.createFile(newFile);
System.out.println("文件创建成功: " + newFile);
// 写入文件
String content = "Hello, NIO 2!\nThis is a test file.";
Files.write(newFile, content.getBytes());
System.out.println("文件写入成功");
// 读取文件
List<String> lines = Files.readAllLines(newFile);
System.out.println("文件内容:");
for (String line : lines) {
System.out.println(line);
}
// 复制文件
Path copyFile = Paths.get("copyFile.txt");
Files.copy(newFile, copyFile, StandardCopyOption.REPLACE_EXISTING);
System.out.println("文件复制成功: " + copyFile);
// 移动文件
Path moveFile = Paths.get("movedFile.txt");
Files.move(copyFile, moveFile, StandardCopyOption.REPLACE_EXISTING);
System.out.println("文件移动成功: " + moveFile);
// 删除文件
Files.delete(newFile);
Files.delete(moveFile);
System.out.println("文件删除成功");
} catch (IOException e) {
e.printStackTrace();
}
}
}IO流的最佳实践
使用try-with-resources语句
- 自动关闭资源,避免资源泄露
- 代码更加简洁、清晰
选择合适的IO流
- 处理文本文件时使用字符流
- 处理二进制文件时使用字节流
- 需要高效读写时使用缓冲流
注意字符编码问题
- 明确指定字符编码,避免平台相关的编码问题
- 使用UTF-8作为默认编码是一个好习惯
使用缓冲区提高效率
- 使用缓冲流可以减少实际IO操作的次数
- 合理设置缓冲区大小
及时关闭资源
- 即使使用try-with-resources,也应该确保资源被正确关闭
- 输出流在关闭前应该调用flush()方法确保数据被完全写入
异常处理
- 捕获并适当处理IO异常
- 提供有意义的错误信息
常见IO操作示例
文件复制
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
public class FileCopyExample {
public static void main(String[] args) {
String source = "source.txt";
String target = "target.txt";
try {
// 使用NIO 2的Files.copy方法复制文件
Files.copy(Paths.get(source), Paths.get(target), StandardCopyOption.REPLACE_EXISTING);
System.out.println("文件复制成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}文件读取
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
public class FileReadExample {
public static void main(String[] args) {
String filePath = "example.txt";
try {
// 读取所有行
List<String> lines = Files.readAllLines(Paths.get(filePath));
// 打印文件内容
System.out.println("文件内容:");
for (String line : lines) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}文件夹遍历
import java.io.IOException;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.EnumSet;
public class DirectoryWalkExample {
public static void main(String[] args) {
String directory = "./src";
try {
Files.walkFileTree(Paths.get(directory), EnumSet.of(FileVisitOption.FOLLOW_LINKS),
Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println("文件: " + file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
System.out.println("目录: " + dir);
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}小结
- Java IO流是用于处理输入输出的机制,分为字节流和字符流两大类
- 字节流以字节为单位进行操作,适用于所有类型的文件
- 字符流以字符为单位进行操作,适用于文本文件,自动处理字符编码
- 缓冲流可以提高IO操作的效率
- 转换流用于连接字节流和字符流
- 数据流用于读写Java基本数据类型
- 对象流用于读写Java对象,涉及序列化和反序列化
- NIO提供了更高效的IO操作方式,引入了缓冲区、通道、选择器等概念
- NIO 2进一步扩展了NIO,提供了更强大的文件操作功能
- 使用try-with-resources语句可以自动关闭资源,避免资源泄露
掌握Java IO是Java编程的重要基础,合理地使用各种IO流可以使程序更加高效、可靠。