目录
3.1 字节流的包装器:FilterInputStream 和 FilterOutputStream
4. 文件字节流:FileInputStream 和 FileOutputStream
6. 数组字节流:ByteArrayInputStream 和 ByteArrayOutputStream
7. 缓冲字节流:BufferedInputStream 和 BufferedOutputStream
8. 转换流:InputStreamReader 和 OutputStreamWriter
File
类
Java中的File
类是java.io
包的一部分,它提供了一种方式来表示文件和目录。以下是File
类的一些基础知识:
构造函数:
File(String pathname)
:创建一个新File对象,表示具有指定路径名的文件或目录。File(String parent, String child)
:创建一个新File对象,表示具有指定父路径和子路径的文件或目录。File(File parent, String child)
:创建一个新File对象,表示具有指定父路径(File对象)和子路径的文件或目录。
方法:
exists()
:检查此File对象表示的文件或目录是否存在。isDirectory()
:检查此File对象表示的是否是一个目录。isFile()
:检查此File对象表示的是否是一个文件。length()
:返回此File对象表示的文件的长度。mkdir()
:创建此File对象表示的目录。delete()
:删除此File对象表示的文件或目录。renameTo(File dest)
:重命名此File对象表示的文件或目录。listFiles()
:返回一个File数组,表示此File对象表示的目录中的文件和目录。getAbsolutePath()
:返回此File对象表示的文件或目录的绝对路径。getCanonicalPath()
:返回此File对象表示的文件或目录的规范路径。lastModified()
:返回此File对象表示的文件的最后修改时间。setLastModified(long time)
:设置此File对象表示的文件的最后修改时间。-
示例:
// 创建File对象,表示当前目录下的"test.txt"文件
File file = new File("test.txt");
// 检查文件是否存在
if (file.exists()) {
System.out.println("文件存在");
}
// 检查是否是文件
if (file.isFile()) {
System.out.println("这是一个文件");
}
// 获取文件的长度
long fileSize = file.length();
System.out.println("文件大小:" + fileSize + "字节");
// 创建一个目录
File dir = new File("newDir");
dir.mkdir();
// 删除文件
file.delete();
// 重命名文件
File newFile = new File("newName.txt");
boolean renamed = file.renameTo(newFile);
if (renamed) {
System.out.println("文件重命名成功");
}
// 获取文件的绝对路径
String absolutePath = file.getAbsolutePath();
System.out.println("文件的绝对路径:" + absolutePath);
注意事项:
File
类主要用于文件和目录的抽象表示,并不涉及文件内容的I/O操作。- 在进行文件操作时,需要处理
IOException
异常。 - 文件路径可以是相对路径或绝对路径。
- 在使用
listFiles()
方法时,如果File对象表示的不是一个目录,那么返回的数组长度为0。 renameTo()
方法在不同操作系统上的行为可能不同,需要谨慎使用。
1.2.处理临时文件方法:
在Java中,处理临时文件通常涉及到java.io.File
类和java.nio.file
包中的类。以下是一些常见的临时文件操作:
创建临时文件:
- 使用
File.createTempFile()
:- 这个方法可以在默认临时文件夹中创建一个临时文件,并可以指定前缀和后缀。
- 文件创建后立即被删除,你需要手动打开它。
File tempFile = File.createTempFile("temp", ".txt");
- 使用
Files.createTempFile()
:- 这个方法在
java.nio.file
包中,允许你指定更多的参数,比如文件属性。 - 返回的是一个
Path
对象,代表临时文件。
- 这个方法在
Path tempFilePath = Files.createTempFile("temp", ".txt");
删除临时文件:
- 临时文件通常在程序结束时删除,但你也可以显式地删除它们。
if (tempFile.exists()) {
tempFile.delete();
}
使用临时目录:
- 你可以获取系统的临时文件夹,并在其中创建文件。
File tempDir = new File(System.getProperty("java.io.tmpdir"));
File tempFileInDir = new File(tempDir, "myTempFile.txt");
确保临时文件在程序退出时删除:
- 使用
java.io.File
的deleteOnExit()
方法,确保文件在JVM退出时删除。
tempFile.deleteOnExit();
读写临时文件:
- 你可以使用标准的I/O类来读写临时文件。
// 写入临时文件
try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile))) {
writer.write("Hello, World!");
}
// 读取临时文件
try (BufferedReader reader = new BufferedReader(new FileReader(tempFile))) {
String line = reader.readLine();
System.out.println(line);
}
注意事项:
- 临时文件通常用于存储程序运行时的中间数据,不应该用于存储重要数据。
- 临时文件的安全性取决于操作系统和JVM的实现,可能存在被其他用户访问的风险。
- 使用
deleteOnExit()
方法删除临时文件时,文件的删除操作是在JVM退出时进行的,如果JVM没有正常退出,文件可能不会被删除。 - 在多线程环境中,创建和删除临时文件的操作应该是线程安全的。
1.3.遍历目录及其子目录下的文件
Java中File
类提供了一些方法来遍历目录及其子目录下的文件,以下是一些详细的知识点:
1. listFiles()
方法
listFiles()
是最基本的方法,用于列出目录中的所有文件和子目录。
File[] listFiles()
: 返回一个File
数组,包含目录中的所有文件和子目录。File[] listFiles(FileFilter filter)
: 返回一个经过过滤器筛选的File
数组。File[] listFiles(FilenameFilter filter)
: 返回一个经过文件名过滤器筛选的File
数组。
2. list()
方法
list()
方法是listFiles()
的一个简单形式,它返回一个字符串数组,包含目录中的所有文件和子目录的名称。
String[] list()
: 返回目录中所有文件和子目录的名称。String[] list(FilenameFilter filter)
: 返回经过文件名过滤器筛选的文件和子目录的名称。
3. 递归遍历
要遍历所有子目录,你需要使用递归方法。
void listFilesRecursively(File dir) {
if (dir.isDirectory()) {
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
listFilesRecursively(file);
} else {
// 处理文件
}
}
}
}
}
4. 使用FileFilter
FileFilter
接口允许你定义过滤条件,只列出满足条件的文件或目录。
FileFilter filter = file -> file.getName().endsWith(".txt");
File[] files = dir.listFiles(filter);
5. 使用FilenameFilter
FilenameFilter
接口类似于FileFilter
,但它只接受文件名作为参数。
FilenameFilter filter = (dir, name) -> name.endsWith(".txt");
File[] files = dir.listFiles(filter);
6. 异常处理
在遍历文件时,可能会抛出SecurityException
,因为某些目录可能没有读取权限。
try {
File[] files = dir.listFiles();
} catch (SecurityException se) {
// 处理安全异常
}
7. 符号链接
在遍历目录时,需要注意符号链接(symlinks)。默认情况下,listFiles()
不会跟随符号链接。
8. 文件属性
File
对象提供了一些方法来获取文件的属性,如exists()
, isDirectory()
, isFile()
, canRead()
, canWrite()
, length()
, lastModified()
等。
9. 文件路径
getAbsolutePath()
, getCanonicalPath()
, getPath()
, getName()
等方法可以用来获取文件的路径和名称。
10. 遍历性能
对于大型文件系统,递归遍历可能会消耗大量资源。在这种情况下,考虑使用java.nio.file
包中的Files.walk()
方法,它提供了更高效的遍历方式。
11. 遍历限制
listFiles()
方法可能不会列出所有文件,特别是如果文件系统在遍历过程中发生变化。此外,它也不会列出隐藏文件,除非指定了相应的过滤器。
12. 线程安全
listFiles()
方法不是线程安全的。如果你需要在多线程环境中使用它,需要自己管理同步。
1.4.删除文件和目录
在Java中,使用I/O操作删除文件和目录涉及到java.io.File
类的一些方法。以下是删除文件和目录的详细知识点:
删除单个文件
-
使用
delete()
方法:File
对象的delete()
方法可以用来删除单个文件。如果文件正在被使用或没有相应的权限,删除操作可能会失败。File file = new File("path/to/file.txt"); boolean isDeleted = file.delete(); // 返回一个布尔值,指示是否删除成功
-
异常处理: 删除操作可能会抛出
SecurityException
,如果你的程序没有足够的权限去删除文件。try { boolean isDeleted = file.delete(); if (isDeleted) { System.out.println("文件已被删除"); } else { System.out.println("文件删除失败"); } } catch (SecurityException se) { System.out.println("没有权限删除文件"); }
删除目录
-
使用递归方法: 删除目录通常需要递归地删除其所有子文件和子目录。
public static void deleteDirectory(File dir) { if (dir.isDirectory()) { File[] items = dir.listFiles(); if (items != null) { for (File item : items) { deleteDirectory(item); // 递归删除子文件和子目录 } } } // 删除空目录 dir.delete(); }
-
删除非空目录:
delete()
方法无法删除包含文件或目录的目录。必须先删除所有子文件和子目录。 -
使用
FileUtils
类(Apache Commons IO库): Apache Commons IO库提供了FileUtils
类,它有一个deleteDirectory(File directory)
方法,可以方便地删除非空目录。import org.apache.commons.io.FileUtils; public class DeleteDirectory { public static void main(String[] args) { try { FileUtils.deleteDirectory(new File("path/to/directory")); } catch (IOException e) { e.printStackTrace(); } } }
符号链接(Symbolic Links)
-
符号链接的处理: 默认情况下,
delete()
方法不会删除符号链接指向的目标文件。它只会删除符号链接本身。 -
强制删除符号链接的目标: 如果你需要删除符号链接指向的目标文件,需要先解析链接,然后删除。
File symlink = new File("path/to/symlink"); if (symlink.exists() && symlink.isDirectory()) { File target = symlink.getCanonicalFile(); deleteDirectory(target); } symlink.delete();
安全和性能考虑
-
检查文件存在性: 在尝试删除之前,检查文件是否存在可以避免异常。
if (file.exists()) { file.delete(); }
-
处理并发删除: 在多线程环境中,文件可能会被其他线程删除。处理这种情况的一种方法是在
delete()
方法失败时进行重试。 -
使用
deleteOnExit()
方法: 如果你希望在程序退出时删除文件,可以使用deleteOnExit()
方法。这个方法会在JVM退出时删除指定的文件。file.deleteOnExit();
-
日志记录: 在删除文件或目录时,记录日志可以帮助调试和跟踪文件操作。
-
清理临时文件: 对于临时文件,确保在不再需要时删除它们,以避免磁盘空间的浪费。
字节流
Java中的I/O(输入/输出)字节流是用于处理字节数据的流。字节流可以是输入流(从数据源读取数据)或输出流(向数据源写入数据)。以下是Java I/O字节流的基础知识点详细讲解:
1. 字节输入流:InputStream
FileInputStream
:从文件中读取字节数据。ByteArrayInputStream
:从字节数组中读取数据。PipedInputStream
:从与另一个线程连接的管道中读取数据
InputStream
是所有字节输入流的抽象类超类。它提供了基本的读取字节数据的方法。
- 常用方法:
int read()
: 读取单个字节的数据,返回读取的字节(0到255的int值),如果已到达流的末尾,则返回-1。int read(byte[] b)
: 读取一些字节数并将它们存储到数组b中,返回实际读取的字节数,如果已到达流的末尾,则返回-1。int read(byte[] b, int off, int len)
: 从指定位置读取最多len个字节到数组b中,从偏移量off开始存储,返回实际读取的字节数,如果已到达流的末尾,则返回-1。void close()
: 关闭流,释放与之关联的所有资源。
2. 字节输出流:OutputStream
FileOutputStream
:向文件写入字节数据。ByteArrayOutputStream
:向字节数组写入数据。PipedOutputStream
:向与另一个线程连接的管道中写入数据。
OutputStream
是所有字节输出流的抽象类超类。它提供了基本的写入字节数据的方法。
- 常用方法:
void write(int b)
: 写入单个字节的数据。void write(byte[] b)
: 写入一个byte数组的数据。void write(byte[] b, int off, int len)
: 写入byte数组中从偏移量off开始的len个字节。void flush()
: 清空输出流,确保所有缓冲中的数据都被写出。void close()
: 关闭流,释放与之关联的所有资源。
3.字节流的使用
读取字节数据
使用InputStream
读取数据的基本方法是调用read()
方法,它可以返回读取的字节(int类型,范围从0到255),或者返回-1表示已到达文件末尾。
FileInputStream fis = new FileInputStream("example.bin");
int byteRead = fis.read(); // 读取单个字节
while (byteRead != -1) {
// 处理读取的字节
byteRead = fis.read(); // 继续读取
}
fis.close(); // 关闭流
写入字节数据
使用OutputStream
写入数据的基本方法是调用write(int b)
方法,传入要写入的字节。
FileOutputStream fos = new FileOutputStream("example.bin");
fos.write(someByte); // 写入单个字节
// 或者写入字节数组
byte[] data = ...;
fos.write(data);
fos.close(); // 关闭流
3.1 字节流的包装器:FilterInputStream
和 FilterOutputStream
这些类提供了对其他输入/输出流的包装,以添加额外的功能。例如,BufferedInputStream
和BufferedOutputStream
提供了缓冲功能来提高I/O操作的效率。
4. 文件字节流:FileInputStream
和 FileOutputStream
FileInputStream
和FileOutputStream
可以直接用于文件的读写操作。- 写入文件时,如果文件已存在,
FileOutputStream
会覆盖原有内容;如果需要追加内容,需要在构造函数中传入true
- 这些类专门用于文件操作,它们继承自
InputStream
和OutputStream
。 FileInputStream
: 从文件中读取字节。FileOutputStream
: 向文件写入字节。
FileOutputStream fos = new FileOutputStream("example.bin", true); // 追加模式
5. 数据字节流:DataInputStream
DataInputStream
类用于读取Java基本数据类型,它继承自FilterInputStream
并提供了对读取方法的覆盖,以支持基本数据类型的读取。
6. 数组字节流:ByteArrayInputStream
和 ByteArrayOutputStream
这些类使用字节数组作为数据源。
ByteArrayInputStream
: 从字节数组中读取数据。ByteArrayOutputStream
: 向字节数组中写入数据。
7. 缓冲字节流:BufferedInputStream
和 BufferedOutputStream
BufferedInputStream
和BufferedOutputStream
:包装其他输入/输出流,提供内部缓冲区以减少实际的I/O操作次数。
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("example.bin"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("example.bin"));
8. 转换流:InputStreamReader
和 OutputStreamWriter
DataInputStream
:从InputStream
中读取基本Java数据类型,并支持将字节转换为字符串。DataOutputStream
:将基本Java数据类型写入OutputStream
,并支持写入字符串。
DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream("example.bin")));
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("example.bin")));
9. 打印流:PrintStream
PrintStream
是一个输出流,可以方便地打印数据,它继承自OutputStream
。
10. 资源管理
- 确保在I/O操作完成后关闭流,以释放系统资源。
- 使用
try-with-resources
语句自动管理资源。
try (FileInputStream fis = new FileInputStream("example.bin");
FileOutputStream fos = new FileOutputStream("example.bin")) {
// I/O操作
} catch (IOException e) {
e.printStackTrace();
}
11. 字节序
在跨平台进行字节数据交换时,需要注意字节序(大端或小端)的问题。
12. 管道流
PipedInputStream
和PipedOutputStream
可用于线程间通信。
13.使用字节流的注意事项:
- 关闭流:始终要记得在操作完成后关闭流,以释放系统资源。
- 异常处理:I/O操作可能会抛出
IOException
,需要适当处理。
try {
// I/O操作
} catch (IOException e) {
e.printStackTrace();
}
- 线程安全:大多数字节流不是线程安全的,如果需要在多线程中使用,需要采取同步措施。
- 数据丢失:在网络或I/O错误的情况下,数据可能会丢失或损坏。
字符流
字符流是IO流中的一种,专门用于处理字符数据。字符流主要用于文本文件的读写操作,它使用字符编码来转换字节和字符。以下是Java中字符流的详细介绍:
字符流的分类:
Reader:用于从字符输入流中读取字符数据。
字符输入流(Reader 类)的方法:
-
read():
- 读取单个字节的数据,返回读取的字符(int 类型,范围从 0 到 65535)。
- 如果已经到达文件末尾,则返回 -1。
-
read(char[] cbuf):
- 读取一些字符并存储到字符数组 cbuf 中。
- 返回实际读取的字符数量,或者在到达文件末尾时返回 -1。
-
read(char[] cbuf, int off, int len):
- 从指定位置 off 开始读取最多 len 个字符到字符数组 cbuf 中。
- 返回实际读取的字符数量,或者在到达文件末尾时返回 -1。
-
ready():
- 检查是否还有更多数据可读。
- 如果下一个 read() 调用能够立即读取至少一个字符,则返回 true。
-
close():
- 关闭该流并释放与此流相关联的所有资源。
字符输入流(Reader):
- FileReader:从文件中读取字符数据。
- FilterReader:作为过滤字符输入流的基类。
- BufferedReader:从字符输入流中读取文本并缓冲字符,以便有效地读取字符、数组和行。
- LineNumberReader:跟踪行号的文本行读取器。
- PushbackReader:可以单个字符地将字符推回输入流的字符流。
-
Writer:用于向字符输出流中写入字符数据。
字符输出流(Writer 类)的方法:
-
write(int c):
- 写入单个字符。
-
write(char[] cbuf):
- 写入字符数组 cbuf 中的所有字符。
-
write(char[] cbuf, int off, int len):
- 写入字符数组 cbuf 中从偏移量 off 开始的 len 个字符。
-
write(String str):
- 写入字符串 str 中的所有字符。
-
write(String str, int off, int len):
- 写入字符串 str 中从偏移量 off 开始的 len 个字符。
-
flush():
- 清空输出流,迫使所有缓冲中的数据被写出。
-
close():
- 关闭该流并释放与此流相关联的所有资源。
字符输出流(Writer):
- FileWriter:向文件写入字符数据。
- FilterWriter:作为过滤字符输出流的基类。
- BufferedWriter:将字符写入字符输出流并可能缓冲字符,以便有效地写入字符、数组和字符串。
- PrintWriter:向文本输出流打印对象的格式化表示形式。
- CharArrayWriter:字符数组输出流。
带缓冲的字符流(BufferedReader 和 BufferedWriter):
-
BufferedReader:
-
继承自 Reader 类,提供了一个字符数组的缓冲区,可以一次性读取多行文本。
-
特殊方法:
- readLine():读取一行文本,包括行终止符。
- lines()(Java 8+):返回一个由文件行组成的流。
-
-
BufferedWriter:
- 继承自 Writer 类,提供了一个字符数组的缓冲区,可以一次性写入多行文本。
字符编码:
字符流在读取或写入时需要指定字符编码,Java默认使用平台默认的字符编码。字符编码用于将字符转换为字节,反之亦然。
使用字符流读取文件:
FileReader fr = null;
try {
fr = new FileReader("example.txt");
int i;
while ((i = fr.read()) != -1) {
System.out.print((char) i);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用字符流写入文件:
FileWriter fw = null;
try {
fw = new FileWriter("example.txt");
fw.write("Hello, World!");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
字符流的高级特性:
- BufferedReader 和 BufferedWriter:这两个类提供了缓冲功能,可以提高读写效率。
- PrintWriter:提供了格式化输出的功能,类似于
System.out
。 - 字符集(Charset):从Java 7开始,可以使用
java.nio.charset.Charset
来指定字符集,而不是依赖平台默认的字符集。
注意事项:
- 总是确保在操作完成后关闭流,以释放系统资源。
- 使用
try-with-resources
语句可以自动管理资源,确保流在使用后被关闭。 - 处理
IOException
,因为IO操作可能会遇到各种I/O错误。
转换流
也称为桥接流 是用来在字节流和字符流之间进行转换的流。由于 Java 的 IO 流库中存在两种类型的流(字节流和字符流),在某些情况下,我们可能需要在它们之间进行转换。转换流提供了一种方便的方式来实现这种转换。
转换输入流(InputStreamReader)
`InputStreamReader` 是一个字符流,它读取字节并使用指定的字符编码将其转换为字符。它通常用于将 `InputStream` 转换为 `Reader`。
常用构造函数:
- `InputStreamReader(InputStream in)`:创建一个使用平台默认字符编码的 `InputStreamReader`。
- `InputStreamReader(InputStream in, String charsetName)`:创建一个使用指定字符编码的 `InputStreamReader`。
#### 示例代码:
InputStream is = ...; // 假设已经有一个字节输入流
try (InputStreamReader isr = new InputStreamReader(is, "UTF-8")) {
int c;
while ((c = isr.read()) != -1) {
System.out.print((char) c); // 将读取的字符打印出来
}
} catch (IOException e) {
e.printStackTrace();
}
转换输出流(OutputStreamWriter)
OutputStreamWriter
是一个字符流,它将字符转换为字节并写入到输出流中。它通常用于将 Writer
转换为 OutputStream
。
常用构造函数:
- `OutputStreamWriter(OutputStream out)`:创建一个使用平台默认字符编码的 `OutputStreamWriter`。
- `OutputStreamWriter(OutputStream out, String charsetName)`:创建一个使用指定字符编码的 `OutputStreamWriter`。
OutputStream os = ...; // 假设已经有一个字节输出流
try (OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8")) {
osw.write("Hello, World!");
osw.flush(); // 确保所有字符都被写出
} catch (IOException e) {
e.printStackTrace();
}
### 注意事项:
- 字符编码:在使用转换流时,指定正确的字符编码非常重要,否则可能会导致字符编码错误,出现乱码。
- 转换流是字符编码的桥梁:它们在字节和字符之间进行转换,使得可以处理字符数据而不是原始字节。
转换流使得 Java 的 IO 流库更加灵活,能够处理各种不同的数据格式和编码需求。在实际开发中,根据需要选择合适的流类型和编码方式,可以提高程序的健壮性和可移植性。