文件和IO
文章目录
文件概述
文件系统是系统内核的一部分,文件分为普通文件,目录文件和其他等等文件,普通文件就是我们常见的xxx.jpg,xxx.txt。目录文件就是文件夹
文件是存储在硬盘上的,标识文件在电脑上的位置用的是文件路径(相对路径和绝对路径),绝对路径是从盘符开始的,相对路径有一个基准,相对于基准的位置
1️⃣绝对路径:D:\program Files\qq\qq.exe(从盘符开始,经过目录(目录就是文件夹)到文件名结束),中间用\分隔,也可以用/分隔
2️⃣相对路径:以.或…开头,从一个基准路径(工作目录)开始,经过目录,到文件名。
文件也是操作系统管理的,在系统内核中有一个专门用来管理文件的部分,叫文件系统,而java针对文件系统进行了封装。
操作文件
定义文件和查看路径
在java中操作文件用的是File类,下面来看具体代码
package file;
import java.io.File;
import java.io.IOException;
public class Demo1 {
public static void main(String[] args) throws IOException {
//通过路径定义一个文件
File file = new File("d:/test.txt");
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());//获取整理过的路径
}
}
✅注意:这里File没有真正在硬盘上创建一个文件,只是先定义了一个文件,下面使用相对路径定义一个文件
package file;
import java.io.File;
import java.io.IOException;
public class Demo1 {
public static void main(String[] args) throws IOException {
//通过路径定义一个文件
//File file = new File("d:/test.txt");
File file = new File("./test.txt");
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());//获取整理过的路径
}
}
相对路径的基准路径就是下面这个路径(工作目录):
✅使用相对路径创建文件时,三种路径就有区别了
是否存在,是否是目录或文件
package file;
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");
System.out.println(file.exists()); //文件是否存在
System.out.println(file.isDirectory()); //是不是目录
System.out.println(file.isFile()); //是不是文件
System.out.println("=====================");
file.createNewFile(); //真正创建文件
System.out.println(file.exists());
System.out.println(file.isDirectory());
System.out.println(file.isFile());
}
}
✅这次我们用file.createNewFile()真正的创建了文件,之前的File只是定义了一个文件,当真正创建了一个文件后,可见左侧目录里就有了一个test.txt
文件创建和删除
package file;
import java.io.File;
import java.io.IOException;
public class Demo4 {
public static void main(String[] args) throws IOException {
File file = new File("test.txt");
file.createNewFile();
file.delete();//删除文件
file.deleteOnExit();//程序退出时删除
}
}
✅file.delete()是立即删除,file.deleteOnExit()是等到程序退出时才删除。
文件夹的创建
package file;
import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
import java.io.File;
public class Demo3 {
public static void main(String[] args) {
File file = new File("test");
System.out.println(file.exists());
System.out.println(file.isDirectory());
System.out.println("===========");
file.mkdir();
System.out.println(file.exists());
System.out.println(file.isDirectory());
}
}
创建多级目录
package file;
import java.io.File;
public class Demo3 {
public static void main(String[] args) {
File file = new File("test/a/b");
System.out.println(file.exists());
System.out.println(file.isDirectory());
System.out.println("===========");
file.mkdirs();
System.out.println(file.exists());
System.out.println(file.isDirectory());
}
}
文件重命名
package file;
import java.io.File;
import java.io.IOException;
public class Demo6 {
public static void main(String[] args) throws IOException {
File file1 = new File("./test1.txt");
File file2 = new File("./test2.txt");
file1.createNewFile();
file1.renameTo(file2);
}
}
✅把test1.txt重命名为test2.txt
读写文件
读写文件步骤
读写文件前需要先打开文件,结束后还得关闭文件
- 打开文件 ,==打开文件其实就是打开文件流
- 读/写文件
- 关闭文件
java中关于文件读写,提供了两组类:
第一组:InputStream OutputStream (基于字节流读写数据) (字节流用来操作二进制文件)
第二组:Reader Writer (基于字符流读写数据)(字符流用来操作文本文件)
==字节流:==以字节为单位读写数据,把二进制数据不经转化直接写出或读入,
字符流:以字符为单位读写数据,把二进制数据转化为字符对应的编码再写出或读入
文件打开不关闭的影响
文件的关闭会释放资源,而打开不关闭就会浪费资源,这个资源主要是什么呢?
这个资源主要是文件描述符表,之前在学进程的时候,我们知道一个线程对应一个PCB,PCB中就有文件描述符表,描述了当前线程打开的文件信息,而文件描述符表其实就是一个数组,也就是用数组来组织了每个文件的信息,数组的下标就是文件描述符。
==每打开一个文件,就会占用一个数组空间,而每关闭一个文件,就释放一个数组空间,==但是要注意,这个数组是有固定容量的,而且系统中也没有设计数组满了要扩容,所以如果代码中频繁打开文件,但是不关闭,数组就会满,再次尝试打开文件就会报错。
读文件
使用InputStream读文本文件
package file;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class Demo7 {
public static void main(String[] args) throws IOException {
File file = new File("test.txt");
//创建文件
file.createNewFile();
//打开文件
InputStream inputStream = new FileInputStream("test.txt");
//读文件
int ret = inputStream.read();
//关闭文件
inputStream.close();
}
}
✅使用InputStream创建一个字节流即是打开了文件,InputStream(代表输入流)是一个抽象类,使用其子类FileInputStream(代表文件输入流)创建一个实例,构造方法传入文件路径,代表打开哪个文件
✅使用read方法读取数据,read方法有三个版本:
1️⃣不带参数版本:读取一个字节的数据,通过返回值接收,返回类型是int,代表用int类型变量接收读取到的一个字节的数据。如果读到文件尾(EOF),返回-1
2️⃣带一个参数版本:参数是一个字节数组,把读到的数据放到这个字节数组中,所以这个参数是一个输出型参数,返回值代表读到了多少个字节的数据
3️⃣带三个参数的版本:和带一个参数的版本类似,不同点是从b[off]这个位置放数据,最多放len个字节的数据
下面写代码来看一下具体使用:
package file;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class Demo7 {
public static void main(String[] args) throws IOException {
//File file = new File("test.txt");
//创建文件
//file.createNewFile();
//打开文件
InputStream inputStream = new FileInputStream("test.txt");
//读文件
byte[] b = new byte[1024];
int len = inputStream.read(b);
for (int i = 0; i < len; i++) {
System.out.println(b[i]);
}
System.out.println(len);
//关闭文件
inputStream.close();
}
}
test.txt文件中的内容:这是一个文本文件,使用的编码格式是utf-8编码
- 把test.txt中的数据一个字节一个字节的读取放到byte数组中,然后打印取出的字节
104对应h,101对应e,108对应l等等,以utf8编码存储字符,一个英文字符占用一个字节,所以每取出一个字节正好对应了一个英文字符,也就是byte数组每个下标正好对应上了一个字符,但要是存的是汉字就不一样了:
比如存储的是“你好”,中文汉字以utf8存储的时候,占用三个字节的空间,然后用read一个字节一个字节取出来,每个汉字就会占用三个字节的空间:
-28,-67,-96这三个字节组合起来才能对应上一个汉字,所以可以明显看到以字节流读取文本文件并不直观,所以可以使用字符流读取文本文件,因为文本文件本身就是按字符编码存储数据的,一个字符一个字符读取,然后存储到字符数组中,再打印,就直接可以打印出字符了
使用Reader读取文本文件
package file;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
public class Demo8 {
public static void main(String[] args) throws IOException {
Reader reader = new FileReader("test.txt");
char[] b = new char[1024];
int len = reader.read(b);
for (int i = 0; i < len; i++) {
System.out.println(b[i]);
}
reader.close();
}
}
一个字符一个字符读出来,放到字符数组中,再打印就能直观的显示出来了,读取汉字也是一样:
使用Scanner读取文本文件
package file;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class Demo9 {
public static void main(String[] args) throws IOException {
//打开文件流
InputStream inputStream = new FileInputStream("test.txt");
Scanner scanner = new Scanner(inputStream);
String str = scanner.next();
System.out.println(str);
inputStream.close();
}
}
我们之前使用Scanner都是从键盘(标准输入流)读取数据,构造方法传入的是System.in,代表从标准输入流读取数据,而传入InputStream就代表从文件流读入数据,这种写法更简单。这里打开文件流得自己手动打开,而打开标准输入流是程序启动默认打开的。
出现异常导致文件未关闭
package file;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class Demo7 {
public static void main(String[] args) throws IOException {
//File file = new File("test.txt");
//创建文件
//file.createNewFile();
//打开文件
InputStream inputStream = new FileInputStream("test.txt");
//读文件
byte[] b = new byte[1024];
int len = inputStream.read(b);
for (int i = 0; i < len; i++) {
System.out.println(b[i]);
}
System.out.println(len);
//关闭文件
inputStream.close();
}
}
上面代码中打开文件或者读取数据都会抛异常,一旦出现异常就有可能导致close()执行不到,出现文件打开了但没有关闭这种情况。
❤️解决办法就是使用try,catch处理异常,然后把close()方法放到finally代码块中,这样就算出异常了,close()也是肯定能执行到的
package file;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class Demo7 {
public static void main(String[] args) {
InputStream inputStream = null;
try {
//打开文件
inputStream = new FileInputStream("test.txt");
//读文件
byte[] b = new byte[1024];
int len = inputStream.read(b);
for (int i = 0; i < len; i++) {
System.out.println(b[i]);
}
System.out.println(len);
}catch (IOException e){
e.printStackTrace();
}finally {
//关闭文件
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
这样的写法就能解决可能关闭不了文件的问题了 ,然后在finally代码块中还得处理一下异常,这样上面一个try catch语句,下面一个try catch语句,比较繁琐,改进办法就是使用try with resources
package file;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class Demo7 {
public static void main(String[] args) {
try (InputStream inputStream = new FileInputStream("test.txt")){
//打开文件
//读文件
byte[] b = new byte[1024];
int len = inputStream.read(b);
for (int i = 0; i < len; i++) {
System.out.println(b[i]);
}
System.out.println(len);
}catch (IOException e){
e.printStackTrace();
}
}
}
上面是具体写法,这种写法在try执行结束后就会自动执行inputStream.close()语句,前提是InputStream实现了Closeable接口,才能这么做
写文件
使用OutputStream写文件
package file;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class Demo10 {
public static void main(String[] args) {
try(OutputStream outputStream = new FileOutputStream("test.txt")){
outputStream.write('a');
}catch (IOException e){
e.printStackTrace();
}
}
}
✅写文件用的是write()方法,用法和读文件时的read()方法相似
使用Writer写文件
package file;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class Demo11 {
public static void main(String[] args) {
try (Writer writer = new FileWriter("test.txt")){
writer.write("hello_world");
}catch (IOException e){
e.printStackTrace();
}
}
}
write()的几个版本:
可以传入一个字符,或者一个字符串,或者一个字符数组
✅注意:每次写文件前都会把文件中原本的内容清除再写文件。
借助PrintStream写文件
package file;
import java.io.*;
public class Demo11 {
public static void main(String[] args) {
try (OutputStream outputStream = new FileOutputStream("test.txt")){
PrintStream printStream = new PrintStream(outputStream);
printStream.println("hello");
printStream.println("world");
}catch (IOException e){
e.printStackTrace();
}
}
}
写文件除了使用OutputStream,Writer也可以使用PrintStream,构造方法里传入outputStream,也可以写文件。
文件操作案例
案例1:查找并删除文件
案例1是遍历删除文件,先让用户输入一个路径,遍历这个路径下的所有文件,让用户决定是否删除
package file;
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
public class Demo12 {
public static void main(String[] args) throws IOException {
System.out.println("请输入一个路径");
Scanner scanner = new Scanner(System.in);
String path = scanner.next();
File file = new File(path);
if (!file.exists()){
System.out.println("不存在:"+file.getCanonicalPath());
return;
}
System.out.println("请输入该路径下的待删除文件名或文件名的一部分");
String toDelete = scanner.next();
scanDelete(file,toDelete);
}
//把该路径下的所有文件尝试删除
public static void scanDelete(File file,String toDelete){
File[] files = file.listFiles();
if (files==null){
return;
}
for (File x:files) {
if(x.isDirectory()){
scanDelete(x,toDelete);
}
if (x.isFile()){
tryDelete(x,toDelete);
}
}
}
public static void tryDelete(File file,String toDelete){
String fileName = file.getName();
if (fileName.contains(toDelete)){
try {
System.out.println("是否要删除该文件?(Y/N)"+file.getCanonicalPath());
Scanner scanner = new Scanner(System.in);
String ret = scanner.next();
if(ret.equals("Y")){
file.delete();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
✅获取一个存在的路径之后,用递归的方式遍历该路径下的所有文件,找到文件然后再确定是否删除
案例2:复制文件
package file;
import java.io.*;
import java.util.Scanner;
//拷贝普通文件
public class Demo13 {
//复制普通文件
public static void main(String[] args) {
System.out.println("请输入待拷贝文件路径");
Scanner scanner = new Scanner(System.in);
String srcPath = scanner.next();
File srcFile = new File(srcPath);
if (!srcFile.exists()){
System.out.println("待拷贝文件不存在");
return;
}
if (!srcFile.isFile()){
System.out.println("待拷贝文件不是普通文件");
return;
}
System.out.println("请输入文件拷贝路径");
String destPath = scanner.next();
File destFile = new File(destPath);
if (destFile.exists()){
System.out.println("文件拷贝路径已经存在,不可拷贝");
return;
}
try {
destFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
//拷贝文件
copyFile(srcFile,destFile);
}
public static void copyFile(File srcFile,File destFile){
try (InputStream inputStream = new FileInputStream(srcFile)){
try (OutputStream outputStream = new FileOutputStream(destFile)){
//缓冲区
byte[] buf = new byte[1024];
while(true){
//从文件读数据到缓冲区
int len = inputStream.read(buf);
//返回值为-1代表读到了EOF
if(len==-1){
break;
}
//从缓冲区取数据到文件
outputStream.write(buf,0,len);
}
}
}catch (IOException e){
e.printStackTrace();
}
}
}
✅先把读取到的数据放到缓冲区里,然后再把缓冲区中的数据写到文件中,循环进行,直到遇到文件尾
❤️解释一下缓冲区:缓冲区其实就是内存中的一块空间,比如一个程序要写数据到文件,如果直接把CPU中的数据写到硬盘上,这是很慢的,因为硬盘的读写速度比CPU慢好几个数量级,就会造成CPU长时间等待IO,无法继续执行其他程序,而如果先把数据写到内存中,后续内存中的数据在存入硬盘上这就比较快了,因为内存读写速度比磁盘快好几个数量级,所以写到内存中比直接写到硬盘上要快的多,写到内存上CPU就可以接着运行其他程序了,大大提高了程序的执行效率。
案例3:根据文件名或内容查找文件
package file;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class Demo14 {
public static void main(String[] args) {
System.out.println("请输入待搜索的目录");
Scanner scanner = new Scanner(System.in);
String path = scanner.next();
File file = new File(path);
if (!file.exists()){
System.out.println("该目录不存在");
return;
}
System.out.println("请输入文件名或内容关键字");
String find = scanner.next();
scanFile(file,find);
}
private static void scanFile(File file, String find) {
File[] files = file.listFiles();
if(files == null){
return;
}
for (File x:files) {
if(x.isDirectory()){
scanFile(x,find);
}
if (x.isFile()){
tryShow(x,find);
}
}
}
private static void tryShow(File x, String find) {
if (x.getName().contains(find)){
try {
System.out.println("文件名匹配,路径:"+x.getCanonicalPath());
} catch (IOException e) {
e.printStackTrace();
}
return;
}
StringBuilder stringBuilder = new StringBuilder();
try (InputStream inputStream = new FileInputStream(x)){
Scanner scanner = new Scanner(inputStream);
while (scanner.hasNextLine()){
stringBuilder.append(scanner.nextLine());
}
if (stringBuilder.indexOf(find) >=0){
System.out.println("文件内容匹配,路径:"+x.getCanonicalPath());
}
}catch (IOException e){
e.printStackTrace();
}
}
}
✅递归遍历目录,找到文件,先判断文件名是否匹配,再判断文件内容是否匹配