一.认识文件
1.文件初识
文件,在计算机中是保存在硬盘上的.
操作系统,把硬盘进行的"抽象"封装,
编程的时候,我们是不会直接操纵硬盘的,而是通过"文件"来间接操作硬盘.
通过"文件资源管理器"来管理文件
打开"此电脑",我们可以看到C盘D盘...然后再打开C盘又可以看到各种文件夹,每个文件夹里面都是各种文件
所以由此可知,"文件资源管理器"是通过N叉树的结构来管理文件的
2.文件路径
那么如何知道一个文件在哪里呢?
我们就需要通过这个文件的文件路径去找到这个文件.
每个文件都有唯一的文件路径,文件路径中的每一个目录都是用 \ 分割
使用" \ "分割文件目录其实是Windows独有的,像Liunx,MaxOS,Android...都是用" / "分割,不过Windows也支持" / "分割目录
文件路径又分为两种,绝对路径 和相对路径
绝对路径:以盘符(C盘,D盘...)开头的文件路径
相对路径:通常是以.或者..开头的文件路径,相对路径是需要有一个基准路径的,也就是说是相对于哪个目录
举个例子:E:\练习代码\JavaEE初阶代码\out\production\JavaEE初阶代码\.idea
这个文件路径是绝对路径
.\.idea这个就是一个相对路径,
它的基准路径就是E:\练习代码\JavaEE初阶代码\out\production\JavaEE初阶代码
绝对路径 = 基准路径 + 相对路径
如果基准路径是这个呢?E:\练习代码\JavaEE初阶代码\out\production\JavaEE初阶代码\.idea\111
相对路径就是**..\.idea**
..表示返回上一级目录
同理E:\练习代码\JavaEE初阶代码\out\production\JavaEE初阶代码\.idea\111\222
这个的相对路径就是**..\..\.idea**
3.文件种类
文件可以分为文本文件 和二进制文件
所谓文本文件,里面存储的都是字符串,是"合法的字符"
什么是合法的字符?对于不同的编码集来说,合法的字符也是不一样的
我们知道ASCII码表,gbk还有UTF8
ASCII码只能用来表示英文,阿拉伯数字,标点符号,所以对于ASCII编码集来说中文就是非法的字符
二进制文件,存什么都行
简单判断文件是文本文件还是二进制文件可以通过记事本打开这个文件,如果不出现乱码就是文本文件,出现了乱码大概率是二进制文件
二.Java中操作文件
1.文件系统操作
创建文件,删除文件,移动文件,获取文件属性...
Java中通过java.io.File类来对⼀个⽂件(包括⽬录)进行抽象的描述
注意,有File对象,并不代表真实存在该文件
这里我们先在这个文件路径下面创建一个text.txt文件

然后在通过File类来new出一个file对象,之后我们对这个file对象进行的各种操作其实就是对这个text文件进行操作.
java
File file = new File("./test.txt");
可以看到我们这边写的是一个相对路径,如果是绝对路径的话就不需要考虑基准路径
但是现在这个相对路径的基准路径是什么呢?
在我们使用的编程工具中,这个就是
我使用的是Trae,像IDEA也是有提示的
下面看看File类的各种方法:

我们来看一下前几个方法的效果:
1.得到文件各种属性的get方法


2.创建文件createNewFile(),删除文件delete(),deleteOnExit():
创建之前我们看下这个基准路径下有什么文件

执行代码:
java
package io;
import java.io.File;
import java.io.IOException;
public class Demo3 {
public static void main(String[] args) {
File file = new File("./测试创建文件");
System.out.println("exists: " + file.exists());
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("exists: " + file.exists());
}
}

此时我们再看刚刚的基准路径下的文件,就多出来了一个我们创建的文件

然后测试删除文件,执行delete操作之后,发现这个文件也就删除了



使用deleteOnExit()方法会在进程结束后删除该文件

在执行程序之后的60秒内,即使执行到deleteOnExit这行代码,但是60内还没运行结束,只有运行结束之后才会真正删除文件

结束后:

3.打印文件目录list(),listFiles()
针对file对象的目录打印
java
public static void main(String[] args) {
File file = new File("./test.txt");
File file1 = new File("./测试文件夹");
System.out.println("exists: " + file.exists());
System.out.println("exists: " + file1.exists());
System.out.println("-------------------------------");
System.out.println(Arrays.toString(file.list()));
System.out.println(Arrays.toString(file1.list()));
System.out.println("-------------------------------");
System.out.println(Arrays.toString(file.listFiles()));
System.out.println(Arrays.toString(file1.listFiles()));
}
}
在测试文件夹里面还含有一个文件夹



