Java 文件操作和 IO

news2024/11/20 13:23:29

1. 认识文件

狭义上的文件(file):针对硬盘这种持久化存储的 IO 设备,当我们想要进行数据保存时,往往不是保存成一个整体,而是独立成一个个的单位进行保存,这种独立的单位就被抽象成文件的概念

文件除了有数据内容之外,还有一部分信息,例如:文件名、文件类型、文件大小等并不作为文件的数据而存在,称这部分信息为文件的元信息

2. 树形结构组织和目录

随着文件越来越多,出现了采用树形结构这种层级结构来组织文件的方法,这样专门用来存放管理信息的特殊文件称为文件夹(folder)或目录(dierctory)

3. 文件路径(Path)

在树形结构的角度来看,树中的每个节点都可以被 一条从根开始,直到节点 的路径所描述,这种描述方式被称为文件的绝对路径(absolute path)

除了可以从根开始进行路径的描述,还可以从任意节点出发,进行路径的描述,这种描述方式就称为相对路径(relative path),相当于当前所在节点的一条路径(表示当前目录,.. 表示当前目录的上一级目录)

tip:

普通文件根据其保存数据的不同,被分为不同的类型

文本文件:保存被字符集(UTF8 / GBK)编码的文本

二进制文件:按照标准格式保存的非被字符集编码过的文件(如:.exe  .dll  .mp3  .mp4  .class)

补充:

Windows 操作系统上,会按照文件名后的后缀来确定文件类型及该类型文件的默认打开程序,但这个习惯不是通用的,在 OSX、Unix、Linux 等操作系统上,一般不会对文件类型做如此精确的分类

文件由于被操作系统进行了管理,所以可以根据不同的用户,赋予其不同的对该文件的权限,一般有 可读、可写、可执行 权限

Windows 操作系统上,还有一类文件比较特殊,就是快捷方式(shortcut),这种文件只是对真实文件的一种引用,其他操作系统上也有类似的概念,如:软连接(soft link)

很多系统为了实现接口的统一性,将所有的 IO 设备都抽象成了文件的概念,使用这一理念最为知名的就是 Unix、Linux 操作系统——万物皆文件

4. Java中操作文件

Java 中通过 java.io.File 类来对一个文件(包括目录)进行抽象的描述

tip:有 File 对象,并不代表真实存在该文件

文件的操作分为两大类

1) 文件系统操作:创建文件、删除文件、创建目录、重命名文件、判定文件存在…

2) 文件内容:读文件、写文件(Java中提供了 File 类进行文件系统操作,这个对象会使用“路径”进行初始化,从而表示一个具体的文件(这个文件可以存在,也可以不存在),基于这个对象进行后续操作)

5. File 类(文件系统操作)

5.1 属性

修饰符及类型

属性

说明

static String

pathSeparator

依赖于系统的路径分隔符,String 类型的表示

static char

pathSeparator

依赖于系统的路径分隔符,char 类型的表示

5.2 构造方法

签名

说明

File(File parent, String child)

根据父目录 + 孩子文件路径,创建一个新的 File 实例

File(String pathname)

根据文件路径创建一个新的 File 实例,路径可以是绝对路径或者相对路径(此处写作相对路径的时候,需要明确基准目录是啥)

File(String parent, String child)

根据父目录 + 孩子文件路径,创建一个新的 File 实例,父目录用路径表示

tip:代码中写的相对路径的基准目录是什么取决于运行程序的方式

  1. 直接在 IDEA 中运行,此时基准路径就是该项目所在的目录
  2. 在命令行中,通过 Java 命令来运行,此时基准路径就是 Java 命令所在的目录
  3. 某个程序,可能是被其他进程调用的,进程1通过创建子进程的方式,运行进程2(虽然在 Java 中很少见,但是可以做到),进程2的基准路径就和进程1相同
  4. 代码执行过程中,还可以通过一些 api 修改基准路径,改成我们指定的某个路径

5.3 方法

修饰符及返回值类型

方法签名

说明

String

getParent()

返回 File 对象的父目录文件路径

String

getName()

返回 File 对象的纯文件名称

String

getPath()

返回 File 对象的文件路径

String

getAbsolutePath()

返回 File 对象的绝对路径

String

getCanonicalPath()

返回 File 对象修饰过的绝对路径

boolean

exists()

判断 File 对象描述的文件是否真实存在

