在计算机中"文件"的概念很大,比如一些 .txt、.doc、.jpg、.mp4 等后缀的文件都叫作 普通文件。当然也有不普通的文件,比如:目录(也叫作目录文件)。同时在计算中也把一些硬件和软件资源给抽象成了文件等。(键盘、显示器等)
而在我们程序员眼中只将文件分为了两类:文本文件 和 二进制文件
文件文件 与 二进制文件 有什么不同?
我们都知道 文本 其实也是由二进制组成,也就是说 文本文件 也是由 二进制 组成。
那为什么要分为 两种文件?
这是因为在文本文件中相邻的二进制之间是有联系的,大都是多个二进制组成一个字符,最终一个一个的字符形成了文本。(从肉眼上看不是乱码的字符)
而二进制文件里面的二进制之间是没有联系的。
最简单区分 文本文件 和 二进制文件 的方式:将一个文件使用 记事本 打开,如果是乱码就是 二进制文件,反之不是乱码就是 文本文件!!!
在操作系统中管理文件是 “文件系统” 模块负责,而 文件系统 则是通过 树形结构 的方式来组织管理文件的。
"此电脑"就是这颗树的根节点,而里面的 C盘、D盘等就相当于一棵棵的子树。(就相当于一棵N叉数)
在组织完文件之后,为了能正确定位 每个文件的 位置,就有了 “路径”。
路径分为 : 相对路径 / 绝对路径
绝对路径就是开头从 盘符 开始的一条字符串:比如 F:\test.txt ( 描述了 text.txt 文件的位置 )
相对路径则是以 .(当前路径) 或者 … (当前路径的父路径)开头。
所以想要使用相对路径,就需要清楚哪个是 基准路径(也就是当前路径)。
那接下来我们就了解一些 java 中的有关文件(普通文件)的操作。
我们知道在 java 中给我们提供了一些有关文件IO的操作。并将它们放在了 java.util.io 中。
在这其中的文件操作大致分为两类:文件系统相关的操作 / 文件内容相关的操作(在C语言里也学过一些)
这个是指通过 “文件资源管理器” 来完成一些操作。
比如:新建文件夹(新建目录)、重命名、删除文件夹(删除目录)、创建文件、删除文件等。
构造方法
//所传的参数可以是 绝对路径 也可以是 相对路径
File file = new File("f:/test.txt");
普通方法(第一部分)
public class Demo1 {
public static void main(String[] args) throws IOException {
//为了好观察效果,这里使用 相对路径
File file = new File("./test.txt");
//返回 file 的父目录文件路径
String parent = file.getParent();
System.out.println(parent);
//返回 file 的纯文件名称
String name = file.getName();
System.out.println(name);
//获取 file 的文件路径
String path = file.getPath();
System.out.println(path);
//获取 file 的绝对路径
String absolutePath = file.getAbsolutePath();
System.out.println(absolutePath);
//获取 file 对象修饰过的绝对路径 (通过运行结果就能观察到不同)
String canonicalPath = file.getCanonicalPath(); //有 IO 异常
System.out.println(canonicalPath);
//判断 file 对象描述的文件是否存在
boolean exists = file.exists();
System.out.println(exists); //我并没有在 当前路径 下创建一个 test.txt 的文件,所以值应给为 false
//判断当前 file 对对象描述的文件是否是一个目录文件
boolean directory = file.isDirectory();
System.out.println(directory);
//判断当前文件是否是一个 普通文件
boolean file1 = file.isFile();
System.out.println(file1);
}
}
普通方法(第二部分)
public class Demo2 {
public static void main(String[] args) throws IOException {
File file = new File("f:/test.txt");
//根据 file 对象创建一个 空文件 ,成功便返回 true,反之则是 false
boolean newFile = file.createNewFile();
System.out.println(newFile);
//删除 file 对象路径下的文件,成功 true,反之则是 false
//注意 该方法是 立即删除
boolean delete = file.delete();
System.out.println(delete);
//这个方法也是删除文件 ,但它是 程序退出之后再删除
file.deleteOnExit();
}
}
普通方法(第三部分)
public class Demo3 {
public static void main(String[] args) {
// File file = new File("./aaa");
File file = new File("./aaa/bbb/ccc/ddd");
// //创建一级目录
// boolean mkdir = file.mkdir();
// System.out.println(mkdir);
//可以一下创建多级目录
boolean mkdirs = file.mkdirs();
System.out.println(mkdirs);
//拿到当前目录下的所有文件名
File file1 = new File("./");
String[] list = file1.list();
System.out.println(Arrays.toString(list));
System.out.println("============================================");
//效果一样,就是返回类型不同 为 File 类型
File[] files = file1.listFiles();
System.out.println(Arrays.toString(files));
}
}
普通方法(第四部分)
public class Demo4 {
public static void main(String[] args) {
File file1 = new File("./aaa");
File file2 = new File("./zzz");
//将 file1 描述的文件 重命名 为 file2描述的文件
boolean b = file1.renameTo(file2);
System.out.println(b);
}
}
我们在上面知道,程序员将普通文件分为两种 : 文本文件 / 二进制文件
所以在这里 java 标准库中也给我们提供了 两种 系列操作: 字节流对象 / 字符流对象。
而我们日常需要使用的一些操作都在里面:打开文件 、读文件、写文件、关闭文件。
在字节流对象里,有关读和写的操作都在两个类里:InputStream(读) 和 OutputStream(写)。
而这两个类都是接口(抽象类)。也就是说明在new对象的时候需要使用继承了这两个类的实例类,去真正实现里面的方法。
在java标准库中继承这个抽象类的实体类是 FileInputStream 。
同时 FileInputStream 类中有一个 read() 方法,它可以从文件中读取字节。
//read有三个重载方法
//没有参数,返回值类型为 int
//表示每次读取一个字节用int类型返回
public int read() throws IOException{...};
//有一个字节数组参数,返回类型为 int ,这种参数我们称作 "输出型参数"
//表示每次读取若干个字节的数据,并将数据存放到 byte[] 中(从下标为0处放),然后返回读取字节的个数。
public int read(byte b[]) throws IOException {...};
//有三个参数:字节数组、下标、长度,返回类型为 int
//表示每次读取若干个字节的数据,并将数据存放到 byte[] 中(从下标 off 处开始放),然后返回读取字节的个数
//len : 表示最多能放多少个元素在 byte[] 中,也就是能放多少个字节。
public int read(byte b[], int off, int len) throws IOException {...};
具体代码:
public class Demo5 {
public static void main(String[] args) throws FileNotFoundException {
//提出来定义,好方便在 finally 里使用
InputStream inputStream = null;
try {
//创建流对象
inputStream = new FileInputStream("f:/test.txt");
//一个一个字节的读取文件
while(true){
int read = inputStream.read();
if(read == -1){
break;
}
//打印一下读取到的字节(字节所对应的int)
System.out.println(read);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//写在 finally 里后,即使触发了异常,也会释放资源
try {
//断言一下 inputStream 是否为 null
assert inputStream != null;
//还要记得释放资源
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
分析:这个代码虽然实现了读取文件中的数据,但是我们发现它实现的很繁琐,那么能不能简化一下呢?
答案是肯定的,我们可以使用 try with resources 来进行简化
try with resources 的作用就是可以帮助我们自动的进行资源的释放。
但是注意在 try() 里创建的实例对象需要实现 Closeable接口,才能写在括号里面!!!
简化后的代码:
public class Demo6 {
public static void main(String[] args) {
//在括号里创建 输入流对象
try(InputStream inputStream = new FileInputStream("f:/test.txt")){
//用字节的方式循环读取文件中的数据
while(true){
int read = inputStream.read();
//如果返回值为 -1,表示已经读到文件的末尾了。
//read返回的值范围 -1 ~ 255
if(read == -1){
break;
}
//打印一下这个字节
System.out.println(read);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
这时便可以很明显的看到代码简单了许多。
当然在上面我们使用的是 没有参数的 read 方法,但是一般我们使用是 一个参数的read 方法,这样效率更高。
具体代码:
public class Demo7 {
public static void main(String[] args) {
try(InputStream inputStream = new FileInputStream("f:/test.txt")){
while(true){
//用来存放每次读取的数据
byte[] bytes = new byte[1024];
int read = inputStream.read(bytes);
//判断是否读完了
if(read == -1){
break;
}
//循环打印 bytes 中的数据
for (int i = 0; i < read; i++) {
System.out.println(bytes[i]);
}
//可以将数据转换成一个字符串,并设置字符编码集
String str = new String(bytes,0,read,"utf-8");
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
在 java 标准库中继承了 OutputStream 的实体类是 FileOutputStream 。
并且 FileOutputStream 中的方法使用也基本上与上面的大同小异。
public class Demo8 {
public static void main(String[] args) {
//这样创建实例的话,每次打开文件后,就会将文件里的内容给清空。
try(OutputStream outputStream = new FileOutputStream("f:/test.txt")){
//想要追加写,就需要在 路径 后每添加一个参数 true,表示追加写
//OutputStream outputStream = new FileOutputStream("f:/test.txt",true);
//一个字节一个字节的写
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
//一次写入多个字节(大多使用这种方式)
byte[] bytes = new byte[]{100,101,102};
outputStream.write(bytes);
} catch (IOException e) {
e.printStackTrace();
}
}
}
顾名思义,就是针对文本文件的读取和写入。(以字符为单位读写)
它也有两个接口: Reader / Writer
同时也有两个实体类来实现它们 FileReader / FileWriter。
使用方法大同小异。
//使用没有参数的 read 方法(每次读取一个字符)
public class Demo9 {
public static void main(String[] args) {
try(Reader reader = new FileReader("f:/test.txt")){
//每次读取一个字符,返回实际读取的字符的ASCII值。读完了返回 -1
while(true){
int read = reader.read();
//判断是否读到了文件末尾
if(read == -1){
break;
}
//打印读到的数据
System.out.println((char)read);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//使用一个参数的 read 方法(每次读取若干字符)
public class Demo10 {
public static void main(String[] args) {
//创建字符流对象
try(Reader reader = new FileReader("f:/test.txt")){
while(true){
//使用字符数组来存储读取的数据
char[] ch = new char[1024];
int read = reader.read(ch);
//返回值为 -1 时,说明读到文件末尾了
if(read == -1){
break;
}
//循环打印每次读取的字符
for (int i = 0; i < read; i++) {
System.out.println(ch[i]);
}
//也可以直接将读到的数据转成一个字符串
String str = new String(ch,0,read);
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class Demo11 {
public static void main(String[] args) {
//和 OutputStream 的实现类一样,如果不多加一个 true 参数,则表示打开文件先清空然后写
try(Writer writer = new FileWriter("f:/test.txt")){
//写一个字符
writer.write(97);
//写一个字符串
writer.write("hello word");
//写一个字符数组
char[] ch = new char[]{'a','a','a'};
writer.write(ch);
} catch (IOException e) {
e.printStackTrace();
}
}
}
我们以前在使用 Scanner 的时候,经常在实例化对象时传的参数时 System.in。这表示从键盘读取数据。
在我们学习了 IO 和 文件 过后,我们可以将 Scanner 和 InputStream 搭配使用读取文本信息。
具体代码:
public class Demo19 {
public static void main(String[] args) {
StringBuilder stringBuilder = new StringBuilder();
try(InputStream inputStream = new FileInputStream("f:/test.txt");
Scanner scanner = new Scanner(inputStream,"utf-8")){
//一行一行的读取数据
while(scanner.hasNextLine()){
stringBuilder.append(scanner.nextLine());
//每读取一行就添加一个换行符
stringBuilder.append("\r\n");
}
//打印出读到的内容
System.out.println(stringBuilder);
} catch (IOException e) {
e.printStackTrace();
}
}
}
这个相较于上面的 Scanner 和 InputStream 的搭配使用,是较为麻烦的。
具体代码:
public class Demo18 {
public static void main(String[] args) {
//true 表示是追加写,false/不加参数 表示不是追加写
try(OutputStream outputStream = new FileOutputStream("f:/test.txt",true);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream,"utf-8");
PrintWriter printWriter = new PrintWriter(outputStreamWriter)){
printWriter.print("fdag");
printWriter.println("aaaa");
printWriter.printf("\r\n%d 等于 %d 的绝对值。",5,-5);
} catch (IOException e) {
e.printStackTrace();
}
}
}
题目:用户从键盘输入一个扫描路径和一个关键词,给该程序则可以在指定路径下查找名称包含关键词的普通文件,从而将它删除掉。
步骤:
具体代码:
public class Demo15 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入需要扫描的路径:");
String srcPath = scanner.next();
System.out.println("请输入需要查找的文件名中的关键字:");
String word = scanner.next();
//1.检查路径是否正确
File file = new File(srcPath);
if(!file.isDirectory()){
System.out.println("扫描路径有误!");
return;
}
//2.遍历所有的文件并进行查找删除操作
findAll(file,word);
}
private static void findAll(File file, String word) {
//1.先拿到文件列表,并检查是否为空
File[] files = file.listFiles();
if(files == null){
return;
}
//2.判断是否是普通文件,然后执行相应的操作
for (File f : files) {
if(f.isFile()){
if(f.getName().contains(word)){
//3.删除操作
delete(f);
}
}else if(f.isDirectory()){
findAll(f,word);
}
}
}
private static void delete(File f) {
Scanner scanner = new Scanner(System.in);
try {
//1.确认是否真正删除该文件
System.out.println(f.getCanonicalPath()+" 是否确认删除该文件(Y/N):");
String inputStr = scanner.next();
//2.判断输入的是否为 Y/y
if(inputStr.equals("Y")||inputStr.equals("y")){
//3.删除文件,并返回结果
boolean delete = f.delete();
//4.通过结果提示是否删除成功!
if(delete){
System.out.println("删除成功!");
}else{
System.out.println("删除失败!!!");
}
}else{
System.out.println("取消删除!");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
题目:普通文件的复制,用户输入一个普通文件的路径和一个复制文件到的目的地。通过程序进行复制操作。
步骤:
具体代码:
public class Demo16 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入拷贝源:");
String srcPath = scanner.next();
System.out.println("请输入目的地路径:");
String destPath = scanner.next();
//1.检查拷贝源是不是一个普通文件路径
File file = new File(srcPath);
if(!file.isFile()){
System.out.println("拷贝源路径输入有误!");
return;
}
//2.读取文件内容并写入到目的地
readToWriter(file,destPath);
}
private static void readToWriter(File file,String destPath) {
//1.先读后写
try(InputStream inputStream = new FileInputStream(file);
OutputStream outputStream = new FileOutputStream(destPath)){
byte[] bytes = new byte[1024];
while(true){
int len = inputStream.read(bytes);
//检查是否读到了文件末尾
if(len == -1){
break;
}
//将内容写到目的地
outputStream.write(bytes,0,len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
题目:用户输入一个文件目录和关键词。通过该程序遍历该目录下所有的普通文件,并判断每个普通文件中是否包含指定的关键词,包含就将该文件的路径打印在控制台上。
步骤:
具体代码:
public class Demo17 {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入扫描路径:");
String srcPath = scanner.next();
System.out.println("请输入内容关键字:");
String word = scanner.next();
//1.检查扫描路径是否有误
File file = new File(srcPath);
if(!file.isDirectory()){
System.out.println("扫描路劲有误!");
return;
}
//2.循环遍历该路径下所有的文件内容,并进行判定
findAll(file,word);
}
private static void findAll(File file, String word) throws IOException {
//1.拿到当前目录下所有的文件
File[] files = file.listFiles();
if(files == null){
return;
}
//2.判断相应的情况,并做出对应的操作
for (File f:files) {
//判断是否是一个普通文件
if(f.isFile()){
//3.检查其中内容是否包含关键字
boolean flag = check(f, word);
if(flag){
//包含就打印该文件的路径
System.out.println(f.getCanonicalPath());
}
}else if(f.isDirectory()){
findAll(f,word);
}
}
}
private static boolean check(File f, String word) {
StringBuilder stringBuilder = new StringBuilder();
//1.读取文件中的内容到 stringBuilder 中。
try(InputStream inputStream = new FileInputStream(f);
Scanner scanner = new Scanner(inputStream,"utf-8")){
//一行一行的读取
while(scanner.hasNextLine()){
//添加每一行的内容
stringBuilder.append(scanner.nextLine());
//添加换行符
stringBuilder.append("\r\n");
}
} catch (IOException e) {
e.printStackTrace();
}
//2.判断文件内容中是否包含关键词。(能找到 indexOf 返回下标,不能则返回-1)
return stringBuilder.indexOf(word) != -1;
}
}
.flush 就是刷新缓冲区的操作。
什么是缓冲区?为什么需要?什么叫刷新缓冲区?
缓冲区就是一块空间,它可以提高程序的运行效率。
举个例子:搬石头
一种方式:(效率很低)
- 我每次搬起一块就走到目的地,然后放下。
另一种方式 :(效率较高)
- 我有一个盆(缓冲区),我每先将石头往盆里放,直到盆放满了。然后我就将盆搬到目的地去。如此反复,最后我发现石头搬完了,但是还有一盆没装满。即使如此,这时我还是要将没装满的盆给搬到目的地去(这个过程就是刷新缓冲区)。
为什么上面的代码中我们没有手动的 .flush 操作,完成缓冲区的刷新?