查看打印效果:

可以看到对一个文件打印出来的是null
对一个文件夹打印出来的是这个文件夹里面的内容,但是只能打印这一层,
"测试对象2"这个文件就没有被打印出来
4.创建目录mkdir(),mkdirs():
可以看到现在是没有名字叫"测试文件夹"这个目录的,执行代码会发现成功创建


如果想创建多层目录,就可以使用mkdirs(),
java
File file = new File("./测试文件夹/层1/层2/层3");
file.mkdirs();

5重命名文件renameTo():

java
package io;
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
public class Demo7 {
public static void main(String[] args) {
File file = new File("./test.txt");
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
File file1 = new File("./重命名.txt");
Scanner scanner = new Scanner(System.in);
System.out.println("输入任意内容");
scanner.next();
file.renameTo(file1);
}
}
输入内容之后,可以看到文件名成功被修改

这个操作还可以改变文件位置,例如我现在创建一个"重命名文件夹",想把这个test.txt文件放到这个文件夹里面,并且改名为"重命名文件"
就可以把这行代码一改

然后执行代码就可以看到这个文件出现在"重命名文件夹"里面了
2.文件内容操作
读文件:把硬盘的数据读到内存
写文件:把内存的数据写到硬盘
进行这两个操作,我们都需要通过"流对象"来进行文件内容操作
Java通过一系列的类来表示"流对象",分为两个大类,字节流 和字符流
字节流:读写数据的时候,以字节为单位,一次最少读取一个字节,读二进制文件,通常使用字节流
对应的类分别是InputStream和OutputStream
字符流 :读写数据的时候,以字符为单位,一次最少读一个字符(一个字符可能对应多个字节,看编码方式),读文本文件,通常使用字符流,对应的类是Read和Writer
1.字节流操作


1.InputStream
在这里有人可能会好奇,为什么read方法是读取字节,但是要用int来接收,
这是因为一个字节的范围是0-255
但是read方法当读到末尾之后会返回-1.所以需要用int来接收

