【JavaEE】文件操作和IO-目录扫描全文检索小程序

简介: JavaEE & 文件操作和IO

JavaEE & 文件操作和IO

在之前的学习中,基本上都是围绕内存展开的~


MySQL 主要是操作硬盘的


文件IO也是是操作硬盘的~


IO : input output


1. 文件系统操作

创造文件,删除文件,重命名文件,创建目录······

一些操作没有权限也做不了~

1.1 路径

就是我们的文件系统上的一个文件/目录的具体位置

目录:文件夹

计算机的目录是有层级结果的,即N叉树


5656d8ee8d4c4bbb9596dc8fbea8e158.png

我的代码库目录:

d50f78a9cfdd4b16b9326ca42f501247.png


那么这篇文章的源码所在的目录具体位置是什么呢?


2e668d88333a45ab9681504a0fb00fb5.png

所以,路径就是:D:/马库/marathon-april-2023/文件IO

这是个绝对路径

绝对路径,即从盘符开始到具体文件/目录

相对路径,从指定目录开始到具体文件/目录

要确认**(基准)工作目录**是什么~

而里面的src目录下有java文件,out目录里面就有class文件,同样有对应的路径


/ 分割,推荐!

\ 分割的话要加转义字符\ , 即 \\

一般只能适用于Windows

…/ 代表这一级的上一个目录

. 代表这当前目录(与后续目录要以 / 分割,即 . / )通常可以省略

. . 代表当前目录的上一级目录(与后续目录要以 / 分割,即 . . / )

. . / . . / 代表上一级目录的上一级目录~

默认源头的源头是“此电脑”目录,可以不写

绝对路径可以认为是以“此电脑”为工作目录的相对路径


并且任何一个文件/目录,对应的路径,肯定是唯一的


在LInux可能出现两个不同路径找到同一文件的情况~

但是在Windows上不存在~

路径与文件一一对应~


路径为文件的身份


1.2 文本文件 与 二进制文件

这个就是字面意思了

文本文件:


存储字符(不仅仅是char类型)

二进制文件:


存储什么都OK,因为任何数据都是以二进制为根本的

判断:


记事本打开,是文本就是文本,是二进制就是二进制~

.txt文件:文本 / 二进制,看你怎么创造的~

de5a1bdd726644e3831259f041011368.png


.java / .c 文件 : 文本文件


拖动到记事本里

65687cef6f4f40c980b055c13283b2c4.png

.class / .exe 文件: 二进制文件~


bc5c53455ddb4e3fa8f4b0b8d97a05ad.png


.jpg / mp3 二进制文件

乱码,即把这一个个字节转化为字符型,而原本不是字符型的~

7d393a34b2a94df38a3582daa0596833.png


pdf xlsx doc … : 二进制文件


csv excel的文本格式:

a1a266cdf4c44d15a7d9ab5eb6f6cdc0.png


1.3 文件系统操作

Java标准库提供了一个类:File

File对象代表着一个文件,是那个文件的抽象表示~

硬盘上的文件 ==> 内存中的File对象 ==> 在内存上改变硬盘上的一些东西

1.3.1 构造File对象

需要传一个文件路径为参数~

这个文件可以存在也可以不存在

例如这张图片~

6071a680e9fc44e38cc4f28f762a6adb.png



9a3cb358b99b48c8858ea852e279a6e7.png


1.3.2 使用File对象

不手动常见文件是不会自动创建的

不会再new的时候创建

序号 方法名 方法说明

1 String getParent() 返回 File 对象的父目录文件路径

2 String getName() 返回 FIle 对象的纯文件名称

3 String getPath() 返回 File 对象的文件路径

4 String getAbsolutePath() 返回 File 对象的绝对路径

5 String getCanonicalPath() 返回 File 对象的修饰过的绝对路径

6 boolean exists() 判断 File 对象描述的文件是否真实存在

7 boolean isDirectory() 判断 File 对象代表的文件是否是一个目录

8 boolean isFile() 判断 File 对象代表的文件是否是一个普通文件

9 boolean createNewFile() 根据 File 对象,自动创建一个空文件。成功创建后返 回 true

10 boolean delete() 根据 File 对象,删除该文件。成功删除后返回 true

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

12 String[] list() 返回 File 对象代表的目录下的所有文件名

13 File[] listFiles() 返回 File 对象代表的目录下的所有文件,以 File 对象表示

14 boolean mkdir() 创建 File 对象代表的目录

15 boolean mkdirs() 创建 File 对象代表的目录,如果必要,会创建中间目录

