文件操作和IO
- 文件
- Java中操作文件
- 文件内容的读写(数据流)
-
- Reader和Writer
-
- [字符输入流 Reader](#字符输入流 Reader)
- [字符输出流 Writer](#字符输出流 Writer)
- [FileReader 和 FileWriter](#FileReader 和 FileWriter)
- InputStream和OutputStream
- 小程序
文件
我们先来认识狭义上的文件(file)。针对硬盘这种持久化存储的I/O设备,当我们想要进行数据保存时,往往不是保存成一个整体,而是独立成一个个的单位进行保存,这个独立的单位就被抽象成文件的概念,就类似办公桌上的一份份真实的文件一般。
树形结构组织和目录
同时,随着文件越来越多,对文件的系统管理也被提上了日程,如何进行文件的组织呢,一种合乎自然的想法出现了,就是按照层级结构进行组织 ------ 也就是我们数据结构中学习过的树形结构。这样,一种专门用来存放管理信息的特殊文件诞生了,也就是我们平时所谓文件夹(folder)或者目录(directory)的概念。
文件路径
如何在文件系统中如何定位我们的一个唯一的文件就成为当前要解决的问题,但这难不倒计算机科学
家,因为从树型结构的角度来看,树中的每个结点都可以被一条从根开始,一直到达的结点的路径所描
述,而这种描述方式就被称为文件的绝对路径(absolute path)
除了可以从根开始进行路径的描述,我们可以从任意结点出发,进行路径的描述,而这种描述方式就被
称为相对路径(relative path),相对于当前所在结点的一条路径
Java中操作文件
File
File类是用于处理文件和目录的基本操作的类。它提供了许多方法来创建、删除、重命名、检查文件或目录的存在性、获取文件属性等。
要使用File类,首先需要实例化一个File对象,该对象可以表示文件或目录的路径。
- 创建文件或目录:
**File file = new File("path/to/file");:**使用指定的路径创建一个File对象。
file.mkdir() :创建一个目录。
file.createNewFile():创建一个新文件。
- 删除文件或目录:
file.delete() :删除文件或目录。
file.deleteOnExit():在JVM退出时删除文件或目录。
- 重命名文件或目录:
file.renameTo(newName):将文件或目录重命名为新名称。
- 判断文件或目录是否存在:
file.exists() :检查文件或目录是否存在。
file.isFile() :检查是否为文件。
file.isDirectory():检查是否为目录。
- 获取文件或目录的属性:
file.getName() :获取文件或目录的名称。
file.getAbsolutePath() :获取文件或目录的绝对路径。
file.length() :获取文件的长度(字节数)。
file.lastModified():获取文件或目录的最后修改时间。
- 遍历目录:
file.listFiles():返回目录下的所有文件和子目录的File对象数组。
构造方法
文件内容的读写(数据流)
(抽象基类) | 输入流 | 输出流 |
---|---|---|
字节流 | InputStream | OutputStream |
字符流 | Reader | Writer |
Java的IO流共涉及40多个类,实际上非常规则,都是由上面四个抽象类派生的
如果衍生自InputStream或者Reader,就可以使用read方法来读数据
如果衍生自OutputStream或者Writer,就可以使用write方法来写数据
文件操作的核心步骤就是打开文件,关闭文件,读文件,写文件
其中关闭文件是非常重要的,一个进程打开一个文件,要在系统中申请一定资源的(占用进程里pcb里文件描述符表的一个表项,且这个表长度有限,不会扩容),此时如果不释放资源,就会出现文件资源泄露的问题.
Reader和Writer
Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。不能操作图片,视频等非文本文件。
常见的文本文件有如下的格式:.txt、.java、.c、.cpp、.py等
注意:.doc、.xls、.ppt这些都不是文本文件。
字符输入流 Reader
java.io.Reader
抽象类是表示用于读取字符流的所有类的父类,可以读取字符信息到内存中。它定义了字符输入流的基本共性功能方法。
public int read()
: 从输入流读取一个字符。 虽然读取了一个字符,但是会自动提升为int类型。返回该字符的Unicode编码值。如果已经到达流末尾了,则返回-1。public int read(char[] cbuf)
: 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。每次最多读取cbuf.length个字符。返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返回-1。public int read(char[] cbuf,int off,int len)
:从输入流中读取一些字符,并将它们存储到字符数组 cbuf中,从cbuf[off]开始的位置存储。每次最多读取len个字符。返回实际读取的字符个数。如果已经到达流末尾,没有数据可读,则返回-1。public void close()
:关闭此流并释放与此流相关联的任何系统资源。
注意:当完成流的操作时,必须调用close()方法,释放系统资源,否则会造成内存泄漏。
字符输出流 Writer
java.io.Writer
抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节输出流的基本共性功能方法。
public void write(int c)
:写出单个字符。public void write(char[] cbuf)
:写出字符数组。public void write(char[] cbuf, int off, int len)
:写出字符数组的某一部分。off:数组的开始索引;len:写出的字符个数。public void write(String str)
:写出字符串。public void write(String str, int off, int len)
:写出字符串的某一部分。off:字符串的开始索引;len:写出的字符个数。public void flush()
:刷新该流的缓冲。public void close()
:关闭此流。
注意:当完成流的操作时,必须调用close()方法,释放系统资源,否则会造成内存泄漏。
FileReader 和 FileWriter
FileReader
java.io.FileReader
类用于读取字符文件,构造时使用系统默认的字符编码和默认字节缓冲区。
FileReader(File file)
: 创建一个新的 FileReader ,给定要读取的File对象。FileReader(String fileName)
: 创建一个新的 FileReader ,给定要读取的文件的名称。
举例,读取文件中的字符数据,显示在控制台上
java
public class Demo1 {
//读取文件中的内容,展示在控制台
public static void main(String[] args) throws IOException {
File file = new File("D:\\code\\2023_java\\IO\\src\\hello.txt");
System.out.println(file.isFile());
try(FileReader fr = new FileReader(file)){
int data;
while ((data = fr.read()) != -1){
System.out.print((char)data);
}
}
}
}
java
public class Demo2 {
public static void main(String[] args) throws IOException {
File file = new File(".\\src\\hello.txt");
try(FileReader fr = new FileReader(file)){
char[] buf = new char[5];
//记录每次读入的字符个数
int len;
while ((len =fr.read(buf)) != -1){
for (int i = 0; i < len; i++) {
System.out.print(buf[i]);
}
}
}
}
}
FileWriter
java.io.FileWriter
类用于写出字符到文件,构造时使用系统默认的字符编码和默认字节缓冲区。
FileWriter(File file)
: 创建一个新的 FileWriter,给定要读取的File对象。FileWriter(String fileName)
: 创建一个新的 FileWriter,给定要读取的文件的名称。FileWriter(File file,boolean append)
: 创建一个新的 FileWriter,指明是否在现有文件末尾追加内容。
对于输入流来说,File类的对象必须在物理磁盘上存在,否则执行就会报FileNotFoundException。如果传入的是一个目录,则会报IOException异常。
这里是引用对于输出流来说,File类的对象是可以不存在的。
如果File类的对象不存在,则可以在输出的过程中,自动创建File类的对象
如果File类的对象存在,
如果调用FileWriter(File file)或FileWriter(File file,false),输出时会新建File文件覆盖已有的文件
如果调用FileWriter(File file,true)构造器,则在现有的文件末尾追加写出内容。
InputStream和OutputStream
如果我们读取或写出的数据是非文本文件,则Reader、Writer就无能为力了,必须使用字节流。
InputStream
java.io.OutputStream
抽象类是表示字节输出流的所有类的超类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。
public void write(int b)
:将指定的字节输出流。虽然参数为int类型四个字节,但是只会保留一个字节的信息写出。public void write(byte[] b)
:将 b.length字节从指定的字节数组写入此输出流。public void write(byte[] b, int off, int len)
:从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。public void flush()
:刷新此输出流并强制任何缓冲的输出字节被写出。public void close()
:关闭此输出流并释放与此流相关联的任何系统资源。
说明:close()方法,当完成流的操作时,必须调用此方法,释放系统资源。
FileInputStream
java.io.FileInputStream
类是文件输入流,从文件中读取字节。
FileInputStream(File file)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。FileInputStream(String name)
: 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。
FileOutputStream
java.io.FileOutputStream
类是文件输出流,用于将数据写出到文件。
public FileOutputStream(File file)
:创建文件输出流,写出由指定的 File对象表示的文件。public FileOutputStream(String name)
: 创建文件输出流,指定的名称为写出文件。public FileOutputStream(File file, boolean append)
: 创建文件输出流,指明是否在现有文件末尾追加内容。
小程序
扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录)
java
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Demo3 {
//扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件
/**
*
* @param rootDir 根目录文件
* @param token 要查找的字符
* @param result 结果集
*/
private static void scanDir(File rootDir, String token, List<File> result){
File[] files = rootDir.listFiles();
//将根目录下的所有文件放在files中
if(files == null || files.length == 0){
//当根目录为空时,直接返回,
return;
}
for (File file: files) {
if(file.isDirectory()){
//如果这个文件是一个文件夹,那么就递归调用
scanDir(file,token,result);
}else {
//如果是一个普通文件,就检查这个文件的文件名是否包含我们要找的字符
//如果包含,就把这个文件放在结果集中
if (file.getName().contains(token)){
result.add(file.getAbsoluteFile());
}
}
}
}
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入你要扫描的根目录");
String rootFirPath = scanner.nextLine();
File rootDir = new File(rootFirPath);
if(!rootDir.isDirectory()){
System.out.println("您输入的根目录不存在或者不是目录,退出");
return;
}
System.out.println("请输入要查找的文件名中的字符");
String token = scanner.nextLine();
List<File> result = new ArrayList<>();
scanDir(rootDir,token,result);
System.out.println("共找到了符合条件的文件 " + result.size() + " 个,它们分别 是");
for (File file: result) {
System.out.println(file.getCanonicalPath());
}
}
}
进行普通文件的复制
java
public class Demo4 {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要复制的文件");
String sourcePath = scanner.next();
//通过输入的文件名构造出一个File对象
File sourceFile = new File(sourcePath);
if(!sourceFile.exists()){
//若输入的文件不存在,直接退出
System.out.println("此文件不存在,请确认路径是否正确");
return;
}
if(!sourceFile.isFile()){
//判断文件是否是一个普通文件
System.out.println("文件不是一个普通文件,请确认路径是否正确");
return;
}
System.out.println("请输入要复制到的目标路径");
String destPath = scanner.next();
File destFile = new File(destPath);
if(destFile.isFile()){
if(destFile.isDirectory()){
System.out.println("目标路径已经存在,并且是一个目录,请确认路径是否正确");
return;
}
if (destFile.isFile()){
System.out.println("目标路径已经存在.是否进行覆盖?y/n");
String ans = scanner.next();
if(!ans.toLowerCase().equals("y")){
System.out.println("停止复制");
return;
}
}
try(InputStream is = new FileInputStream(sourceFile);
OutputStream os = new FileOutputStream(destFile)){
byte[] buf = new byte[1024];
int len;
while (true){
len = is.read(buf);
if(len == -1){
break;
}
os.write(buf,0,len);
}
os.flush();
}
}
System.out.println("复制结束");
}
}