文件其实就是在硬盘上存储数据的方式
操作系统一般通过“文件系统”这样的模块来管理硬盘
①打开方式:右键windows徽章,里面有磁盘管理选项
②作用:可以看到自己的硬盘个数以及硬盘各部分区域
操作系统通过将硬盘分成许多区域,然后通过一定格式去组织硬盘上的数据
③Windows上的文件系统:NTFS
④Linux上的文件系统:EXT4
通过“N叉树”的树型结构来组织
①绝对路径:以盘符开头的路径;相当于从此电脑出发寻找文件的过程
(盘符:英文字母加一个 ':')
例如:C:/inetpub/custerr
②相对路径:以.或者..开头的路径
(.表示当前目录;..表示当前目录的上一层目录)
(相对路径需要有一个基准目录/工作目录,表示从这个基准目录出发怎么样才能找到文件)
例如:我的D盘中的test目录下有个“1aA”照片
(1)假设我以D盘为基准目录,那么找到“1aA”照片的相对路径为./test/1aA.jpeg
(2)假设我以D:/test为基准目录,那么找到“1aA”照片的相对路径为./1aA.jpeg
(3)假设我以D:/test/tt为基准目录,那么找到“1aA”照片的相对路径为../1aA.jpeg
①文本文件:存储的是字符
②二进制文件:存储的是二进制
如何判断是文本文件还是二进制文件?
方法:用记事本打开,看得懂的就是文本文件,看不懂的就是二进制文件
(用记事本打开文件,就是尝试把当前数据在码表中进行查询)
①包:属于java.io包
i:input输入
o:output输出
②作用:用来描述文件的信息、路径的操作
(暂不涉及关于文件中内容的读写操作)
③注意:File 对象是用来描述一个具体的对象,这个File对象可以对应一个真实存在的文件,也可以对应一个不存在的文件;因此,有 File 对象,并不代表真实存在该文件
①String getParent():返回 File 对象的父目录文件路径(父目录就是上一层目录)
②String getName(): 返回 FIle 对象的纯文件名称
package file; import java.io.File; import java.io.IOException; public class Demo1 { public static void main(String[] args) { //并不要求该test.txt文件真实存在 File file = new File("./test.txt"); //File()括号里初始化可以是相对路径也可以是绝对路径 System.out.println(file.getParent()); System.out.println(file.getName()); } }
③String getPath():返回 File 对象的文件路径
(文件路径是绝对路径还是相对路径取决于你初始化给File对象的是绝对还是相对)
import java.io.File; import java.io.IOException; public class Demo1 { public static void main(String[] args) { File file = new File("./test.txt"); System.out.println(file.getPath()); //因为我初始化给的是相对路径,所以一会打印的也是相对路径 } }
④String getAbsolutePath():返回 File 对象的绝对路径
(如果初始化是相对路径,那么就把初始化的相对路径拼接上当前路径返回)
⑤String getCanonicalPath():返回 File 对象的修饰过的绝对路径
(需要抛IOException异常)
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"); System.out.println(file.getAbsolutePath()); System.out.println(file.getCanonicalPath()); } }
⑥boolean createNewFile():根据 File 对象的初始化,自动创建一个空文件
成功创建后返回 true
(需要抛IOException异常)
⑦boolean exists():判断 File 对象描述的文件是否真实存在
⑧boolean isDirectory():判断 File 对象代表的文件是否是一个目录
⑨boolean isFile():判断 File 对象代表的文件是否是一个普通文件
(1)此时test.txt文件不存在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"); //此时test.txt文件不存在 System.out.println("没有创建文件时:"); System.out.println(file.exists()); System.out.println(file.isFile()); System.out.println(file.isDirectory()); } }
(2)此时test.txt文件创建了
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"); //此时test.txt文件不存在 System.out.println("创建文件后:"); file.createNewFile(); //创建test.txt文件 System.out.println(file.exists()); System.out.println(file.isFile()); System.out.println(file.isDirectory()); } }
⑩boolean delete():根据 File 对象,删除该文件。成功删除后返回 true
⑪void deleteOnExit():根据 File 对象,标注文件将被删除,删除动作会到 JVM 运行结束时才会进行
⑫boolean mkdir():创建 File 对象代表的目录,只能创建一个目录
⑬boolean mkdirs():创建 File 对象代表的目录,可以创建多级目录
import java.io.File; public class Demo3 { public static void main(String[] args) { File file1 = new File("./testDir1"); File file2 = new File("./testDir/111/222/333"); // mk => make dir => directory // mkdir 一次只能创建一层目录 file1.mkdir(); //mkdirs 可以一次创建多级目录 file2.mkdirs(); } }
⑭boolean renameTo(File dest):进行文件改名,同时也能起到文件移动的效果
(1)当两个文件名不同时,file1 renameTo(file2)就是将file1文件名改成file2文件名
(2)当两个文件名相同时,file1 renameTo(file2)就是将file1路径的文件移动到file2路径下
1.当两个文件名不同import java.io.File; public class Demo4 { public static void main(String[] args) { //file1和file2文件名不同,此时起到的作用就是file1文件名改成file2文件名 File file1 = new File("./test.txt"); File file2 = new File("./test1.txt"); file1.renameTo(file2); } }
2.当两个文件名相同
import java.io.File; public class Demo4 { public static void main(String[] args) { //file1和file2文件名相同,此时起到的作用就是file1文件移动到file2路径下 File file1 = new File("./test.txt"); File file2 = new File("./src/file/test.txt"); file1.renameTo(file2); } }
这部分涉及到关于文件中内容的读写操作
①概念:指的是我们在操作文件的时候具体是想一次操作多少个字节
(我们是通过一系列的类来操作文件内容中的读写)
②字节流:主要通过InputStream、OuputStream这两个类的衍生来操作字节文件
以操作字节为单位
操作的是二进制文件
③字符流:主要通过Reader、Writer这两个类的衍生来操作字符文件
以操作字符为单位
操作的是文本文件
④Java IO流是一个比较庞大的体系,涉及到很多的类,这些不同的类,都有各自不同的特性,但是总的来说,使用方法都是类似的
(1)通过构造方法,创建和打开文件
(2)通过close方法,关闭文件(这个close方法上述四个类用法都是一样的)
(3)如果衍生自InputStream或者Reader,就可以使用read方法读取数据
(4)如果衍生自OuputStream或者Writer,就可以使用write方法写入数据
释放必要资源:close()
对于InputStream、OuputStream、Writer、Reader用法都一样
注意:这个操作非常重要!我们需要知道,让进程打开一个文件,是要从系统这里申请一定的资源,也即是会占用PCB里的文件描述符表的一个表位,但其实文件描述符表它的表位是有限的,不会自动扩展,一旦你打开文件不释放,就会造成“文件资源泄露”,会导致文件描述符表位被占满,到时候就打不开文件了!
close使用方法一:try with resources;try代码全部执行完自动调用close,无需手动!
(手动添加有弊端,原因在于抛出异常,或者 return,close 就都执行不到了)
// 使用 try with resources // 当try里面的代码执行完,就会自动帮你调用close,无需手动 // 括号里面写对象的创建语句(可以是一条/多条) try(){ }
close使用方法二:try-catch-finally;手动将close添加到finally里面(不太推荐)
(虽然finally最终还是会执行到close,但是这种方式我们不常用也不推荐)
try { // 中间的代码无论出现啥情况, close 都能保证执行到. } finally { 引用对象.close(); }
①创建Reader对象
注意:因为Reader是一个抽象类,我们使用FileReader类创建来一个Reader
FileReader构造方法:可以填写一个文件路径(绝对路径/相对路径都行), 也可以填写一个构造好的 File 对象
②Reader方法
一般是配合循环使用,因为读数据不可能只读一个两个
(1)int read():读取一个字符的数据
(返回 -1 代表已经完全读完了)
(2)int read(char[] buf):把读到的所有以字符为单位的数据全部放入自定义的buf数组中;自定义buf数组给的初始化的值是字符为单位的数
(返回值是实际读到的数量)
(返回 -1 代表已经完全读完了)
(3)int read(char[] buf, int off, int len):从off下标开始,读取len个字符到数组buf中
(返回值是实际读到的数量)
(返回 -1 代表已经完全读完了)
import java.io.FileReader; import java.io.IOException; import java.io.Reader; // Reader 使用. 需要先在项目目录下准备好一个 hello.txt 的文件,里面填充 "Hello World" 的内容 public class Demo5 { public static void main(String[] args) throws IOException { //使用try with resources,代码执行完自动close try (Reader reader1 = new FileReader("./test.txt")) { while (true) { //自定义buf数组,1024个字符 char buf[] = new char[1024]; //将在test.txt中读到的内容放进且填充buf数组 //返回实际读到的个数n int n = reader1.read(buf); System.out.println(n); if (n == -1) { // 读到文件末尾了. break; } for (int i = 0; i < n; i++) { System.out.print(buf[i] + " "); //打印读到的内容 } System.out.println("\n"); } } } }
①创建InputStream对象
注意:因为InputStream是一个抽象类,我们使用FileInputStream类创建InputStream
FileInputStream构造方法:可以填写一个文件路径(绝对路径/相对路径都行), 也可以填写一个构造好的 File 对象
②InputStream方法
使用方法和Reader类似,且一般是配合循环使用
注意:InputStream虽然是字节流,但是同样可以读取字符,只不过是编码
(但同样可以采取一些措施来让这些编码变成文字,下文介绍)
(1)int read():读取一个字节的数据
(返回 -1 代表已经完全读完了)
(2)int read(byte[] buf):把读到的所有以字节为单位的数据全部放入自定义的buf数组中;自定义buf数组给的初始化的值是字节为单位的数
(返回值是实际读到的数量)
(返回 -1 代表已经完全读完了)
(3)int read(char[] buf, int off, int len):从off下标开始,读取len个字节到数组buf中
(返回值是实际读到的数量)
(返回 -1 代表已经完全读完了)
这里我们还是以读取字符为例(test.txt里面的内容是你好世界)
通过运行结果我们能看到,读取到的内容并非“你好世界”,而是一堆编码,这是因为字节流InputStream读取到的是文本的每个字节,因此,要想看到汉字,我们可以采取一些额外的工具类,例如String,以及下文提到的Scanner,这里我们用String的构造方法为例!
import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; public class Demo6 { public static void main(String[] args) throws IOException { //test.txt里面的内容是你好世界 //使用try with resources,代码执行完自动close try (InputStream inputStream = new FileInputStream("./test.txt")) { while (true) { //自定义buf数组,1024个字节 byte[] buf = new byte[1024]; int n = inputStream.read(buf); System.out.println(n); if (n == -1) { // 读到文件末尾了. break; } for (int i = 0; i < n; i++) { System.out.printf("%x ", buf[i]); //打印读取到的内容,一般我们用十六进制表示 } System.out.println("\n"); } } } }
String构造方法让编码转化成文字
方法:String(数组名, 开始下标, 读取多少个, 字符编码);
下面我们修改代码,只加了一条语句:
虽然String可以修改,但是我们并不推荐,接下来介绍一下Scanner!!!
①Scanner(InputStream inputstream)
对insputstream对象进行读取(没有指定字符集)
将构造好的InputStream对象作为Scanner的参数
②Scanner(InputStream inputstream, String charset)
使用 charset 字符集进行对insputstream对象进行读取
将构造好的InputStream对象作为Scanner的参数
③读取内容的方法:引用对象.next()、引用对象.nextInt()
跟以前控制台输入一样,看文本内容是什么就写什么
import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Scanner; public class Demo7 { public static void main(String[] args) throws IOException { try (InputStream inputStream = new FileInputStream("./test.txt")) { //将构造好的InputStream对象放入Scanner中作为参数 //指定字符集为UTF-8 Scanner scanner = new Scanner(inputStream,"UTF-8"); // 此时就是从 test.txt 这个文件中读取数据了!! String s = scanner.next(); System.out.println(s); } } }
①创建Writer对象
注意:因为Writer是一个抽象类,我们使用FileWriter类创建Writer
FileWriter构造方法1:可以填写一个文件路径(绝对路径/相对路径都行), 也可以填写一个构造好的 File 对象 ,这种方法打开文件之后会清空之前的内容
FileWriter构造方法2:以追加的方式写为true,这样打开文件之后不会清空之前的内容
②Writer方法
输出流对象(无论是字节流还是字符流),在打开文件之后会清空之前所有的内容
(1)void write(int c):写入一个数字
(2)void write(String str):写入一个字符串
①构造方法1:不追加的形式
运行完成后可以看到之前的内容没有了
import java.io.FileWriter; import java.io.IOException; import java.io.Writer; public class Demo8 { public static void main(String[] args) throws IOException { //这种是以不追加方式的构造方法 //test.txt原本具有你好世界的内容 try (Writer writer = new FileWriter("./test.txt")) { // write 写一个字符串 writer.write("hello java"); } } }
②构造方法2:以追加的形式
运行完成后可以看到之前的内容依旧保留
import java.io.FileWriter; import java.io.IOException; import java.io.Writer; public class Demo8 { public static void main(String[] args) throws IOException { //这种是以追加方式的构造方法 //test.txt原本具有你好世界的内容 try (Writer writer = new FileWriter("./test.txt",true)) { // write 写一个字符串 writer.write("hello java"); } } }
①创建OutputStream对象
注意:因为OutputStream是一个抽象类,我们用FileOutputStream创建OutputStream
FileOutputStream构造方法1:可以填写一个文件路径(绝对路径/相对路径都行), 也可以填写一个构造好的 File 对象
FileOutputStream构造方法2:以追加的方式写为true,这样打开文件之后不会清空之前的内容
②OutputStream方法
注意:InputStream虽然是字节流,但是同样可以写入字符,只不过要强制类型转换成byte字节类型或者使用PrintWriter
可以使用getBytes()方法将字符串转换为字节数组的方法
getBytes()方法将字符串中的字符转换成byte类型并存储到byte数组中
(1)void write(int b):写入要给字节的数据
(2)void write(byte[] buf): 将buf这个字节数组中的数据全部写入OutputStream对象中
(3)int write(byte[] buf, int off, int len) :将buf这个字节数组中从off开始的下标写入到OutputStream对象中,一共写 len 个
(4)void flush():重要!!!我们知道 I/O 的速度是很慢的,所以,大多的 OutputStream 为 了减少设备操作的次数,在写数据的时候都会将数据先暂时写入内存的 一个指定区域里,直到该区域满了或者其他指定条件时才真正将数据写 入设备中,这个区域一般称为缓冲区。但造成一个结果,就是我们写的 数据,很可能会遗留一部分在缓冲区中。需要在代码的最后或者合适的位置, 调用 flush(刷新)操作,将数据刷到设备中
代码1:
import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; public class Demo9 { public static void main(String[] args) throws IOException { //这种是以追加方式的构造方法,不会清空原本内容 try (OutputStream outputStream = new FileOutputStream("./test.txt",true)) { outputStream.write('H'); outputStream.write('e'); outputStream.write('l'); outputStream.write('l'); outputStream.write('o'); // 不要忘记 flush!!! outputStream.flush(); } } }
代码2:
import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; public class Demo9 { public static void main(String[] args) throws IOException { //这种是以追加方式的构造方法,不会清空原本内容 try (OutputStream outputStream = new FileOutputStream("./test.txt",true)) { byte[] buf = new byte[] { (byte)'G', (byte)'o', (byte)'o', (byte)'d' //因为像Good这种是字符char型,要强制转成byte类型 }; outputStream.write(buf); // 不要忘记 flush!!! outputStream.flush(); } } }
代码3:
import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; public class Demo9 { public static void main(String[] args) throws IOException { //这种是以追加方式的构造方法,不会情况原本内容 try (OutputStream outputStream = new FileOutputStream("./test.txt",true)) { String s = "Nothing"; //通过getBytes()方法将字符串中的字符转换成byte类型并存储到byte数组中 byte[] buf = s.getBytes(); outputStream.write(buf); // 不要忘记 flush!!! outputStream.flush(); } } }
①作用
PrintWriter 类中提供了我们熟悉的 print/println/printf 方法
OutputStream是字节流,本身应该写入的是字节类型的数据;当你想写入字符,又不想强制转换字节类型数组那样太麻烦了,就可以将OutputStream对象作为PrintWriter的参数,这样再调用print/println/printf 即可按照你需要的格式写入字符
②使用方法
1.将写好的OutputStream对象作为PrintWriter构造方法的参数
2.调用print/println/printf 方法
例如:
OutputStream outputStream = new FileOutputStream("./test.txt",true); PrintWriter writer = new PrintWriter(outputStream); // 接下来我们就可以方便的使用 writer 提供的各种方法了 writer.print("Hello"); writer.println("你好"); writer.printf("%d: %s\n", 1, "没什么"); // 不要忘记 flush writer.flush();
题目:扫描用户指定的目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件
这个题目要求我们能够熟悉File类并且能够写出深度优先的代码!
contains(关键字):查看是否包含关键字
import java.io.File;
import java.util.Scanner;
public class Demo10 {
private static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) {
// 1. 让用户输入一个目录. 后续的查找都是针对这个目录来进行的.
System.out.println("请输入要搜索的根目录: ");
File rootPath = new File(scanner.next());
// 2. 再让用户输入要搜索/要删除的关键词.
System.out.println("请输入要删除的关键词: ");
String keyword = scanner.next();
// 3. 判定一下当前输入的目录是否有效.
if (!rootPath.isDirectory()) {
System.out.println("您此时输入的路径不是合法目录!");
return;
}
// 4. 遍历目录. 从根目录出发, 按照 深度优先(递归) 的方式, 进行遍历
scanDir(rootPath, keyword);
}
public static void scanDir(File currentDir, String word) { //扫描查找文件整体代码
// 1. 先列出当前目录中都包含哪些内容,把内容放进files数组.
File[] files = currentDir.listFiles();
if (files == null || files.length == 0) {
// 判断是不是空的目录或者非法的目录
return;
}
// 2. 遍历列出的文件, 分两个情况分别讨论.
//for-each循环,把files数组一个个放入f
for (File f : files) {
// 加个日志, 方便看程序执行的过程.
System.out.println(f.getAbsolutePath());
if (f.isFile()) {
// 3. 如果当前文件是普通文件, 看看文件名是否包含了 word, 来决定是否要删除.
dealFile(f, word);
} else {
// 4. 如果当前文件是目录文件, 就递归执行 scanDir
scanDir(f, word);
}
}
}
private static void dealFile(File f, String word) { //删除文件整体代码
// 1. 先判定当前文件名是否包含 word
if (!f.getName().contains(word)) {
// 此时这个文件不包含 word 关键词. 直接跳过.
return;
}
// 2. 包含 word 就需要询问用户是否要删除该文件?
System.out.println("该文件是: " + f.getAbsolutePath() + ", 是否要确认删除? (Y/N)");
String choice = scanner.next();
if (choice.equals("Y") || choice.equals("y")) {
f.delete();
}
// 如果是其他值, 都忽略.
}
}
题目:进行普通文件的复制
import java.io.*;
import java.util.Scanner;
// 完成文件复制.
public class Demo11 {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
// 1. 输入路径并且合法性判定
System.out.println("请输入要复制的源文件路径: ");
String src = scanner.next();
File srcFile = new File(src); //直接将src路径构造成File对象
if (!srcFile.isFile()) {
System.out.println("您输入的源文件路径非法!");
return;
}
System.out.println("请输入要复制到的目标路径: ");
String dest = scanner.next();
File destFile = new File(dest); //直接将dest路径构造成File对象
// 不要求目标文件本身存在. 但是得保证目标文件所在的目录, 得是存在的.
// 假设目标文件写作 d:/tmp/cat2.jpg, 就需要保证 d:/tmp 目录是存在的.
if (!destFile.getParentFile().isDirectory()) {
System.out.println("您输入的目标文件路径非法!");
return;
}
// 2. 进行复制操作的过程. 按照字节流打开.
//打开源文件srcFile,往目标路径destFile写入
try (InputStream inputStream = new FileInputStream(srcFile);
OutputStream outputStream = new FileOutputStream(destFile)) {
while (true) {
byte[] buffer = new byte[20480];
int n = inputStream.read(buffer);
System.out.println("n = " + n);
if (n == -1) {
System.out.println("读取到 eof,即读取到末尾了,循环结束! ");
break;
}
outputStream.write(buffer, 0, n);
}
}
}
}
题目:扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Demo12 {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
// 接收用户输入的路径
System.out.println("请输入要扫描的路径:");
String rootPath = scanner.next();
// 校验路径合法性
if (rootPath == null || rootPath.equals("")) {
System.out.println("路径不能为空。");
return;
}
//实例化为File对象
File rootDir = new File(rootPath);
//判断rootDir的真实性
if (rootDir.exists() == false) {
System.out.println("指定的目录不存在,请检查。");
return;
}
if (rootDir.isDirectory() == false) {
System.out.println("指定的路径不是一个目录。请检查。");
return;
}
// 接收要查找的关键字
System.out.println("请输入要查找的关键字:");
String key = scanner.next();
if (key == null || key.equals("")) {
System.out.println("查找的关键字不能为空,请检查。");
return;
}
// 遍历目录查找符合条件的文件
// 保存找到的文件
List fileList = new ArrayList<>();
scanDir(rootDir, key, fileList);
// 打印结果
if (fileList.size() > 0) {
System.out.println("共找到了 " + fileList.size() + "个文件:");
for (File file: fileList) {
System.out.println(file.getAbsoluteFile());
}
} else {
System.out.println("没有找到相应的文件。");
}
}
private static void scanDir(File rootDir, String token, List fileList) throws IOException {
// 获取目录下的所有文件并放入files数组中
File[] files = rootDir.listFiles();
if (files == null || files.length == 0) {
return;
}
// 遍历
for (File file : files) {
if (file.isDirectory()) {
// 如果是目录文件就递归
scanDir(file, token, fileList);
} else {
// 如果是普通文件就判断
// 文件名是否包含关键词
if (file.getName().contains(token)) {
fileList.add(file);
} else {
if (isContainContent(file, token)) {
fileList.add(file.getAbsoluteFile());
}
}
}
}
}
private static boolean isContainContent(File file, String token) throws IOException {
// 定义一个StringBuffer存储读取到的内容
StringBuffer sb = new StringBuffer();
// 输入流
try (InputStream inputStream = new FileInputStream(file)) {
try (Scanner scanner = new Scanner(inputStream, "UTF-8")) {
// 读取每一行
while (scanner.hasNext()) {
// 一次读一行
sb.append(scanner.nextLine());
// 加入一行的结束符 (String对象写入到文件时,换行符应该把\n写完整成\r\n)
sb.append("\r\n");
}
}
}
return sb.indexOf(token) != -1; //sb.indexOf(token)找不到就会返回-1 (-1!= -1 返回false,反之返回true)
}
}