16 boolean renameTo(File dest) 进行文件改名,也可以视为我们平时的剪切、粘贴操作

17 boolean canRead() 判断用户是否对文件有可读权限

18 boolean canWrite() 判断用户是否对文件有可写权限

小小演示:


绝对路径

public static void main(String[] args) throws IOException {
    File file = new File("d:/马图/瞪眼.jpg");
    System.out.println(file.getParent());
    System.out.println(file.getName());
    System.out.println(file.getPath());
    System.out.println(file.getAbsoluteFile());
    System.out.println(file.getCanonicalFile());
}


IOException是IO操作的会抛出的常见异常

是首查异常,也叫编译时异常

f06f4cf38b0e4b639bdea188ae0ab211.png


相对路径

默认是与src目录的上一级的为工作目录~

就是项目所在目录

而不是src这一级

cdc7b11b58624c3c9a1af1cc36975f47.png


打印得出来这个文件不代表就存在这个文件~

是否存在?

029b1584dfa948d3835f4c2ba5ca97b0.png


性质,创造文件,删除文件

是存在既不是目录又不是普通文件的文件的

例如socket文件等等~

public static void main(String[] args) {
    File file = new File("./helloWorld.txt");
    System.out.println(file.exists());
    System.out.println(file.isDirectory());//是目录吗?(文件夹)
    System.out.println(file.isFile());//是文件吗?(普通文件)
}

6654c6d367024e02a79cd46101d8e7b1.png



创建与删除目录

public static void main(String[] args) {
    File file = new File("./helloWorld");
    if(!file.exists()) {
        file.mkdir();
    }
    System.out.println(file.exists());
    System.out.println(file.isFile());
    System.out.println(file.isDirectory());
}


make directory

228dbe43ed4c48c1836eba9099cd0b1c.png



路径转化为数组

获取目录里的所有文件/目录


list ==> 文件 /目录名数组
listFile ==> File对象 数组
public static void main(String[] args) {
    File file = new File("helloWorld");
    String[] results1 = file.list();
    File[] results2 = file.listFiles();
    System.out.println(Arrays.toString(results1));
    System.out.println(Arrays.toString(results2));
}

01e5e7effb9942c9a387bb9bfc8183a9.png



重命名

public static void main(String[] args) {
    File file = new File("helloWorld");
    file.renameTo(new File("HELLO_WORLD"));
}


6893f45ab661464f9718558d577ff6f0.png


2. 文件内容操作

针对文件内容进行 读 与 写


文件操作依赖于一些类,或者说是多组类


文本 ==> ”字符流“

二进制 ==> “字节流”

“流”:


数据的运输像河流一样,流向哪,从哪流来~


850ef4516b55329a718d4b5dad815905.jpg

2.1 获取文件输入流InputStream(字节流)

public static void main(String[] args) throws IOException {
    InputStream inputStream = new FileInputStream("HELLO_WORLD");
    //coding
    inputStream.close();
}

有了输入流,就相当于你有了“介质”


相当于打开文件,文件的信息可以出来

关闭输入流


相当于关闭文件

如果不关闭,可能会导致,文件资源泄露 ===>

进程里有个文件描述符表,一旦打开文件多了,这个表可能会爆了,导致机器出问题!

Java的对象,没用了会自动释放,但是这里的流对象并不会!!!

正确的写法:(利用finally保证关闭能够进行)


try括号内为打开文件操作,默认finally关闭文件~

try with resources操作