先来创建一个文件,然后往文件中写入"hello",然后试着使用read()方法去读取,看看是什么效果
java
public static void main(String[] args) throws IOException {
File file = new File("./test.txt");
if(!file.exists()){
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
//创建流对象这个过程,在操作系统中,对应"打开文件"这个过程
InputStream inputstream = new FileInputStream(file);
while(true){
int read = inputstream.read();
if(read == -1){
break;
}
//字节内容一般都是用16进制表示
System.out.printf("0x%x\n",read);
}
}
test.txt文件里面的内容:

打印结果:

打开ASCII码表,查看这几个字符对应的意思,可以知道这几个字符代表的就是hello
如果里面是中文呢?

再次尝试打印

这个对应的就不是ASCII码表了,而是要去查询UTF-8码表对应的
2.read(byte[] b)和read(byte[] , int off,int len)
这两个带有参数的版本其实就是将读到的数据存储到数组中,
这个byte数组也叫做"输出型参数"
java
public static void main(String[] args) throws IOException {
File file = new File("./test.txt");
if(!file.exists()){
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
InputStream inputstream = new FileInputStream(file);
byte[] bytes = new byte[1024];
while(true){
int n = inputstream.read(bytes);
if(n == -1){
break;
}
for(int i = 0;i < n ;i++){
System.out.printf("0x%x\n",bytes[i]);
}
}
}
使用这个方法,就是先创建一个数组,用来接收read方法读到的数据,然后每次试着把byte这个数组装满,然后打印,直到读完这个数组
结果:

tip:读取完文件之后需要关闭文件,这是因为每次打开文件都会在文件描述符表中插入一个元素,文件描述符表是不会自动扩容的,所以说如果一直打开文件,但是不关闭文件,这样就会把这个表里面的内容消耗殆尽,之后再打开就会打开失败了
java
inputstream.close();
为了防止忘记close操作,我们可以使用下面这种方法
java
public static void main(String[] args) throws IOException {
File file = new File("./test.txt");
byte[] bytes = new byte[1024];
try(InputStream inputstream = new FileInputStream(file)){
while(true){
int n = inputstream.read(bytes);
if(n == -1){
break;
}
for(int i = 0;i < n ;i++){
System.out.printf("0x%x\n",bytes[i]);
}
}
}catch(IOException e){
e.printStackTrace();
}
}
将创建流对象的操作放到try里面,这样在结束的时候就会自动调用close()
2.OutputStream
OutputStream和InputStream操作类似,都是一个抽象类,下面是OutputStram的各种方法

让我们试着写入数据,看看文件里面会有什么变化

可以看到在write之后,文件里面多出来了abcd四个字母,这四个字母对应的ASCII码就是97,98,99,100
我们再尝试写入汉字试试

此时文件里面的内容就变成"你好"了
这时候有人就会问了,那刚刚写进去的abcd怎么不见了呢?
这里就有一个知识点,在使用OutputStream的时候,如果不指定追加写的话,每次写新数据就会清空原来的数据
那么如何指定追加写呢?
只需要在后面加一个true即可

这样我们多次运行这个代码,看看效果

这时候就是在原来内容的基础上继续写了
2.字符流操作
1.Reader类
刚刚我们学习的都是字节流的相关方法,现在来看看字符流
字符流和字节流类似,同样需要通过一个类来实现,这个类就是Reader
Reader类的核心方法就是read()
read()是一次读一个字符,-1表示结束
read(char[] cbuf)表示一次读若干个字符,填充到char[].返回实际读到的字符个数
read(char[] cbuf,int off,int len),表示一次读若干个字符,填充到char[]的部分


将test文件里面的内容改成"你好abcd",然后使用read()方式读取字符,并打印

如果是按照数组读的话,这样即可

2.Writer类
Writer和Reader的使用方法类似

Writer类的核心方法就是write(),可以看到这里有很多种方法,第一个可以传一个字符串,这样我们想写啥基本上就可以写啥

可以看到在执行完这段代码之后,test的内容就改变完成了

同理想要进行追加写操作,也是需要加个参数true
三.小程序练习
1.文件搜索
这是一个可以搜索文件,询问是否删除的一个小代码,很简单,大家可以试试
java
package io;
import java.io.File;
import java.util.Scanner;
public class Demo15 {
static int count = 0;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入要查询的目录:");
String dirPath = scanner.next();
File dir = new File(dirPath);
if(!dir.isDirectory()){
System.out.println("输入的不是目录");
return;
}
Scanner scanner2 = new Scanner(System.in);
System.out.print("请输入要查询的文件名:");
String name = scanner2.next();
File file = new File(name);
//创建searchFile方法递归查询
searchFile(dir,file);
if(count == 0){
System.out.println("查询不到该文件");
}
}
private static void searchFile(File dir, File file) {
// TODO Auto-generated method stub
File[] files = dir.listFiles();
if(files == null){
//空目录直接return
return;
}
for(File f : files){
if(f.isDirectory()){
searchFile(f,file);
}else{
if(f.getName().contains(file.getName())){
System.out.println("找到文件:" + f.getAbsolutePath());
count = 1;
tryDelete(f);
}
}
}
}
private static void tryDelete(File f) {
// TODO Auto-generated method stub
System.out.println("是否删除文件:(Y/N)");
Scanner scanner = new Scanner(System.in);
String choice = scanner.next();
if(choice.equals("y")||choice.equals("Y")){
f.delete();
System.out.println("删除成功");
return;
}
}
}
2.文件复制
功能:输入想要复制的源文件和想要复制的目标文件位置,完成文件复制操作
主要思路:使用流对象对源文件进行读操作,然后将源文件里面的内容写到目标文件里面
这里注意一个点:当我们输入的目标文件位置不存在的时候,OutputStream会自动帮我们在这个路径下生成目标文件,所以不需要判断目标文件位置是否存在,也不需要手动创建这个目标文件位置
java
package io;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Scanner;
public class Demo16 {
static int count = 0;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入要复制的源文件路径:");
String filename = scanner.nextLine();
File sourcefile = new File(filename);
if(!sourcefile.isFile()){
System.out.println("文件不存在");
return;
}
System.out.print("请输入要复制的目标文件路径:");
String destfilename = scanner.nextLine();
File destfile = new File(destfilename);
if(destfile.getParentFile() == null){
System.out.println("目标文件路径错误");
return;
}
copyFile(sourcefile, destfile);
if(count == 0){
System.out.println("复制失败");
}
}
private static void copyFile(File sourcefile, File destfile) {
try(
FileInputStream fis = new FileInputStream(sourcefile);
FileOutputStream fos = new FileOutputStream(destfile);
){
byte[] buffer = new byte[1024];
while(true){
int len = fis.read(buffer);
if(len == -1){
break;
}
fos.write(buffer, 0, len);
}
count = 1;
System.out.println("复制成功");
}catch(IOException e){
e.printStackTrace();
}
}
}