boolean

isDirectory()

判断 File 对象代表的文件是否是一个目录

boolean

isFile()

判断 File 对象代表的文件是否是一个普通文件

boolean

createNewFile()

根据 File 对象,自动创建一个空文件,成功创建后返回 true

boolean

delete()

根据 File 对象,删除该文件,成功删除后返回 true

void

deleteOnExit()

根据 File 对象,标注文件被删除,删除动作会到 JVM 运行结束时才会进行

String[ ]

list()

返回 File 对象代表的目录下所有的文件名

File[ ]

listFiles()

返回 File 对象代表的目录下所有的文件,以 File 对象表示

boolean

mkdirs()

创建 File 对象代表的目录

boolean

mkdirs()

创建 File 对象代表的目录,如果必要,会创建中间目录

boolean

renameTo(File dest)

进行文件改名,也可以视为我们平时的剪切、粘贴操作

A.renameTo(B) 将 A 的名字改为 B

boolean

canRead()

判断用户是否对文件有可读权限

boolean

canWrite()

判断用户是否对文件有可写权限

示例:

1) 绝对路径:

import java.io.File;
import java.io.IOException;

public class Demo1 {
    public static void main(String[] args) throws IOException {
        File file = new File("D:\\haha\\test.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());//修饰过的绝对路径
    }
}


2) 相对路径:

import java.io.File;
import java.io.IOException;

public class Demo1 {
    public static void main(String[] args) throws IOException {
        //File file = new File("./test.txt");
        File file = new File("../test.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());//修饰过的绝对路径
    }
}


3) 创建文件:

import java.io.File;
import java.io.IOException;

public class Demo2 {
    public static void main(String[] args) throws IOException {
        File file = new File("./test.txt");

        boolean ok = file.createNewFile();//创建空文件夹
        System.out.println(ok);//判断是否创建成功

        System.out.println(file.exists());//判断文件是否存在
        System.out.println(file.isFile());//判断是否为普通文件
        System.out.println(file.isDirectory());//判断是否为目录
    }
}


4) 删除文件:

import java.io.File;

public class Demo3 {
    public static void main(String[] args) {
        File file = new File("./test.txt");
        boolean ok = file.delete();//删除该文件
        System.out.println(ok);
    }
}


5) 进程结束后删除文件:

import java.io.File;
import java.util.Scanner;

public class Demo4 {
    public static void main(String[] args) {
        File file = new File("./test.txt");
        file.deleteOnExit();//在进程退出的时候删除
        System.out.println("删除操作执行完毕");

        Scanner scanner = new Scanner(System.in);
        scanner.next();//用一个输入操作使进程不立即结束
    }
}

该方法存在的意义:用来构造“临时文件”


6) 返回 File 对象代表目录下所有文件名:

import java.io.File;
import java.util.Arrays;

public class Demo5 {
    public static void main(String[] args) {
        File file = new File("./src");
        //返回当前目录下所有文件名
        System.out.println(Arrays.toString(file.list()));
    }
}


7) 以 File 对象表示:

import java.io.File;
import java.util.Arrays;

public class Demo5 {
    public static void main(String[] args) {
        File file = new File("./src");
        //返回当前目录下所有文件名
        System.out.println(Arrays.toString(file.listFiles()));
    }
}

tip:直接使用 list / listFile 只能看到当前目录中的内容,要想看到某个目录下所有的目录和文件,就需要递归完成


8) 递归打印当前目录下所有目录和文件:

import java.io.File;

public class Demo6 {
    public static void main(String[] args) {
        File f = new File("./");

        scan(f);
    }

    private static void scan(File currentDir) {
        //1.先判断是否是目录
        if (!currentDir.isDirectory()) {
            return;
        }

        //2.列出当前目录中包含的内容
        File[] files = currentDir.listFiles();
        //当前数组为空或者数组不为空,但里面没有元素
        if(files == null || files.length == 0) {
            //不存在的路径 / 空目录
            return;
        }

        //3.打印当前目录
        System.out.println(currentDir.getAbsolutePath());

        //4.遍历当前目录所有内容,依次进行判定
        for (File f : files) {
            if(f.isFile()) {
                //如果是普通文件,直接打印文件路径
                System.out.println(f.getAbsolutePath());
            } else {
                //如果是目录,就继续递归
                scan(f);
            }
        }
    }
}

