前言:
大家好,我是良辰丫,今天我将与大家一起学习文件操作的相关操作,跟随我的步伐,一起往下看!???
?个人主页:良辰针不戳
?所属专栏:javaEE初阶
?励志语句:生活也许会让我们遍体鳞伤,但最终这些伤口会成为我们一辈子的财富。
?期待大家三连,关注,点赞,收藏。
?作者能力有限,可能也会出错,欢迎大家指正。
?愿与君为伴,共探Java汪洋大海。
目录
1. 初识文件与IO1.1 文件1.2 IO1.3 文件结构组织与目录1.4 路径1.4.1 绝对路径1.4.2 相对路径 2. 文件类型2.1 文本文件2.2 二进制文件 3. IO代码操作3.1 File3.2 File的一些方法3.3 文件内容操作3.4 InputStream等字节流3.5 字符流 4. 文件操作的案例4.1 文件查询4.2 文件删除4.3 文件复制
1. 初识文件与IO
1.1 文件
文件
: 这个概念想必我们并不陌生,电脑上的文件,手机上的文件,一个文件夹,或者一个文本文件,再或者一个word文件等都可以看做一个文件.咱们日程谈到的文件,其实是硬盘上的文件. 看到这里,咱们回忆一下硬盘(外存)与内存的区别
咱们写一个简单的代码,定义一个变量,其实就是在内存上申请空间;但是数据库中数据一般是保存在硬盘上的.
1.2 IO
IO是文件的一种操作,大多数都表示输入输出流在操作硬盘
的一种操作.文件输入输出流的概括,为什么叫流呢?大家可以想象一下水流,从一头流向另一头,文件流和水流有点相似,大家可以稍作理解. 1.3 文件结构组织与目录
咱们之前学过二叉树,文件的结构组织相当于一种 N叉树结构.简单的截图看一下我的一个目录结构.
1.4 路径
所谓路径
: 描述某个文件所在的位置.路径可以说是一个文件的身份标识.windows系统中路径是唯一的,但是在Linux系统中路径可能不同.
1.4.1 绝对路径
绝对路径
: 从盘符开始,到某个文件的路径.(下面我们会用代码来描述)
下面是一个绝对路径
E:\bite\java学习\4.JavaEE初阶\4.JavaEE初阶
1.4.2 相对路径
所谓相对路径
,是相对于某个位置开始,到你要找的那个文件.
下面是一个相对路径,其中点表示当前路径,也就是相对于java学习这个路径
.\4.JavaEE初阶\4.JavaEE初阶.
2. 文件类型
普通文件也会根据其保存数据的不同,也经常被分为不同的类型,我们一般简单的划分为文本文件和二进制文件,分别指代保存被字符集编码的文本和按照标准格式保存的非被字符集编码过的文件。
2.1 文本文件
我们肉眼可以看懂的文件(帮助大家理解,并不能这样说)文本文件存储的是文本,文本文件的内容都是由ASCII字符组成的.其实,计算机只能识别二进制文件,文本文件是计算机进行翻译,帮助我们理解的.2.2 二进制文件
计算机进行识别的文件,我们单纯通过肉眼不理解文件内容.我们如何识别文本文件和二进制文件呢?一种简单的办法是用记事本打开某个文件,我们能理解就是文本文件,理解不了的就是二进制文件.二进制就是一个个的字节,记事本尝试把若干个字节的数据往utf8码表里替换,替换出来啥就是啥,替换不出来的就是方块.我们电脑上建立的快捷方式相当于是真实文件的一个引用,和我们java里面的引用很相似.3. IO代码操作
3.1 File
有File对象,并不代表对象真实存在,File对象是硬盘上的一个文件的抽象表示.
文件是存储在硬盘上的,直接通过代码操作硬盘,不太方便.在内存中创建一个对应的对象,操作这个内存中的对象,就可以间接影响到硬盘的文件情况了.构造File对象的过程中,可以使用绝对路径/相对路径进行初始化,这个路径指向的文件,可以存在,也可以不存在.
public static void main(String[] args) throws IOException{ //File创建的文件并不一定真实存在 File file = new File("d:/123.text"); //获取父目录 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()); }
public static void main(String[] args) throws IOException { // 在相对路径中, ./ 通常可以省略 File file = new File("d:/123.txt"); //判断文件是否存在 System.out.println(file.exists()); System.out.println(file.isDirectory()); //判断是否为普通文件 System.out.println(file.isFile()); // 创建文件 file.createNewFile(); System.out.println(file.exists()); System.out.println(file.isDirectory()); System.out.println(file.isFile()); file.delete(); System.out.println("删除文件之后"); System.out.println(file.exists()); }
public static void main(String[] args) { File file = new File("e:/aaa/bbb"); // 只能创建一级目录 // file.mkdir(); // 创建多级目录,注意mkdirs为复数 file.mkdirs(); }
public static void main(String[] args) throws IOException { File file = new File("e:/aaa"); String[] results = file.list(); System.out.println(Arrays.toString(results)); File[] results2 = file.listFiles(); System.out.println(Arrays.toString(results2)); }
public static void main(String[] args) { // 重命名 File src = new File("e:/aaa"); File dest = new File("e:/fff"); src.renameTo(dest); }
3.2 File的一些方法
下面有许多File方法,咱们没有必要可以去记,只需要记忆一些常见的,然后需要用的时候我们去查就可以了.(常见的我会用代码展示)
返回类型 | 方法名 | 说明 |
---|---|---|
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对象标注文件将被删除 |
String[] | list() | 返回File对象代表的目录下的所有文件名 |
File[] | listFiles() | 返回File对象代表的目录的所有文件,以File表示 |
boolean | mkdir() | 创建File对象代表的目录 |
boolean | mkdirs() | 创建File对象代表的目录,如果必要会创建中间目录 |
boolean | renameTo(File dest) | 进行文件改名 |
boolean | canRead | 判断用户是否有可读权限 |
boolean | canWrite | 判断用户是否有可写权限 |
void | flush() | 我们知道 I/O 的速度是很慢的,所以,大多的 OutputStream 为了减少设备操作的次数,在写数据的时候都会将数据先暂时写入内存的一个指定区域里,直到该区域满了或者其他指定条件时才真正将数据写入设备中,这个区域一般称为缓冲区。但造成一个结果,就是我们写的数据,很可能会遗留一部分在缓冲区中。需要在最后或者合适的位置,调用 flush(刷新)操作,将数据刷到设备中。 |
3.3 文件内容操作
根据文本文件,提供了一组类,统称为"字符流",典型代表为Reader,Writer,读写的基本单位是字符.二进制文件,提供了一组类,统称为"字节流",典型代表为InputStream,OutputStream,读写的基本单位是字节.流对象又分为输入流和输出流.3.4 InputStream等字节流
InputStream用来进行IO操作,不仅仅可以读写硬盘文件,还可以读写别的(后面的网络编程经常用.)
注意:
文件的关闭操作非常重要
inputStream.close()
那么问题来了,咱们java不是有gc垃圾处理机制嘛?为什么还需要手动释放资源呢?
java中内存中的文件一般不需要手动释放,但是这里的文件(硬盘)的资源是需要手动释放的,咱们这里的文件主要是文件描述符.进程是使用PCB这样的结构来表示:pid,内存指针,文件描述符.文件描述符记录了当前进程打开了哪些文件,每次打开一个文件,都会在这个表里,申请一个位置,这个表可以当成一个数组,数组下标就是文件描述符,数组元素就是这个文件在内核中的结构体的表示.但是这个表的长度是有限制的,不能无休止的打开不进行释放,一旦满了,继续打开,就会打开失败,可能会导致文件资源泄露,内存泄露,甚至会有更严重的问题.
下面我写了两种close的方式.
注释掉的代码拥有close操作,但是显得代码并不美观.我们需要了解的是==带有资源的try操作,会在代码块结束的时候,自动执行close关闭操作.因为InputStream实现了Closeable类.==这就是我们没有加注释的写法,是不是更加美观了呢? public static void main(String[] args) throws IOException { // 这个过程, 相当于 C 中的 fopen , 文件的打开操作~~// InputStream inputStream = null;// try {// inputStream = new FileInputStream("e:/hello.txt");// } finally {// inputStream.close();// } try (InputStream inputStream = new FileInputStream("e:/hello.txt")) { // 读文件 // read 一次返回的是一个字节. 但是此处的返回值类型是 int !!! while (true) { int b = inputStream.read(); if (b == -1) { // 读到末尾了, 结束循环即可 break; } System.out.printf("%x\n", b); } } }
如果文件读取完毕,read就会返回-1.此处使用的是字节流,每次读取的不是字符,而是字节,读出的这一串数据,就是每个字符的ASCII码. read 和 write还可以一次读写多个字节,使用byte[]来表示.read会尽可能把byte[]填满,读到末尾,返回-1.write会把byte[]所有数据写入文件中. 下面是把字节流数据写入到文件中,但是咱们写入的数据是字节流,也能叫做二进制文件,咱们用肉眼看不懂.
public static void main(String[] args) { try (OutputStream outputStream = new FileOutputStream("e:/hello.txt")) { outputStream.write(1); outputStream.write(2); outputStream.write(3); } catch (IOException e) { e.printStackTrace(); } }
3.5 字符流
字符流
: 按照字符为单位进行读写文件.
public static void main(String[] args) { try (Reader reader = new FileReader("e:/hello.txt")) { while (true) { int c = reader.read(); if (c == -1) { break; } //int强制类型转换成char char ch = (char)c; System.out.print(ch); } } catch (IOException e) { e.printStackTrace(); } }
看上面的截图,咱们就可以非常清楚的看到文本内容,因为咱们读取的方式是字符流.字节流与字符流好多用法都相同,咱们就不一一展示了.
4. 文件操作的案例
4.1 文件查询
接下来咱们完成一个简单的文件操作案例,进行文件内容的查询操作.
简单进行递归遍历目录,把所有文件都列出来.每次找到一个文件打开,读取里面的内容.判断是否拥有要查找的关键词,如果存在,列出该文件夹.咱们的循环采取foreach的方式,foreach不用判断条件,不用管是否越界,而且具有自动遍历的方式,非常好用.
package io;import java.io.*;import java.util.*;public class Test02 { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // 1. 先让用户指定一个要搜索的根目录 System.out.println("请输入要扫描的根目录: "); //当前目录下文件不存在 File rootDir = new File(scanner.next()); if (!rootDir.isDirectory()) { System.out.println("输入有误, 您输入的目录不存在!"); return; } // 2. 让用户输入一个要查询的词. System.out.println("请输入要查询的词: "); String word = scanner.next(); // 3. 递归的进行目录/文件的遍历了 scanDir(rootDir, word); } private static void scanDir(File rootDir, String word) { // 列出当前的 rootDir 中的内容. 没有内容, 直接递归结束 File[] files = rootDir.listFiles(); if (files == null) { // 当前 rootDir 是一个空的目录, 这里啥都没有. // 没必要往里递归了 //这是递归的结束条件 return; } // 目录里有内容, 就遍历目录中的每个元素 for (File f : files) { System.out.println("当前搜索到: " + f.getAbsolutePath()); if (f.isFile()) { // 是普通文件 // 打开文件, 读取内容, 比较看是否包含上述关键词 String content = readFile(f); if (content.contains(word)) { System.out.println(f.getAbsolutePath() + " 包含要查找的关键字!"); } } else if (f.isDirectory()) { // 是目录 // 进行递归操作 scanDir(f, word); } else { // 不是普通文件, 也不是目录文件, 直接跳过 continue; } } }//下面的方法的作用就是把文件内容遍历一次,然后提取出来. private static String readFile(File f) { // 读取文件的整个内容, 返回出来. // 使用字符流来读取. 由于咱们匹配的是字符串, 此处只能按照字符流处理, 才是有意义的. StringBuilder stringBuilder = new StringBuilder(); try (Reader reader = new FileReader(f)) { // 一次读一个字符, 把读到的结果给拼装到 StringBuilder 中. 统一转成 String while (true) { int c = reader.read(); if (c == -1) { break; } stringBuilder.append((char)c); } } catch (IOException e) { e.printStackTrace(); } return stringBuilder.toString(); }}
咱们写的只是一个普通的查找文件,当我们的路径范围比较大的时候,就可能查不到我们想要的结果.一般的项目中的查询需要用到一种倒排索引的数据结构,目前咱们不需要了解.
下面的文件删除与文件复制与文件查询的原理基本一样,大家可以去尝试一下.
4.2 文件删除
package io;import java.io.*;import java.util.*;public class Del { public static void main(String[] args) throws IOException { Scanner scanner = new Scanner(System.in); System.out.print("请输入要扫描的根目录: "); String rootDirPath = scanner.next(); File rootDir = new File(rootDirPath); if (!rootDir.isDirectory()) { System.out.println("您输入的根目录不存在或者不是目录,退出"); return; } System.out.print("请输入要找出的文件名中的字符: "); String token = scanner.next(); List<File> result = new ArrayList<>(); // 因为文件系统是树形结构,所以我们使用深度优先遍历(递归)完成遍历 scanDir(rootDir, token, result); System.out.println("共找到了符合条件的文件 " + result.size() + " 个,它们分别 是"); for (File file : result) { System.out.println(file.getCanonicalPath() + " 请问您是否要删除该文件?y/n"); String in = scanner.next(); if (in.toLowerCase().equals("y")) { file.delete(); } } } private static void scanDir(File rootDir, String token, List<File> result) { File[] files = rootDir.listFiles(); 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()); } } } }}
4.3 文件复制
package io;import java.io.*;import java.util.*;public class KaoBei { public static void main(String[] args) throws IOException { Scanner scanner = new Scanner(System.in); System.out.print("请输入要复制的文件(绝对路径 OR 相对路径): "); String sourcePath = scanner.next(); File sourceFile = new File(sourcePath); if (!sourceFile.exists()) { System.out.println("文件不存在,请确认路径是否正确"); return; } if (!sourceFile.isFile()) { System.out.println("文件不是普通文件,请确认路径是否正确"); return; } System.out.print("请输入要复制到的目标路径(绝对路径 OR 相对路径): "); String destPath = scanner.next(); File destFile = new File(destPath); if (destFile.exists()) { 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)) { try (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("复制已完成"); }}
后序:
文件操作其实很简单,语法也不复杂,除了学习语法还要深刻了解字节流(可以看做二进制文件)和字符流(可以看做文本文件).学了这么多,大家稍加练习,熟悉一下文件操作的语法吧.