提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
上节博客我们讲解了JUC组件的用法、线程安全的集合类、死锁问题以及核心面试考点,这节博客我们就一起了解I\O文件的相关知识。
一、认识文件
我们先来认识狭义上的文件(file)。针对硬盘这种待久化存储的I/O设备,当我们想要进行数据保存时,往往不是保存成一个整体,而是独立成一个个的单位进行保存,这个独立的单位就被抽象成文件的概念,就类似办公桌上的一份份真实的文件一般。
文件除了有数据内容之外,还有一部分信息,例如文件名、文件类型、文件大小等并不作为文件的数据存在,我们把这部分信息可以视为文件的元信息。

树形结构组织和目录
同时,随着文件越来越多,对文件的系统管理也被提上了日程,如何进行文件的组织呢,一种合乎自然的想法出现了,就是按照层级结构进行组织---也就是我们数据结构中学习的树形结构。这样,一种专门用来存放管理信息的特殊文件诞生了,也就是我们平时所谓文件夹(folder)或者目录的概念。

Linux上的树形结构

Windows上的树形结构

这是文件夹
文件路径
如何在文件系统中如何定位我们的一个唯一的文件就成为当前要解决的问题,但这难不倒计算机科学家,因为从树形结构的角度来看,树中的每个结点都可以被一条从根开始,一直到达的结点的路径所描述,而这种描述方式就被称为文件的绝对路径。

二、Java中操作文件
本节内容中,我们主要涉及文件的元信息、路径的操作,暂时不涉及文件中内容的读写操作。
Java中通过java.io.File类来对一个文件(包括目录)进行抽象的描述。注意,有File对象,并不代表真实存在该文件。
1.File的常见属性、方法
属性
|---------------|---------------|-------------------------|
| 修饰符及类型 | 属性 | 说明 |
| static String | pathSeparator | 依赖于系统的路径分隔符,String类型的表示 |
| static char | pathSeparator | 依赖于系统的路径分隔符,char类型的表示 |
方法
|--------|--------------------|------------------|
| 修饰符 | 方法签名 | 说明 |
| String | getParent() | 返回File对象的父目录文件路径 |
| String | getName() | 返回File对象的纯文件名称 |
| String | getPath() | 返回对象的文件路径 |
| String | getAbsolutePath() | 返回对象的绝对路径 |
| String | getCanonicalPath() | 返回对象的修饰过的绝对路径 |
代码示例1:
观察 get 系列的特点和差异
java
public class demo1 {
public static void main(String[] args) throws IOException {
File file = new File("..\\keep-passion.txt"); // 并不要求该⽂件真实存
System.out.println(file.getParent());
System.out.println(file.getName());
System.out.println(file.getPath());
System.out.println(file.getAbsolutePath());
System.out.println(file.getCanonicalPath());
}
}

|---------|-----------------|--------------------------------|
| boolean | isDirectory() | 判断File对象代表的文件是否是一个目录 |
| boolean | isFile() | 判断File对象代表的文件是否是一个普通文件 |
| boolean | createNewFile() | 根据File对象,自动创建一个空文件。成功创建后返回true |
|------------|----------------|---------------------------------------|
| String[] | list() | 返回File对象代表的目录下的所有文件名 |
| File[] | listFiles() | 返回File对象代表的目录下的所有文件,以File对象表示 |
| boolean | mkdir() | 创建File对象代表的目录 |
| void | deleteOnExit() | 根据File 对象,标注文件将被删除,删除动作会到JVM运行结束时才会进行 |
| boolean | mkdirs() | 创建File对象代表的目录,如果必要,会创建中间目录 |
示例2:
普通⽂件的创建
java
public class demo2 {
public static void main(String[] args) throws IOException {
File file = new File("keep-passion.txt");//要求该文件不存在,才能展示
System.out.println(file.exists());
System.out.println(file.isFile());
System.out.println(file.isDirectory());
System.out.println(file.createNewFile());
System.out.println(file.exists());
System.out.println(file.isDirectory());
System.out.println(file.isFile());
}
}

示例3:
普通⽂件的删除
java
public class demo3 {
public static void main(String[] args) throws IOException {
File file = new File("hello-world.txt");
System.out.println(file.exists());
System.out.println(file.createNewFile());
System.out.println(file.exists());
System.out.println(file.delete());
System.out.println(file.exists());
}
}

