IO流
IO流
1.File类
Java中的File
类是java.io
包中的一个重要类,它代表了文件和目录路径名的抽象表示形式。通过File
类,我们可以对文件和目录进行各种操作,如创建、删除、重命名、查询属性等,但需要注意的是,File
类本身并不提供文件内容的读写功能,这需要通过输入/输出流(如FileInputStream
、FileOutputStream
等)来实现。
1.1.File类的构造方法
File
类提供了三种形式的构造方法,用于创建File
对象:
File(String pathname)
:根据给定的路径名字符串创建File
对象。如果pathname
是实际存在的路径,则该File
对象可能表示目录;如果pathname
是文件名,则该File
对象可能表示文件。File(String parent, String child)
:根据父目录路径名和子文件或目录名创建File
对象。File(File parent, String child)
:根据父File
对象和子文件或目录名创建File
对象。
1.2.File类的常用方法
File
类提供了许多用于文件和目录操作的方法,以下是一些常用的方法:
-
文件/目录的创建、删除和重命名
createNewFile()
:当且仅当具有该名称的文件尚不存在时,创建一个新的空文件。delete()
:删除此抽象路径名表示的文件或目录。deleteOnExit()
:在虚拟机终止时,请求删除此抽象路径名表示的文件或目录。renameTo(File dest)
:将此抽象路径名重命名为给定的抽象路径名。
-
检查文件/目录的存在性
exists()
:测试此抽象路径名表示的文件或目录是否存在。
-
获取文件/目录的属性
length()
:返回由此抽象路径名表示的文件的长度,以字节为单位。lastModified()
:返回此抽象路径名表示的文件最后一次被修改的时间。isDirectory()
:测试此抽象路径名表示的是否为目录。isFile()
:测试此抽象路径名表示的是否为文件。isHidden()
:测试此抽象路径名指定的文件是否是隐藏文件。
-
列出目录内容
list()
:返回一个字符串数组,这些字符串指定了此抽象路径名表示的目录中的文件和目录。listFiles()
:返回一个File
数组,这些文件和目录是此抽象路径名表示的目录中的文件和目录。
-
路径操作
getAbsolutePath()
:返回此抽象路径名的绝对路径名字符串。getPath()
:将此抽象路径名转换为一个路径名字符串。getName()
:返回由此抽象路径名表示的文件或目录的名称。getParent()
:返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回null
。getParentFile()
:返回此抽象路径名父目录的File
对象;如果此路径名没有指定父目录,则返回null
。
1.3.注意事项
- 在处理文件和目录时,路径有两种不同的表示方式:绝对路径和相对路径。绝对路径是从文件系统的根目录开始的完整路径,而相对路径是相对于当前工作目录的路径。
- Windows系统的路径分隔符是反斜杠
\
,但在Java程序中,反斜杠是转义字符,因此需要使用两条反斜杠\\
或直接使用斜杠/
来表示Windows路径。Java程序支持将斜线/
当成平台无关的路径分隔符。 - 为了确保跨平台兼容性,建议使用
File.separator
来获取当前系统的路径分隔符。
1.4.示例
如何使用File
类来检查文件是否存在、获取文件属性以及列出目录内容:
import java.io.File;
public class FileExample {
public static void main(String[] args) {
File file = new File("C:\\example\\test.txt");
// 检查文件是否存在
if (file.exists()) {
System.out.println("文件存在");
// 获取文件属性
System.out.println("文件长度: " + file.length() + " 字节");
System.out.println("最后修改时间: " + file.lastModified());
// 判断是否为文件或目录
if (file.isFile()) {
System.out.println("这是一个文件");
} else if (file.isDirectory()) {
System.out.println("这是一个目录");
}
//
2.流的基本概念
在Java中,流(Stream)是一个核心概念,它是对输入输出设备的一种抽象理解,用于处理数据的传输。
2.1.流的概念
- 定义:流是一组有顺序的、有起点和终点的字节集合,是对数据传输的一种抽象。在Java中,数据的输入输出操作都是以流的方式进行的。
- 本质:流代表了数据的无结构化传递,可以看作是字节(byte)的集合,这些字节按照一定的顺序在数据源和目的地之间传输。
- 方向性:流具有方向性,根据数据的流向可以分为输入流(Input Stream)和输出流(Output Stream)。输入流用于从数据源读取数据到程序中,而输出流用于将程序中的数据写入到数据源。
2.2.流的作用
- 数据传输:流是数据在程序和设备(如文件、网络等)之间传输的通道。
- 数据封装:流封装了数据传输的细节,使得程序可以更加方便地进行数据的读写操作。
- 数据处理:通过不同的流类,可以对数据进行各种处理,如过滤、转换、缓冲等。
2.3.流的类层次
Java中的流类都封装在java.io
包中,主要包括四个顶级抽象类:InputStream
、OutputStream
、Reader
和Writer
。这些类及其子类提供了丰富的流操作功能。
- InputStream:字节输入流的根类,定义了所有字节输入流类所共同具有的方法。
- OutputStream:字节输出流的根类,定义了所有字节输出流类所共同具有的方法。
- Reader:字符输入流的根类,定义了所有字符输入流类所共同具有的方法。
- Writer:字符输出流的根类,定义了所有字符输出流类所共同具有的方法。
2.4.流的操作
对流的操作主要包括打开流、读写数据、关闭流等步骤。在Java中,使用流进行数据操作时,需要遵循“先打开,后关闭”的原则,以确保资源的正确释放和避免资源泄露。
2.5.示例
以下是一个简单的示例,展示了如何使用FileInputStream
和FileOutputStream
类来读取和写入文件:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class StreamExample {
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
// 打开输入流,读取文件
fis = new FileInputStream("input.txt");
// 打开输出流,写入文件
fos = new FileOutputStream("output.txt");
// 假设这里进行数据的读写操作...
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流,释放资源
try {
if (fis != null) {
fis.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3.流的分类
Java中的流可以按照不同的标准进行分类:
-
按照数据流向分类:
- 输入流(Input Stream):用于读取数据,如从文件、网络等数据源读取数据。
- 输出流(Output Stream):用于写出数据,如将数据写入文件、网络等目的地。
-
按照处理数据类型的单位分类:
- 字节流(Byte Stream):以字节为单位处理数据,适用于所有类型的数据(包括文本和二进制数据)。常见的字节流类有
InputStream
和OutputStream
。 - 字符流(Character Stream):以字符为单位处理数据,适用于文本数据。字符流基于Unicode编码,常见的字符流类有
Reader
和Writer
。
- 字节流(Byte Stream):以字节为单位处理数据,适用于所有类型的数据(包括文本和二进制数据)。常见的字节流类有
3.1.InputStream|字节输入流(接口)
在Java中,InputStream
是一个抽象类,它位于java.io
包中,是所有字节输入流的基类。InputStream
类定义了一个名为read
的抽象方法,这个方法用于从输入流中读取数据。由于它是一个抽象类,因此你不能直接实例化它,而是需要通过它的子类来创建输入流对象。
InputStream
类及其子类提供了多种读取数据的方式,包括但不限于以下几种:
-
按字节读取:通过
read()
方法读取输入流中的下一个字节,并返回该字节作为int
类型(范围从0到255)的值。如果到达文件末尾,则返回-1。 -
按字节数组读取:通过
read(byte[] b, int off, int len)
方法读取输入流中的最多len
个字节的数据,并将它们存储到字节数组b
中,从off
位置开始。返回实际读取的字节数,如果到达文件末尾,则返回-1。 -
按字节数组读取(简化版):通过
read(byte[] b)
方法读取输入流中的数据,直到填满指定的字节数组b
或到达文件末尾。返回读取的字节数。
InputStream
类及其子类被广泛用于从各种数据源读取数据,如文件、内存缓冲区、网络连接等。以下是一些常见的InputStream
子类:
- FileInputStream:用于从文件中读取数据。
- ByteArrayInputStream:用于从字节数组中读取数据。
- BufferedInputStream:为另一个输入流添加缓冲功能,以提高读取效率。
- ObjectInputStream:用于读取经过序列化的对象。
- FilterInputStream:是一个装饰者类,用于装饰其他输入流,为它们提供额外的功能或属性。
使用InputStream
时,通常需要遵循以下步骤:
-
创建InputStream对象:通过适当的子类创建InputStream对象,并传入必要的参数(如文件路径、字节数组等)。
-
读取数据:通过调用
read()
方法或其重载版本来读取数据。 -
关闭流:完成读取后,调用
close()
方法来关闭流,释放与之关联的资源。注意,在关闭流之前,应确保已经读取了所有需要的数据,因为一旦流被关闭,就无法再从中读取数据了。
如何使用FileInputStream
从文件中读取数据:
import java.io.FileInputStream;
import java.io.IOException;
public class InputStreamExample {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("example.txt"); // 创建FileInputStream对象
int data;
while ((data = fis.read()) != -1) { // 循环读取文件中的每个字节
System.out.print((char) data); // 将字节转换为字符并打印
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close(); // 关闭流
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3.2.OutputStream|字节输出流(接口)
OutputStream
是Java IO(输入输出)库中的一个重要抽象类,它位于java.io
包中。这个类是所有字节输出流的超类,提供了输出字节数据到目的地(如文件、内存、网络等)的共性方法。以下是对OutputStream
的详细解析:
3.2.1.基本概述
- 定义:
OutputStream
是字节输出流的抽象类,用于表示数据输出到目的地。 - 作用:作为所有字节输出流的基类,定义了输出数据的基本方法,如
write()
方法用于写入字节数据。 - 继承关系:它是Java IO库中处理字节输出流的起点,所有具体的字节输出流类(如
FileOutputStream
、ByteArrayOutputStream
等)都继承自它。
3.2.2.主要方法
- write(int b):将指定的字节写入此输出流。
- write(byte[] b):将
b.length
字节从指定的字节数组写入此输出流。 - write(byte[] b, int off, int len):从指定的字节数组写入
len
字节到输出流,从偏移量off
开始。 - flush():刷新此输出流并强制写出所有缓冲的输出字节。
- close():关闭此输出流并释放与此流相关联的任何系统资源。
3.2.3.子类及应用场景
- FileOutputStream:用于将数据写入文件。它是
OutputStream
的一个具体实现,用于处理文件写入操作。 - ByteArrayOutputStream:在内存中创建一个字节数组缓冲区,所有数据写入此流都将保存在该字节数组中。这个类非常适合需要捕获内存缓冲区输出流的场景。
- BufferedOutputStream:为另一个输出流添加缓冲功能,提高写入效率。它内部维护了一个字节缓冲区,通过减少实际写入次数来优化性能。
- PipedOutputStream:与
PipedInputStream
一起使用,实现线程间的管道通信。数据可以被一个线程写入PipedOutputStream
,然后由另一个线程从PipedInputStream
中读取。
3.2.4.使用注意事项
- 关闭流:在使用完输出流后,应该及时调用
close()
方法关闭流,以释放系统资源。在try-with-resources
语句中自动管理资源是一种更好的做法。 - 刷新缓冲区:在某些情况下,即使调用了
write()
方法,数据也可能还保留在输出流的缓冲区中,而没有真正写入到目的地。此时,可以通过调用flush()
方法来强制刷新缓冲区,确保数据被写出。 - 异常处理:在读写过程中可能会遇到
IOException
,因此需要对这些异常进行适当处理,以保证程序的健壮性。
3.2.5.示例代码
如何使用FileOutputStream
将字符串写入文件:
import java.io.FileOutputStream;
import java.io.IOException;
public class OutputStreamExample {
public static void main(String[] args) {
String filePath = "output.txt";
String content = "Hello, OutputStream!";
byte[] bytes = content.getBytes();
try (FileOutputStream fos = new FileOutputStream(filePath)) {
fos.write(bytes);
// 在try-with-resources语句中,fos会在代码块执行完毕后自动关闭
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.3.Reader|字符输入流(接口)
在Java中,Reader
是java.io
包下的一个抽象类,用于读取字符流。与InputStream
类处理字节流不同,Reader
类及其子类用于处理字符流,这意味着它们以字符为单位读取数据,而不是字节。字符流对于处理文本数据特别有用,因为文本数据通常以字符(如字母、数字、标点符号等)的形式存在,而不是字节。
3.3.1.主要方法
Reader
类定义了一些用于读取字符数据的方法,其中最重要的是read()
方法。read()
方法有几个重载版本,允许你以不同的方式读取字符:
int read()
:读取单个字符,并返回该字符的整数值(范围从0到65535)。如果到达流的末尾,则返回-1。int read(char[] cbuf, int off, int len)
:将字符读入数组的一部分。返回实际读取的字符数,如果到达流的末尾,则返回-1。void read(CharBuffer target)
(Java NIO):从输入流中读取字符到指定的字符缓冲区。
此外,Reader
类还提供了close()
方法来关闭流,并释放与之关联的资源。
3.3.2.子类
Reader
类有许多子类,用于从不同类型的源读取字符数据。以下是一些常见的子类:
- FileReader:用于从文件中读取字符数据。
- BufferedReader:为其他字符输入流提供缓冲,以提高读取效率。它还提供了
readLine()
方法,用于按行读取文本。 - StringReader:用于从字符串中读取字符数据。
- CharArrayReader:用于从字符数组中读取字符数据。
- InputStreamReader:是字节流到字符流的桥梁。它使用指定的字符集将字节解码为字符。
3.3.3.使用示例
以下是一个使用FileReader
和BufferedReader
从文件中读取文本数据的示例:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class ReaderExample {
public static void main(String[] args) {
String filePath = "example.txt";
try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.3.4.注意事项
- 在处理文件或网络等资源时,务必在完成后关闭流,以释放系统资源。
try-with-resources
语句是管理资源的一种方便且安全的方式。 - 当读取字符流时,需要考虑到字符编码的问题。
InputStreamReader
允许你指定字符编码,这对于处理非UTF-8编码的文本文件特别有用。 - 字符流和字节流在处理文本数据时的主要区别在于字符流以字符为单位进行读取和写入,而字节流以字节为单位进行操作。在处理文本时,字符流通常更加直观和方便。
3.4.Writer|字符输出流(接口)
在Java中,Writer
是一个位于java.io
包下的抽象类,它用于将字符数据写入到目标位置,如文件、网络连接、内存缓冲区等。Writer
类及其子类提供了一系列方法来实现字符数据的输出操作。以下是对Java中Writer
类的详细解析:
3.4.1.概述
- 定义:
Writer
是字符输出流的抽象类,用于将字符数据写入到目的地。 - 继承关系:
Writer
类继承自java.lang.Object
,并实现了Closeable
、Flushable
、Appendable
和AutoCloseable
接口。 - 子类:
Writer
类有多个子类,如FileWriter
、BufferedWriter
、PrintWriter
、StringWriter
等,它们提供了不同的字符输出功能。
3.4.2.主要功能方法
- write(int c):写入单个字符。该字符包含在给定整数值的16个低位中,16个高位被忽略。
- write(char[] cbuf):写入字符数组。
- write(char[] cbuf, int off, int len):写入字符数组的一部分。
off
是开始写字符的偏移量,len
是要写入的字符数。 - write(String str):写入字符串。
- write(String str, int off, int len):写入字符串的一部分。
off
是开始写字符的偏移量,len
是要写入的字符数。 - append(CharSequence csq):将指定的字符序列追加到此writer。
- append(CharSequence csq, int start, int end):将指定字符序列的子序列追加到此writer。
- append(char c):将指定的字符追加到此writer。
- flush():刷新该流的缓冲。一次
flush()
调用将刷新Writers和OutputStreams链中的所有缓冲区。 - close():关闭此流,但要先刷新它。关闭流后,进一步的
write()
或flush()
调用将导致抛出IOException
。
3.4.3.使用场景
- 文件写入:使用
FileWriter
子类将字符数据写入文件。 - 内存写入:使用
StringWriter
或CharArrayWriter
子类将字符数据写入内存缓冲区。 - 缓冲写入:使用
BufferedWriter
子类为其他字符输出流提供缓冲,以提高写入效率。 - 格式化写入:使用
PrintWriter
子类提供格式化输出,如打印对象、自动刷新等。
3.4.4.注意事项
- 字符编码:在写入字符数据时,需要注意字符编码的问题。
FileWriter
默认使用系统默认的字符编码,但可以通过构造函数指定字符编码。 - 缓冲区管理:
Writer
及其子类通常使用缓冲区来提高写入效率。调用flush()
方法可以强制将缓冲区中的数据写入目标位置,而close()
方法在关闭流之前会先调用flush()
方法。 - 异常处理:在进行IO操作时,可能会遇到
IOException
,因此需要对这些异常进行适当处理,以保证程序的健壮性。
3.4.5.示例代码
以下是一个使用FileWriter
将字符串写入文件的示例代码:
import java.io.FileWriter;
import java.io.IOException;
public class WriterExample {
public static void main(String[] args) {
String filePath = "output.txt";
String content = "Hello, Writer!";
try (FileWriter writer = new FileWriter(filePath)) {
writer.write(content);
// 无需手动调用flush(),try-with-resources会自动在结束时调用close(),close()会先调用flush()
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.流框架类结构图
5.对象序列化与反序列化
在Java中,对象序列化(Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程,通常是将对象转换为字节序列,以便可以保存到文件中或在网络上传输。反序列化(Deserialization)则是序列化的逆过程,即将字节序列恢复为对象。
5.1.序列化与反序列化的用途
- 持久化:将对象的状态保存到文件中,以便在程序关闭后再次运行时能够重新加载这些对象。
- 网络传输:在网络中传输对象时,需要将对象序列化为字节流,然后在接收端进行反序列化以恢复对象。
5.2.实现序列化与反序列化的步骤
5.2.1. 实现Serializable
接口
要使一个类的对象可序列化,该类必须实现java.io.Serializable
接口。这个接口是一个标记接口,不包含任何方法,其存在仅用于指示对象可以被序列化。
import java.io.Serializable;
public class MyObject implements Serializable {
// 类的成员变量
private static final long serialVersionUID = 1L; // 可选,用于版本控制
private int id;
private String name;
// 构造方法、getter和setter省略
}
注意:serialVersionUID
是一个可选的字段,用于确保序列化和反序列化时版本的兼容性。如果类的序列化版本发生变化(如增加了新的字段),并且希望保持与旧版本的兼容性,就需要显式地指定serialVersionUID
。
5.2.2. 序列化对象
使用ObjectOutputStream
类将对象写入到输出流中,如文件输出流FileOutputStream
。
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SerializeExample {
public static void main(String[] args) {
MyObject obj = new MyObject();
// 设置obj的属性值...
try (FileOutputStream fileOut = new FileOutputStream("obj.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
out.writeObject(obj);
System.out.println("Serialized data is saved in obj.ser");
} catch (IOException i) {
i.printStackTrace();
}
}
}
5.2.3. 反序列化对象
使用ObjectInputStream
类从输入流中读取对象,如文件输入流FileInputStream
。
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class DeserializeExample {
public static void main(String[] args) {
MyObject obj = null;
try (FileInputStream fileIn = new FileInputStream("obj.ser");
ObjectInputStream in = new ObjectInputStream(fileIn)) {
obj = (MyObject) in.readObject();
System.out.println("Deserialized data is: " + obj);
} catch (IOException i) {
i.printStackTrace();
return;
} catch (ClassNotFoundException c) {
System.out.println("Class not found");
c.printStackTrace();
return;
}
// 使用obj对象...
}
}
5.3.注意事项
- 序列化时,只会序列化对象的非静态和非瞬态(即没有用
transient
关键字修饰的)成员变量。 - 序列化过程中会抛出
IOException
和ClassNotFoundException
,需要适当处理这些异常。 - 序列化后的数据是依赖于Java虚拟机(JVM)的,不同版本的JVM序列化的数据可能不兼容。
- 序列化是一种深度克隆技术,可以复制对象及其引用的对象图。但是,它并不总是适用于所有情况,特别是当对象包含对系统资源的引用(如文件句柄、网络连接等)时。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!