9) 创建目录

import java.io.File;

public class Demo7 {
    public static void main(String[] args) {
        File file = new File("./abc/def/ghi/jkl");
        boolean ok = file.mkdirs();
        System.out.println(ok);
    }
}


10) 移动文件

也就是修改文件所在的路径,文件路径的修改,也可以视为一种“重命名”

import java.io.File;

public class Demo8 {
    public static void main(String[] args) {
        File srcFile = new File("./abc");
        File destFile = new File("./abc1234");
        boolean ok = srcFile.renameTo(destFile);
        System.out.println(ok);
    }
}

 

6. 数据流(文件内容的读写)

文件内容操作就是读文件和写文件,操作系统提供了 API,Java 对其进行了封装,将这些封装的类称为“文件流” / “ IO 流”

Java 实现 IO 流的类有很多,主要分为两个大类:

  • 字节流(二进制):读写数据的基本单位就是字节(InputStream、OutputStream)
  • 字符流(文本):读写数据的基本单位就是字符,字符流内部做的工作会更多一些,会自动查询码表,把二进制数据转换成对应的字符(Reader、Writer)

上面四个类都是“抽象类”,真正干活的并不是他们,在 Java 中提供了很多类来实现上述四个抽象类,虽然类的种类很多,但是用法都是差不多的

tip:数据远离 cpu 就是输出,数据靠近 cpu 就是输入

但凡类的名字以 Reader、Writer 结尾的,就是实现了 Reader 和 Writer 的字符流对象

但凡类的名字以 InputStream、OutputStream 结尾的,就是实现了 InputStream 和 OutputStream 的字节流对象

6.1 InputStream 概述

方法

返回值类型方法名说明
intread()读取一个字节的数据,返回 -1 代表已经读完了
intread(byte [ ] b)最多读取 b.length 字节的数据到 b 中,返回实际读到的数量,-1 代表已经读完了
intread(byte[ ] b, int off, int len)最多读取 len - off 字节的数据到 b 中,放在从 off 开始,返回实际读到的数量,-1 代表已经读完了
voidclose()关闭字节流

说明

InputStream 只是一个抽象类,要使用还需要具体的实现类,关于 InputStream 的实现类有很多,基本可以认为不同的输入设备都可以对应一个 InputStream 类,我们现在只关心从文件中读取,所以使用 FileInputStream

FileInputStream 概述

构造方法

方法名说明
FileInputStream(File file)利用 File 构造输入流
FileInoutStream(String name)利用文件路径构造文件输入流

示例:

完全读取文件

第一步:打开文件