示例4
观察 deleteOnExit 的现象
java
public class demo4 {
public static void main(String[] args) throws IOException, InterruptedException {
File file = new File("./test.txt");
System.out.println(file.exists());
System.out.println(file.createNewFile());
//在进程退出时删除文件
file.deleteOnExit();
Thread.sleep(10000);
}
}
程序运⾏结束后,⽂件还是被删除了.
示例5:
观察⽬录的创建
java
public class demo6 {
public static void main1(String[] args) {
File file = new File("dir");//要求目录不存在,
System.out.println(file.isDirectory());
System.out.println(file.isFile());
System.out.println(file.mkdir());
System.out.println(file.isDirectory());
System.out.println(file.isFile());
}
}

示例6
观察目录的创建2
java
public class{
public static void main(String[] args) {
File file = new File("parent//dir");
System.out.println(file.isDirectory());
System.out.println(file.isFile());
System.out.println(file.mkdir());
System.out.println(file.isDirectory());
System.out.println(file.isFile());
}
}

mkdir() 的时候,如果中间⽬录不存在,则⽆法创建成功; mkdirs() 可以解决这个问题。
java
public class demo6 {
public static void main(String[] args) {
File file = new File("parent\\dir");//要求目录不存在,
System.out.println(file.isDirectory());
System.out.println(file.isFile());
System.out.println(file.mkdirs());
System.out.println(file.isDirectory());
System.out.println(file.isFile());
}
}

示例7:
返回file目录下的所有文件
java
public class demo5 {
public static void main(String[] args) {
//针对文件是无法进行list的
// File file = new File(".test.txt");
//必须要针对目录来进行list
File file = new File("d:/");
String[] list = file.list();
System.out.println(Arrays.toString(list));
File[] files = file.listFiles();
System.out.println(Arrays.toString(files));
}
}

2.文件内容的读写--数据流