public static void main(String[] args) {
    try(InputStream inputStream = new FileInputStream("HELLO_WORLD/123.txt")) {
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

3a3226474341448f96266e33e28015cc.pngInputStream实现Closeable接口,那么就可以这样操作~

这就是try with resource操作的要求

注意:后续内容都是以这种方式打开与隐式关闭文件的




2.1.1 read方法

只能说文件里有个指针指着读在哪了,并不是读了后原文件就删了~

方法名 方法说明

int read() 一次读一个字节并返回,返回-1代表读完了

int read(byte[] b) 填满此数组为止,返回-1表示读完(可能填不满)

int read(byte[] b, int off, int len) 填满此数组的[off, off + len)为止,返回-1表示读完(可能填不满)

在java对此方法的描述中提到:返回的字节转化为int类型,范围是0 - 255

手写一些数据:


f4c4fb86f65d4ee5832c2d758f3505ca.png


2.1.2 不带参数的read方法


public static void main(String[] args) {
    try(InputStream inputStream = new FileInputStream("HELLO_WORLD/123.txt")) {
        int b = 0;
        do {
            b = inputStream.read();
            System.out.println(b);
        }while(b != -1);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

测速结果:


c3d57616ddd1480a9e5aa4a12096360e.png

对于中文:


089c8a5837f94b7f8108d9011b8ab3c9.png

测试结果:

不是说一个中文 ==> 一个char字符 两个字节吗,为什么这里是三个字节一个汉字

那是因为Unicode每个字符是两个字节

UTF-8汉字是三个字节,其他字符一个字节~

我编译器无脑全设置UTF-8了

而读取的内容可没有规定就是Java的char类型呀~

但是我们可以通过一些手段翻译这个东西,后面讲~

163d0b5ce38b44e48394bb5d39a45d40.png


字符对应表 - 查询网站:查看字符编码(UTF-8) (mytju.com)

666d8424286e4e0daa626ae3757b9896.png


E9 : 233 A9 : 169 AC : 172 ················


完美对应~


2.1.3 给定数组的read方法


public static void main(String[] args) {
    try(InputStream inputStream = new FileInputStream("HELLO_WORLD/123.txt")) {
        byte[] bytes = new byte[9];
        System.out.println(inputStream.read(bytes));
        System.out.println(Arrays.toString(bytes));
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

测试结果:


b01bf1a515434ea3ae02e04d810b2e3b.png

咋变负数了?


因为读取到的字节仍然是 -128 - 127 的

只不过刚才返回int类型的是无符号的~

如何翻译呢?


用String的构造方法~

04c5e29b5b5b49ee9834e6665522bbb7.png


2.2 获取文件输出流OutputStream(字节流)


public static void main(String[] args) {
    //每次打开输出流,都会清空文件内容~
    try(OutputStream outputStream = new FileOutputStream("HELLO_WORLD/123.txt")) {
    } catch (IOException e) {
        e.printStackTrace();
    }
}


每次打开文件,会清空原内容!

714496028d5b4919b8fd4743ff353db4.png

2.2.1 write方法

方法名 方法说明

void write(int b) 传入一个int型,内部强行转化为byte型

void write(byte[] b) 将整个字节数组写入文件中

int write(byte[] b, int off, int len) 将字节数组的[off, off + len)部分写入文件中

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

flush很重要,在关闭之前没有flush,文件内容就无法得以更新

2.2.2 write 传入单个字节的构造方法

public static void main(String[] args) {
    //每次打开输出流,都会清空文件内容~
    try(OutputStream outputStream = new FileOutputStream("HELLO_WORLD/123.txt")) {
        outputStream.write(1);
        outputStream.write(2);
        outputStream.write(3);
        outputStream.write(4);
        outputStream.flush();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}


d397c906cff346efb4879555453cf0c4.png



2.2.3 write 传入字节数组的构造方法

public static void main(String[] args) {
    //每次打开输出流,都会清空文件内容~
    try(OutputStream outputStream = new FileOutputStream("HELLO_WORLD/123.txt")) {
        outputStream.write(new byte[]{1, 2, 3, 4, 5, 6, 7});
        outputStream.flush();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}


33ece4268f7f41cfa261bb4957c6f641.png

2.3 字符流 Reader 与 Writer

2.3.1 Reader的读方法

对比于字节流,这里读的是字符,读进字符数组~

public static void main(String[] args) throws FileNotFoundException {
        try(Reader reader = new FileReader("HELLO_WORLD/123.txt")) {
            char ch = (char)reader.read();
            char[] chars = new char[7];
            reader.read(chars);
            System.out.println(ch);
            System.out.println(chars);
            int c = 0;
            do {
                c = reader.read();
                System.out.println((char)c);
            }while (c != -1);
        } catch (IOException e) {
            e.printStackTrace();
        }
1



测试结果:

同样read返回-1,代表读完了~


69a6a685bf1b4b4098a1461ebc0a2839.png

2.3.2 Writer的写操作

对比于字节流,这里写入的是字符,字符数组,或者字符串~

efc0bbc2dc654928a8a8243710e1bdee.png



public static void main(String[] args) {
        try(Writer writer = new FileWriter("HELLO_WORLD/123.txt")) {
            writer.write('0');
            writer.write(new char[]{'1', '2', '3', '4', '5', '6'});
            writer.write("789");
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

写操作跟字节流一样,无此文件,自动创建~


并且还会清空原内容


测试结果:

0397ce150e854e7680beb02a41734153.png



3. 小程序练习:全文检索

就是遍历目录,并在文件内容中查找信息


4462ffc3bd7048b2b25202234a13feec.png

接下来以简单粗暴的方式去实现~


3.1 控制台输入根目录与关键字

public static void main(String[] args) throws IOException {
    Scanner scanner = new Scanner(System.in);
    System.out.print("请输入要扫描的根目录:");
    String root = scanner.next();
    File file = new File(root);
    if(!file.isDirectory()) { // 1. 目录不存在 2. 不是目录
        System.out.println("输入错误");
        return;
    }
    System.out.print("请输入要查询的词:>");
    String words = scanner.next();
    scan(file, words);//扫描
}


根据根目录构造File对象

如果这个file对象不是目录或者不存在的话,则说明输入错误,直接返回退出程序

如果是目录,输入要关键字

调用scan方法对目录进行扫描(自己实现)

3.2 scan递归方法

n叉树就得写循环来递归了

如果是扫描到二进制文件,我们也不指望里面有我们要的文本,因为二进制一般存放一些后端数据信息,并不是给人看的,不是观赏性的,但是二进制文件还是可能会读到的~

记得设立递归出口,死递归会导致栈溢出

public static void scan(File file, String words) throws IOException {
    File[] files = file.listFiles();
    if(files == null) { 
        // 这里空目录对应的并不是空数组!是null~
        return;
    }else {
        for (int i = 0; i < files.length; i++) {
            File f = files[i];
            if(f.isFile()) {
                String content = readAll(f);
                if(content.contains(words)) {
                    System.out.println(f.getCanonicalFile());
                }
            }
            if(f.isDirectory()) {
                scan(f, words);
            }
            //两种都不是的其他文件,就不能读~
        }
    }
}

d83961ff2ccd4483b32d2af576af06ff.png

3.3 readAll读取文件方法

利用StringBuilder拼接字符串~

用Reader字符流读取数据~

对于Java,这些流对象只是读取方式,对文件是二进制还是文本没有要求

最终返回

public static String readAll(File f) {
    StringBuilder stringBuilder = new StringBuilder();
    try (Reader reader = new FileReader(f)){
        while(true) {
            int c = reader.read();
            if(c == -1) {
                break;
            }
            stringBuilder.append((char)c);
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return stringBuilder.toString();
}


堆溢出~

1bbaf48549a246a091e80d2258f80f91.png


3.4 测试

测试用例:

如果文件数量多,内容多,以此法会卡的半死

到时候我们学习一下“倒排索引”这种数据结构,可能能够很好地优化!


7364975af10240ef84cac5ee643ad559.png

根目录是:d:/马库/marathon-april-2023

关键字是:马大帅

测试结果:

fde3ab9e59ba4f9286c12f858246cbce.png


测试结果正常!

另外两个可能是其他项目里提到了这个关键字 ^ V ^


目录
相关文章
|
6月前
文件操作与IO(一些小项目)
文件操作与IO(一些小项目)
|
1月前
|
搜索推荐 索引
【文件IO】实现:查找文件并删除、文件复制、递归遍历目录查找文件
【文件IO】实现:查找文件并删除、文件复制、递归遍历目录查找文件
34 2
|
5月前
|
存储 安全 Unix
【.Net Core】深入理解IO之文件和目录
【.Net Core】深入理解IO之文件和目录
55 4
|
5月前
|
Linux 网络安全 开发工具
【linux】基础IO |文件操作符
【linux】基础IO |文件操作符
40 0
|
5月前
|
Java
文件操作与IO(3) 文件内容的读写——数据流
文件操作与IO(3) 文件内容的读写——数据流
40 0
|
5月前
|
Java Windows
文件操作和IO(2):Java中操作文件
文件操作和IO(2):Java中操作文件
28 0
|
6月前
|
存储 JSON 安全
Python中的文件操作与文件IO操作
【5月更文挑战第14天】在Python中,文件操作是常见任务,包括读取、写入和处理文件内容。`open()`函数是核心,接受文件路径和模式(如&#39;r&#39;、&#39;w&#39;、&#39;a&#39;、&#39;b&#39;和&#39;+&#39;)参数。本文详细讨论了文件操作基础,如读写模式,以及文件IO操作,如读取、写入和移动指针。异常处理是关键,使用`try-except`捕获`FileNotFoundError`和`PermissionError`等异常。进阶技巧涉及`with`语句、`readline()`、`os`和`shutil`模块。数据序列化与反序列化方面,介绍了
62 0
|
6月前
|
Unix Linux 开发工具
【探索Linux】P.11(基础IO,文件操作)
【探索Linux】P.11(基础IO,文件操作)
39 0
|
3月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
4月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用