//该代码只是打开文件和关闭文件,没有读取
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class Demo9 {
    public static void main(String[] args) {
        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream("./test.txt");//打开文件
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                inputStream.close();//关闭文件
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

隐含打开操作


有打开就必然有关闭

打开文件,其实是在 该进程文件描述符表 中创建了一个新的表项

  • 进程:PCB(进程控制块)
  • 文件描述符表:描述了该进程都要操作哪些文件,可以认为其是一个数组,数组的每个元素就是一个 struct file 对象(Linux 内核),每个结构体都描述了对应操作的文件的信息,数组的下标就称为“文件描述符”
文件资源泄露问题

而每次打开一个文件,就相当于在数组上占用了一个位置,在系统内核中,文件描述符表数组是固定长度 & 不可扩容的,除非主动调用 close 关闭文件,否则就会使这里的资源越来越少,若数组满了,后续再打开文件就会失败

上述问题就称为“文件资源泄露”,该问题隐蔽性很高,在编写相关代码时,一定要注意关闭操作

在本例中,虽然要求使用完毕后要关闭,但是本代码不写 close 也行,因为 close 后面进程就结束了

close 就是释放文件描述符表里的元素,这里进程结束,意味着整个 pcb 销毁了,pcb 上的文件描述符表整个释放了


关闭文件常用写法:

本例中,将关闭操作放在 finally 中保证其能运行,虽然很严谨,但是比较麻烦,下面一种简单 & 可靠的方法:

        try (InputStream inputStream = new FileInputStream("./test.txt")) {

        } catch (IOException e) {
            e.printStackTrace();
        }

前提:必须是实现了 Closeable 接口的类才能放到 try() 里面

这种写法,既不必写 finally,也不必写 close 了

try with resources 这里的()中创建的资源(可以是多个),try 的 { } 执行完毕后,会自动执行这里的 close


第二步:读取文件 read()

该方法调用一次,读一个字节,返回值就是这个字节的内容(byte)

这里之所以返回值是 int 的原因是因为:byte => 8 比特位,表示的数据范围(不考虑符号位):0 -> 255

因为 read 读取到流的末尾发现没有字节可用的话,就会返回 -1,byte 表达不了,只能用 int(short不做考虑)

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class Demo10 {
    public static void main(String[] args) {
        try (InputStream inputStream = new FileInputStream("./test.txt")) {
            while (true) {
                int b = inputStream.read();
                if (b == -1) {
                    //读取完了
                    break;
                }
                //表示字节,使用十六进制打印显示
                System.out.printf("0x%x\n", b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

读取字符:hello

读取字符:你好

上述是 read 的无参版本,一次只读一个字节,这样的效率是很低的,频繁读取多次硬盘,硬盘的 IO 是耗时比较大的,希望能减少 IO 的次数


使用到 read(byte [ ] b)

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class Demo11 {
    public static void main(String[] args) {
        try (InputStream inputStream = new FileInputStream("./test.txt")) {
            while (true) {
                byte[] buffer = new byte[1024];
                int n = inputStream.read(buffer);
                if(n == -1) {
                    break;
                }
                for (int i = 0; i < n; i++) {
                    System.out.printf("0x%x\n", buffer[i]);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

此处把 buffer 形参当成了“输出型参数”,使用参数来表示输出的结果(“输入性参数”使用返回值表示输出结果)

这个操作会把硬盘中读到的对应数据填充到 buffer 内存的字节数组中,一次 IO 尽可能填满 buffer 数组,这样虽然一次读取的内容变多了,但是比一次读取一个,分很多次读,效率高很多

tip:while 循环在进行第二次循环时,由于已无字节可读,n 被赋值为 -1,break 循环结束


使用 read(byte[ ] b, int off, int len)

这个版本类似于上面的,也是把数据往字节数组 b 里填充,但不是使用整个数组了,而是使用数组中 [off, off + len] 范围的区间(offset 是“偏移量”)

6.2 OutputStream 概述

方法

返回值类型方法名说明
voidwrite(int b)写入要给字节的数据
voidwrite(byte[ ] b)将 b 这个字符数组中的数据全部写入到 os 中
intwrite(byte[ ] b, int off, int len)将 b 这个字符数组中从 off 开始的数据写入 os 中,一共写 len 个
voidclose()关闭字节流
voidflush()刷新操作,将数据刷到设备中

tip:IO 的速度是很慢的,所以大多的 OutputStream 为了减少设备操作的次数,在写数据时都会将数据先暂时写入内存的一个指定区域内,直到该区域满了或其他指定条件时才真正将数据写入设备中,这个区域一般称为缓冲区,但这样做造成一个结果:写的数据,可能会遗留一部分在缓冲区中,需要在最后或者合适的位置,调用 flush(刷新)操作,将数据刷到设备中

说明:OutputStream 同样只是⼀个抽象类,要使⽤还需要具体的实现类。我们现在还是只关⼼写⼊⽂件中,所以使⽤ FileOutputStream

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class Demo12 {
    public static void main(String[] args) {
        try (OutputStream outputStream = new FileOutputStream("./test.txt")) {
            outputStream.write(97);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

可以看到 test 文件中被写入了小写的 a,下面再写入字符“你好”的十六进制码

发现写操作会将之前的内容清空,再进行写操作,只要使用 OutputStream 打开文件,内容就无了


追加写操作

OutputStream 默认是写入之前会清空原来的内容,还有一个操作“追加写”,保持原内容不变,在末尾继续写入新内容

开启追加写模式,并以一次多字节方式写入:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class Demo12 {
    public static void main(String[] args) {
        try (OutputStream outputStream = new FileOutputStream("./test.txt",true)) {
            byte[] buffer = new byte[] { (byte)0xe4, (byte)0xbd, (byte)0xa0, (byte)0xe5, (byte)0xa5, (byte)0xbd };
            outputStream.write(buffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

当利用 write(byte[ ] b, int off, int len) 进行字符输入时,一定要确保在 utf8 中能查找到,否则就会出现乱码:


字符流

InputStream / OutputStream 读写数据是按照字节来操作的,如果要读写字符(中文)的话,此时就需要我们手动来区分哪几个字节是一个字符,再确保把这几个字节作为整体来写入,这样太麻烦,为了方便处理字符,引入了字符流

示例:读取 test 文件中的每一个字符

import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class Demo13 {
    public static void main(String[] args) {
        try (Reader reader = new FileReader("./test.txt")) {
            while (true) {
                int c = reader.read();
                if(c == -1) {
                    return;
                }
                char ch = (char)c;
                System.out.println(ch);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

tip:在上面读取字符“你好”的时候可以发现,其是按照 utf8 编码来存储的,每个汉字是 3 个字节

而在 Java 中的 char 是 2 个字节,这是因为当使用 char 表示这里的汉字时,不再使用 utf8 而是使用 unicode,在unicode 中,一个汉字就是 2 个字节,如下图

使用字符流读取数据的过程中,Java 标准库内部就自动针对数据进行编码进行转码了


字符数组方式代码:

import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class Demo14 {
    public static void main(String[] args) {
        try (Reader reader = new FileReader("./test.txt")) {
            char[] buffer = new char[1024];
            int n = reader.read(buffer);
            System.out.println(n);
            for (int i = 0; i < n; i++) {
                System.out.println(buffer[i]);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果:


写入操作 Writer

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class Demo15 {
    public static void main(String[] args) {
        try (Writer writer = new FileWriter("./test.txt")) {
            writer.write("你好世界");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

也是通过传入参数 true 来设置为追加写


利用 Scanner 进行字符读取

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;

public class Demo16 {
    public static void main(String[] args) {
        try (InputStream inputStream = new FileInputStream("./test.txt")) {
            Scanner scanner = new Scanner(inputStream);

            while (scanner.hasNextInt()) {
                System.out.println(scanner.nextInt());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

7. 实际应用:

示例 1:扫描指定目录,找到名称中包含指定字符的所有普通文件(不含目录),并询问用户是否要删除该文件

import java.io.File;
import java.util.Scanner;

public class Demo17 {
    public static void scan(File currentFile, String key) {
        if(!currentFile.isDirectory()) {
            return;
        }
        File[] files = currentFile.listFiles();
        if(files == null || files.length == 0) {
            return;
        }

        for (File f : files) {
            if(f.isFile()) {
                //针对普通文件进行处理
                //判断文件名是否符合要求并提示用户删除
                doDelete(f, key);
            } else {
                //针对目录进行处理
                //继续递归
                scan(f, key);
            }
        }
    }

    private static void doDelete(File f, String key) {
        if(!f.getName().contains(key)) {
            //文件名中不包含指定关键字
            return;
        }
        //提示用户是否确认删除
        Scanner scanner = new Scanner(System.in);
        System.out.println(f.getAbsolutePath() + " 是否确认删除 Y / N");
        String choice = scanner.next();
        if(choice.equals("Y") || choice.equals("y")) {
            f.delete();
        }
    }

    public static void main(String[] args) {
        System.out.println("请输入要搜索的路径:");
        Scanner scanner = new Scanner(System.in);
        String rootPath = scanner.next();
        File rootFile = new File(rootPath);
        if(!rootFile.isDirectory()) {
            System.out.println("输入路径不存在!");
            return;
        }
        System.out.println("请输入要删除文件名的关键字:");
        String key = scanner.next();

        //进行递归查找
        scan(rootFile, key);
    }
}

示例2:进行普通文件的复制

import java.io.*;
import java.util.Scanner;

public class Demo18 {
    public static void main(String[] args) {
        //1.输入路径并作校验
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入源文件的路径:");
        String srcPath = scanner.next();
        File srcFile = new File(srcPath);
        if (!srcFile.isFile()) {
            System.out.println("源文件路径有误!");
            return;
        }
        System.out.println("请输入目标文件的路径:");
        String destPath = scanner.next();
        File destFile = new File(destPath);
        if (!destFile.getParentFile().isDirectory()) {
            System.out.println("目标文件路径有误!");
            return;
        }

        //2.执行复制过程
        try (InputStream inputStream = new FileInputStream(srcFile);
             OutputStream outputStream = new FileOutputStream(destFile)) {
            while (true) {
                byte[] buffer = new byte[1024];
                int n = inputStream.read(buffer);
                System.out.println("n = " + n);
                if(n == -1) {
                    break;
                }
                //将 buffer 写入 outputStream 中
                outputStream.write(buffer, 0, n);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

tip:

示例3:扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Scanner;

public class Demo19 {
    public static void main(String[] args) {
        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 key = scanner.next();

        //进行递归
        scan(rootFile, key);
    }
    public static void scan(File rootFile, String key) {
        if(!rootFile.isDirectory()) {
            return;
        }
        File[] files = rootFile.listFiles();
        if (files == null || files.length == 0) {
            return;
        }
        for (File f : files) {
            if (f.isFile()) {
                //若是文件,进行后续操作
                doSearch(f, key);
            } else {
                //递归
                scan(f, key);
            }
        }
    }
    public static void doSearch(File f, String key) {
        //打开文件,读取文件内容,判断文件内容是否包含 key
        StringBuilder stringBuilder = new StringBuilder();

        try (Reader reader = new FileReader(f)) {
            char[] buffer = new char[1024];
            while (true) {
                int n = reader.read(buffer);
                if(n == -1) {
                    break;
                }
                String s = new String(buffer, 0, n);
                stringBuilder.append(s);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        if(stringBuilder.indexOf(key) == -1) {
            //未找到
            return;
        }
        //找到了
        System.out.println("找到匹配的文件:" + f.getAbsolutePath());
    }
}

tip:这里的代码逻辑是非常低的,每次查询都会涉及到大量的硬盘 IO 操作

这种思路不适合频繁查询的场景,也不适合目录中文件数目特别多的场景

搜索引擎就是这种基于内容的查询,但是它不是通过上述“遍历文件”的方式实现的,而是引入了一个数据结构:“倒排索引”

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1978962.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024第五届华数杯数学建模竞赛C题思路+代码

目录 原题背景背景分析 问题一原题思路Step1:数据读取与处理Step2:计算最高评分&#xff08;Best Score, BS&#xff09;Step3:统计各城市的最高评分&#xff08;BS&#xff09;景点数量 程序读取数据数据预处理 问题二原题思路Step1: 定义评价指标Step2: 收集数据Step3: 标准化…

【linux深入剖析】初识线程---线程概念

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 1. Linux线程概念什么是线…

RK3568笔记五十二:HC-SR04超声波模块驱动测试

若该文为原创文章&#xff0c;转载请注明原文出处。 一、HC-SR04简介 HC-SR04超声波模块是一种常用于距离测量和障碍物检测的模块。它通过发射超声波信号并接收回波来计算所测量物体与传感器之间的距离。 1、基本原理 TRIG引脚负责发送超声波脉冲串。此引脚应设置为高电平10…

4.指令系统

4.指令系统 指令系统概述—复习指导 一个好的框架是学习掌握知识的捷径&#xff0c;能够帮助我们更快的掌握所学知识&#xff0c;下面从王道计算机组成原理书本第四章-指令系统出发&#xff0c;讲解一下第四章的知识框架组成&#xff0c;下面是指令系统这一章节的思维导图和详…

【doghead】h264测试文件、读取、模拟采集时间戳及packtizer

使用原生代码但是原生仓库没有264文件使用绝对路径但是在wsl2的ubuntu22.04中构建添加264文件路径 clion IDE : 来自RtspServer 的测试264文件 PHZ76 提供的264文件读取264成功:按照帧读取 这里是模拟采集视频帧? 定时器:

【Rust练习】7.引用与借用

练习题来自&#xff1a;https://practice-zh.course.rs/ownership/borrowing.html 1 fn main() {let x 5;// 填写空白处let p __;println!("x 的内存地址是 {:p}", p); // output: 0x16fa3ac84 }其实Rust的借用&#xff0c;就类似C的指针和引用&#xff0c;如果你…

面对挫折和恶语 良好心态非常重要

你等年纪轻轻一遇挫折,便松散懈怠,日后怎成大器? 虽称满腹经纶,却是鸡肠鼠肚,连几句恶语都容它不下,你等要记住,为人者,有大度成大器也!夫处世之道,即应变之术, 岂可偏执一端? 【迷茫时&#xff0c;不妨听听司马仲达的人生格言】https://www.bilibili.com/video/BV1JF411i7…

React--》掌握styled-components重塑React样式管理

想象一下&#xff0c;如果你的React组件不仅能自描述其逻辑&#xff0c;还能直接声明自己的样式&#xff0c;这种“所见即所得”的编程体验是不是让人心动不已&#xff1f;styled-components正是这样一把钥匙&#xff0c;它彻底颠覆了我们对React样式管理的传统认知&#xff0c…

Python 中单例模式实现的几种方式

在设计模式中&#xff0c;单例模式是经常被提及和使用的一种模式。它保证一个类只有一个实例&#xff0c;并提供全局访问点。在Python中&#xff0c;有多种实现单例模式的方法。那么&#xff0c;如何选择合适的方法来实现单例模式呢&#xff1f; 单例模式在Python中的几种实现方…

软件设计师笔记-网络基础知识

计算机网络的发展 计算机网络&#xff08;计算机技术通信技术&#xff09;的发展是一个逐步演进的过程&#xff0c;从简单的具有通信功能的单机系统&#xff0c;到复杂的以局域网及因特网为支撑环境的分布式计算机系统&#xff0c;这一历程经历了多个关键阶段&#xff1a; #me…

大模型是否潜在地进行多跳推理?

人工智能咨询培训老师叶梓 转载标明出处 以往的研究表明&#xff0c;基于Transformer的LLMs能够在参数中存储和检索事实信息&#xff0c;以完成简单提示&#xff0c;例如“Stevie Wonder的母亲是谁”。此外&#xff0c;当必要信息明确给出时&#xff0c;LLMs表现出了显著的上下…

JS优化了4个自定义倒计时

<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><title>4个自定义倒计时</title><style>* {margin: 0;padding: 0;box-sizing: border-box;user-select: none;body {background: #0b1b2c;}}hea…

(javaweb)maven--Java项目的构建工具

目录 一.Maven概述 二.Idea导入maven项目 三.maven核心功能--依赖管理 四.依赖管理--依赖传递 五.依赖管理--依赖范围 六.依赖管理--生命周期 一.Maven概述 1.管理和构建Java项目的工具 2.Apache开源&#xff08;源代码开放&#xff09; 3. 4. Maven核心配置文件&#xff…

解题 - 左旋字符串的三种解法(思路)

文章目录 前言&#xff1a; 题干&#xff1a; 解题思路&#xff1a; 思路一&#xff1a; 思路二&#xff1a; 思路三&#xff1a; 小结 前言&#xff1a; 路漫漫其修远兮&#xff0c;吾将上下而求索。 题干&#xff1a; 解题思路&#xff1a; 创建变量k &#xff0c;用…

【知识专栏丨python数分实战】关于电商零售客户细分数据分析及可视化

今天这篇文章将给大家介绍关于电商零售客户细分数据分析及可视化的案例分析。 01 数据整理 导入数据 import pandas as pdimport numpy as npfrom pyecharts.charts import *import pyecharts.options as opts import warningswarnings.filterwarnings(ignore) 数据读取及预…

shell的基础介绍

文章目录 shell数组读取数组关联数组获取数组的长度 Shell运算符算术运算符关系运算符布尔运算符逻辑运算符字符串运算符文件测试运算符 Shell echo命令1.显示普通字符串2.显示转义字符3.显示变量4.显示换行5.显示不换行6.显示结果定向至文件7.原样输出字符串&#xff0c;不进行…

Macbook上运行Windows系统工具Parallels Desktop19

亲爱的笔记本小能手们&#xff0c;你们是否也有这样的困扰&#xff1a;在Macbook上运行Windows系统&#xff0c;却发现虚拟机软件要么卡顿&#xff0c;要么操作复杂&#xff1f;别急&#xff0c;今天就给你们种草一款神器——Parallels Desktop 19。 Parallels Desktop最新绿色…

无人机工程师技术高级证书详解

随着无人机技术的飞速发展&#xff0c;其在航拍、农业、测绘、救援、物流等多个领域的应用日益广泛&#xff0c;对无人机工程师的专业技能与综合素质提出了更高要求。无人机工程师技术高级证书&#xff0c;作为对无人机领域高级工程师专业技能的权威认证&#xff0c;不仅是对个…

QT网络编程

Qt 给用户提供了网络编程的接口&#xff0c;包括TCP、UDP、HTTP三种协议的API以及各种类&#xff0c;可以了解一下。 而在 QT 中想要使用网络编程&#xff0c;必须在pro文件中添加 network 模块&#xff0c;否则无法包含网络编程所需的头文件。 UDP UDP是传输层的协议&#…