InputStream常用的方法及构造方法
方法
|----------|------------------|------------------------------------------|
| 修饰及返回值类型 | 方法名 | 说明 |
| int | read() | 读取一个字节的数据,返回-1代表已经完全读完了 |
| int | read(byte[] b) | 最多读取b.length字节的数据到b中,返回实际读到的数量;-1代表以及读完了 |
| void | close() | 关闭字节流 |
InputStream只是一个抽象类,要使用还需要具体的实现类。关于InputStream的实现类有很多,基本可以认为不同的输入设备都可以对应一个InputStream类,我们现在只关心从文件中读取,所以使用FileInputStream。
InputStream代码展示
示例1
将文件完全读完的两种方式。相比较而言,后一种的IO次数更少,性能更好。
java
public class demo7 {
public static void main(String[] args) throws IOException {
//需要先在项目目录下准备好一个passion.txt的文件,里面填充"passion"的内容
try (InputStream inputStream = new FileInputStream("passion.txt")){
while (true){
int a = inputStream.read();
if(a== -1){
break;
}
System.out.printf("c",a);
}
}
}
}
//第二种形式
public class demo7 {
public static void main(String[] args) {
try (InputStream inputStream = new FileInputStream("passion.txt")){
byte[] buf = new byte[1024];
int len;
while (true){
len = inputStream.read(buf);
if(len== -1){
break;
}
for (int i = 0; i < len; i++) {
System.out.printf("c",len);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
示例2:
这⾥我们把⽂件内容中填充中⽂看看,注意,写中⽂的时候使⽤ UTF-8 编码。hello.txt 中填写 "保持热情"
注意:这⾥我利⽤了这⼏个中⽂的 UTF-8 编码后⻓度刚好是 3 个字节和⻓度不超过 1024 字节的现
状,但这种⽅式并不是通⽤的。
java
public class demo7 {
public static void main(String[] args) {
try(FileInputStream inputStream = new FileInputStream("passion.txt")){
byte[] buf = new byte[1024];
int len;
len = inputStream.read(buf);
while (true){
if(len==-1){
break;
}
for (int i = 0; i < len; i+=3) {
String s = new String(buf,i,3,"UTF-8");
System.out.printf("%s",s);
}
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
利用Scanner进行字符读取
上述例子中,我们看到了对字符类型直接使用InputStream进行读取是非常麻烦且困难的,所以,我们使用一种我们之前比较熟悉的类来完成该工作,就是Scanner类。
|---------------------------------------|----------------------|
| 构造方法 | 说明 |
| Scanner(InputStream i,String charset) | 使用charset字符集进行i的扫描读取 |
java
public class demo8 {
public static void main(String[] args) throws IOException {
try(FileInputStream inputStream = new FileInputStream("passion.txt")){
Scanner scanner = new Scanner(inputStream);
while (scanner.hasNext()){
String s = scanner.next();
System.out.println(s);
}
}
}
}
OutputStream的常用方法
|-----------|-----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 修饰符及返回值类型 | 方法名 | 说明 |
| void | write(int a) | 写入要给字节的数据 |
| void | write(byte[] a) | 将a这个字符数组中的数据全部写入o中 |
| int | write(byte[] a,int off,int len) | 将a这个字符数组中从off开始的数据写入o中,一共写len个 |
| void | close() | 关闭字节流 |
| void | flush() | 重要:我们知道I/O的熟读是很慢的,所以,大多的OutputStream为了减少设备操作的次数,在写数据的时候都会将数据先暂时写入内存的一个指定区域里,直到该区域满了或者其他指定条件时才真正将数据写入设备中,这个区域一般称为缓冲区。但造成一个结果,就是我们写的数据,很可能会遗留一部分在缓冲区,调用flush(刷新)操作,将数据刷到设备中。 |
说明:OutputStream同样只是一个抽象类,要使用还需要具体的实现类。我们现在还是只关心写入文件中,所以使用FileOutputStream。
代码示例:
示例1
java
public class demo9 {
public static void main(String[] args) throws IOException {
try(FileOutputStream outputStream = new FileOutputStream("out.txt")){
outputStream.write('p');
outputStream.write('a');
outputStream.write('s');
outputStream.write('s');
outputStream.write('i');
outputStream.write('o');
outputStream.write('n');
outputStream.flush();
}
}
}
java
public class demo9 {
public static void main(String[] args) throws IOException {
try(FileOutputStream outputStream = new FileOutputStream("out.txt")){
byte[] a = new byte[]{
'g','o','o','d'
};
outputStream.write(a,0,4);
outputStream.flush();
}
}
}
java
public class demo9 {
public static void main(String[] args) throws IOException {
try(FileOutputStream outputStream = new FileOutputStream("out.txt")){
String s = "great";
String str = "相信自己";
byte[] a = str.getBytes();
outputStream.write(a);
outputStream.flush();
}
}
}
使用PrintWriter
上述,其实我们已经完成输出工作,但总是有所不方便,我们接下来将OutputStream处理下,使用PrintWriter类来完成输出,因为Prin
tWriter类中为我们提供了我们所熟悉的print/println/printf方法
代码示例:
java
public class demo10 {
public static void main(String[] args) throws UnsupportedEncodingException, FileNotFoundException {
FileOutputStream outputStream = new FileOutputStream("passion.txt");
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream,"utf-8");
PrintWriter writer = new PrintWriter(outputStreamWriter);
writer.print("keep");
writer.println("保持热情");
writer.printf("%d: %s\n",9,"相信自己时最棒的");
writer.flush();
}
}
小程序练习
我们学会了文件的基本操作+文件内容读写操作,接下来,我们要运用我们所学的操作,来实现一些小工具程序,来锻炼我们的能力。
示例1
扫描指定目录,并找到名称中包含指定字符的所以普通文件(不包含目录),并且后续询问用户hi否要删除该文件。
java
public class demo11 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入你要搜索的目录:");
String rootDir = scanner.next();
File file = new File(rootDir);
if (!file.isDirectory()){
System.out.println("这不是目录");
return;
}
System.out.println("请输入你要删除的关键字");
String keyword = scanner.next();
scanDir(file,keyword);
}
private static void scanDir(File file, String keyword) {
//1.列出当前目录中包含的内容
File[] files = file.listFiles();
if (files ==null){
//当前目录为空
return;
}
//2.遍历当前目录中内容
for (File file1:files){
System.out.println("遍历目录&文件:"+file1.getAbsolutePath());
//判断当前文件是目录还是普通文件。
if (file1.isFile()){
//4.如果是普通文件,则判断文件名是否包含关键字
dealFile(file1,keyword);
}else {
//5.如果是目录,则递归调用本方法
scanDir(file1,keyword);
}
}
}
private static void dealFile(File file1, String keyword) {
if (file1.getName().contains(keyword)){
System.out.println("发现文件:"+file1.getAbsolutePath()+",包含关键字!,是否删除?(y/n)");
Scanner scanner = new Scanner(System.in);
String input = scanner.next();
if (input.equalsIgnoreCase("y")){
file1.delete();
System.out.println("文件已删除!");
}
}
}
}
示例2
普通文件的复制
java
public class demo12 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入源文件的路径");
String scrPath = scanner.next();
System.out.println("请输入目标文件路径:");
String destPath = scanner.next();
File srcFile = new File(scrPath);
if (!srcFile.isFile()){
System.out.println("源文件不存在或不是文件");
return;
}
File destFile = new File(destPath);
//要求destFile不一定存在。
//但是destFile所在的目录必须存在
//例如,destFile为d:/test/test.txt,则要求d:test目录存在
if (!destFile.getParentFile().isDirectory()){
System.out.println("目标文件所在目录不存在");
return;
}
//真正进行复杂文件的操作
//此处不应该使用追加写,需要确保目标文件和源文件一模一样
try(InputStream inputStream = new FileInputStream(srcFile);
OutputStream outputStream = new FileOutputStream(destFile)){
while (true){
byte[] cop = new byte[1024];
int ret = inputStream.read(cop);
if (ret == -1){
break;
}
//此处的write不应该写整个cop数组的
//cop数组不一定被填满,要按照实际的ret这个长度来写入
//读多少,写多少
outputStream.write(cop,0,ret);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
示例3
扫描指定⽬录,并找到名称或者内容中包含指定字符的所有普通⽂件(不包含⽬录)
注意:我们现在的⽅案性能较差,所以尽量不要在太复杂的⽬录下或者⼤⽂件下实验
java
package Review.IO;
import java.io.*;
import java.util.Scanner;
public class demo13 {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要搜索的路径:");
String rootPath = scanner.next();
File rootFile = new File(rootPath);
if (!rootFile.isDirectory()){
System.out.println("输入的路径不是目录");
return;
}
System.out.println("请输入要搜索的关键字:");
String keyword = scanner.next();
scanDir(rootFile,keyword);
}
private static void scanDir(File rootFile, String keyword) throws IOException {
//1.列出当前目录下所有的内容
File[] files = rootFile.listFiles();
if (files == null){
//当前目录为空,直接返回
return;
}
//2.遍历当前目录下所有的文件
for (File file : files){
if (file.isFile()){
//是普通文件
dealFile(file,keyword);
}else {
//是目录,递归调用
scanDir(file,keyword);
}
}
}
private static void dealFile(File file, String keyword) throws IOException {
//1.判定文件名是否包含关键字
if (file.getName().contains(keyword)){
//包含关键字,打印文件名
System.out.println("文件名包含关键字:"+file.getAbsolutePath());
return;
}
//2.判定文件内容是否包含,由于keyword是字符串,就按照字符流的范式来处理
StringBuilder stringBuilder = new StringBuilder();
try(Reader reader = new FileReader(file)){
while (true){
char[] chars = new char[1024];
int len = reader.read(chars);
if (len == -1){
break;
}
stringBuilder.append(chars,0,len);
}
}
//3.判定stringBuilder是否包含关键字
if (stringBuilder.indexOf(keyword)>= 0){
//包含关键字
System.out.println("文件内容包含关键字:"+file.getAbsolutePath());
}
return;
}
}
总结
本文围绕 Java I/O 文件操作展开,先介绍文件的基础概念(定义、组织方式、路径),再详细讲解File类的属性与方法(文件 / 目录的创建、删除、遍历等),接着阐述数据流(InputStream/OutputStream)的文件读写逻辑及优化方式,最后通过三个实用工具程序巩固知识点,覆盖文件操作的核心场景与实践技巧。