本章目标
- 认识文件
- 学习文件管理
- 学习
java
中的文件操作 - 熟悉
java
面向字节流/字符流IO
操作
认识文件
什么是文件呢?
我们先来认识狭义上的文件(
file
)。针对硬盘这种持久化存储的I/O
设备,当我们想要进行数据保存时,往往不是保存成一个整体,而是独立成一个个的单位进行保存,这个独立的单位就被抽象成文件的概念,就类似办公桌上的一份份真实的文件一般.
计算机中的文件就和我们现实中的文件相似!
我们一般通过硬盘
存储文件!
所以我们硬盘下存储了好多文件
例如:
.java
文件(我们编写的java
代码) .jpg
文件(图片也是文件),.txt
文件(文本文件)…我们硬盘下的文件还有很多…
我们这里就不一一举例了!
不同的后缀名,为不同类型的文件!
所以我们需要打开查看后缀名(默认是查看不了后缀名的),便于我们知道文件的类型!
我们知道存储在硬盘上的就是文件,除了上面这些可以打开的文件,还有目录文件!目录(我们所说的文件夹)也是文件的一种!
像这些文件夹都是文件的一种!
在操作系统中,网卡也是一种文件,还有很多硬件设备都看作是文件!
键盘,鼠标,打印机等等都看做是文件,还有些软件也被当做是文件!
这样便于管理,操作同样的文件管理的代码可以操作所有的文件
文件分类
我们这里的分类站在程序员的角度!
我们将文件分为两类:
- 文本文件
文本文件存储的是字符,我们知道一个字符,可能占多个字节单位! - 二进制文件
二进制文件储存的是二进制数(0/1
),二进制文件中的字节是分开的!
我们刚刚还说文件有很多类…为啥现在就2类了,如何区分他们呢?
如果用记事本打开不乱码就是文本文件,打开乱码就是二进制文件!
可以看到jpg
是二进制文件,很多文件都是二进制文件!
像word
那些也是二进制文件,因为他们有一些格式,属于富文本,也就是二进制! 只有像那些txt
文件才是文本文件!
目录结构
我们硬盘下的文件都是由文件系统管理,就是我们电脑中的文件
我们的文件由文件系统管理,就是我们此电脑下的硬盘!
文件是由树形结构管理!采用多叉树的形式进行管理!
所以通过多叉树的结构管理文件系统!
我们的叶子节点就是一个文件!而非叶子节点就是目录文件!
路径
如果我们要描述一个文件所在的位置,我们可以采用两种描述方式!
这两种描述方式是操作系统给我们提供的路径的方式,定位一个文件的位置!
-
绝对路径
绝对路径就是电脑我们打开一个文件目录,上方所看到的路径地址!
D:\java\2022_1_13\src
这里的test.java
就是在这个路径里面!
简单说就是如果这个路径第一个是盘符(C/D
盘等),那么这个路径就是绝对路径,我们在电脑中任意位置都可以精准的找到这个文件!
就相当于你身份证上的地址!超详细! -
相对路径
相对路径,我们学过物理都知道,相对就是相对某个参考系而言,地址所在的位置!
如果我们已经知道了,你的具体某个现地址,所以只要知道你的乡,村就可以找到你的家庭地址了!
也就是我们需要有参考位置!
参考这个目录文件,你所在的路径,就是相对路径!
假如我们以D:\java\2022_1_13
为参考地址也就是基准目录!
那么out
文件如何表示呢!
./out
这就表示了基准下的out
相对路径了!
如果我们要找基准的父文件目录中的文件呢?
我们通过../
的方式找到基准路径的父目录!
../2022_1_12
以D:\java\2022_1_13
为基准路径表示2022_1_8
文件位置!
./
表示当前目录!../
表示上一级目录!
还有你发现一个小细节没有!
为啥路径可以用/
也可以用\
呢?
一般情况下用/
反斜杠表示路径!
而windows
支持用\
表示路径!但是也支持/
可以看到windows
下输出的路径都是\
!!!
/
便于我们编程使用,如果我们用/
编译器就以为我们使用了转义字符,就比较繁琐!
如果要使用\
我们要对\
转义操作!
java文件操作
我们java
提供了一个File
类,在java.io
包下,我们通过这个类就可以完成对文件的操作,首先这个文件对象就描述了一个文件/目录,就可以实现很多功能!
File
文件和目录路径名的抽象表示。
文件系统相关的操作
文件系统相关的操作也就是我们看到的文件系统所具有的操作文件的功能,我们通过java
代码也能实现!
这里可执行的功能,我们通过java
代码也都可以实现!
例如:打开文件,删除文件,查看文件大小,日期…一系列关于文件的操作我们都可以进行!
我们java
有一组文件操作的API
通过上述的方法就可以对文件进行管理!
我们来演示几个常用的方法:
package file;
import java.io.File;
import java.io.IOException;
public class File1 {
public static void main1(String[] args) {
//我们创建一个File对象,传入路径
File file1 = new File("D:/1.txt");
File file2 = new File("D:/test.txt");
//判断该文件是否存在!
System.out.println(file1.getName()+"是否存在:"+file1.exists());
System.out.println(file2.getName()+"是否存在:"+file2.exists());
}
public static void main(String[] args) {
File file = new File("D:/test.txt");
System.out.println("是否可读:"+file.canRead());
System.out.println("是否可写"+file.canWrite());
System.out.println(file.isDirectory());
System.out.println("路径:"+file.getAbsolutePath());
System.out.println("路径:"+file.getPath());
System.out.println("是否为目录文件:"+file.isDirectory());
System.out.println("父目录路径:"+file.getParent());
System.out.println("父目录文件:"+file.getParentFile());
//在D盘下创建 4_22.txt文件!
//只是创建了一个文件对象!在硬盘中并没有文件!
File newFile = new File("D:/4_22.txt");
try {
//在硬盘中创建该文件!
newFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("文件:"+newFile.getName());
System.out.println("文件大小:"+newFile.length());
System.out.println("删除文件:"+newFile.delete());
System.out.println("文件是否存在:"+newFile.exists());
}
}
我们D
盘下并没有1.txt
文件,所以文件不存在!
我们通过文件对象File
调用createNewFile()
方法,就可以真正的在我们的计算机中创建该文件!
//创建目录文件!
public static void main(String[] args) {
//创建目录!
File file = new File("./aaa");
// 创建一级目录 mkdir 方法!
file.mkdir();
System.out.println(file.isDirectory());
// 创建多级目录 mkdirs 方法!
File file1 = new File("../aaa/bbb/ccc");
file1.mkdirs();
System.out.println(file1.isDirectory());
}
在该项目的父目录路径下创建一个目录文件!
在该项目下创建一个目录文件!
还有很多关于文件系统相关的操作,大家可以阅读java
的API
进行学习!!!
点击查看javaAPI
文件内容相关的操作
文件内容相关的操作,就是对文件内容进行读写操!
我们也是通过java
中的API
进行学习!
我们java
提供了两种对文件操作读取的方案!
- 字符流: 以字符为单位进行读取!
- 文件流: 以字节为单位进行读取!
这里的字节流和字符流就对应了我们的二进制文件和字符文件的读取
什么是流呢?
就好比水流,我们将文件的读取抽象成了水流!
- 输入流: 就好比你在喝水,文件读取的过程!
- 输出流:就好比你在输出知识,文件删除的过程!
分类 | 抽象类读(子类) | 抽象类写(子类) |
---|---|---|
字节流 | InputStream (FileInputStream) |
OutputStream (FileOutputStream) |
字符流 | Reader (FileReader) |
Writer (FileReader) |
我们通过这个表格就可以对字符流和字节流的读取进行区分!
我们会详细学习这些读取类!
字节流
字节流:就是以字节为单位,一次读写一个或多个字节!
读操作
如果我们要对一个文件进行读操作,都要进行那些步骤呢?
- 打开文件
- 进行读操作
- 关闭文件
我们通过下面案例对字节流读取进行学习!
//字节流读操作!
import java.io.*;
public class File2 {
public static void main(String[] args) throws IOException {
//输入操作,也就是读文件(学习读书过程就是输入)
File file = new File("./hello.txt");
//输入流,打开文件!
InputStream inputStream = new FileInputStream(file);
byte[] result = new byte[1024];
//byte数组用来存放读取结果
while (true){
int len = inputStream.read(result);//保存了有效字节数!
if(len==-1){
//读取完成!
break;
}
for (int i = 0; i <len; i++) {
//将结果打印!
System.out.print((char)result[i]);
}
}
//关闭文件!
inputStream.close();
}
}
hello.txt
文件:
读取结果:
FileInputStream
用于读取诸如图像数据的原始字节流!
构造方法:
我们这里的字节流读操作,用到了read
方法!
下面3个read
方法都是对输入流进行读操作!
int read()
从该输入流读取一个字节的数据。
无参方法read()
,默认读取一个字节,返回值是一个整型int
!
为啥不是返回一个byte型呢?
我们知道byte
范围是-128~127
而我们规定当返回值为-1
就表示读取到文件末尾!
如果这样就冲突了!
所以这里的返回值是int
一个字节的范围是 0~255
int read(byte[] b)
从该输入流读取最多b.length
个字节的数据为字节数组。
我们通过byte
数组,去接收读取的结果!
而这里的返回值是读取到的有效字节个数!
返回-1
就是读取结束!
int read(byte[] b, int off, int len)
从该输入流读取最多 len`字节的数据为字节数组。
和上面的读取操作类似!
这里的b
字节数组存放读取内容时,是从off
数组下标开始,一次最多可以读取len
长度的字节!
而返回值是读取到有效字节数!
输出流写操作
写文件也是类似的步骤!
- 打开文件
- 进行写操作
- 关闭文件
我们先看一个案例:
//字节流写操作
public static void main(String[] args) throws IOException {
//输出操作,也就是写文件(学习做题,写博客过程就是输出)
File file = new File("./4_22.txt");
//打开文件!
OutputStream outputStream = new FileOutputStream(file);
//我们将string字符串写入 file文件!
String string = "hello world!";
//我们要通过字节方式进行写操作!
outputStream.write(string.getBytes());
System.out.println("写入成功!");
//关闭流文件!
outputStream.close();
}
FileOutputStream
用于写入诸如图像数据的原始字节流。 对于写入字符流,请考虑使用FileWriter 。
我们输出流写操作,主要通过write
方法进行!
void write(byte[] b)
将 b.length个字节从指定的字节数组写入此文件输出流。void write(byte[] b, int off, int len)
将 len字节从位于偏移量 off的指定字节数组写入此文件输出流。void write(int b)
将指定的字节写入此文件输出流。
这里的write
和read
类似!
这里是将b
数组里的内容写到file
文件!
注意:
这里每次打开文件进行写操作,都会将文件内容置空,再进行写操作!如果并不存在此文件,就会创建改文件!
如果我们要从文件末尾开始写操作呢?
//追加写入操作!
public static void main(String[] args) throws IOException {
//输出操作,也就是写文件(学习做题,写博客过程就是输出)
File file = new File("./4_22.txt");
//打开文件!
//true可追加!默认不可追加!
OutputStream outputStream = new FileOutputStream(file,true);
//我们将string字符串写入 file文件!
String string = "hello world!";
//我们要通过字节方式进行写操作!
outputStream.write(string.getBytes());
System.out.println("追加写入成功!");
//关闭流文件!
outputStream.close();
}
我们需要在创建输出流对象时通过构造方法传参设置是否可追加!
字符流
字符流:就是以字符为单位,一次读取一个或多个字符!
字符流读操作:
//字符流读操作!
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
public class File3 {
public static void main(String[] args){
Reader reader = null;
try {
//打开文件!
reader = new FileReader("./hello.txt");
while (true){
//一次读取一个字符!
int x = reader.read();
if(x==-1){
//读取结束!
break;
}
//输出该字符!
System.out.print((char)x);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//关闭文件!
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
我们可以看到上面的代码比较繁琐,因为要处理好多异常,还有关闭文件操作!
我们可以简化一下代码,将文件流在try
语句块创建,语句块结束就会自动关闭资源!
//优化后的代码!
public static void main(String[] args){
///try() 中语句块结束自动关闭资源!
try (Reader reader = new FileReader("./hello.txt")){
//打开文件!
while (true){
//一次读取一个字符!
int x = reader.read();
if(x==-1){
//读取结束!
break;
}
//输出该字符!
System.out.print((char)x);
}
} catch (IOException e) {
e.printStackTrace();
}
}
注意:
只要实现了Closeable
这个接口的类都可以进行这样的操作!
而我们的文件操作流对象实现了这个接口!
字符流的读取和字节流的读取大同小异!
int read()
读一个字符int read(char[] cbuf)
将字符读入数组。abstract int read(char[] cbuf, int off, int len)
将字符读入数组的一部分。int read(CharBuffer target)
尝试将字符读入指定的字符缓冲区。
字符流写操作:
//字符流写操作!
public static void main(String[] args) {
//打开文件!
try(Writer writer = new FileWriter("./4_22.txt",true)){
String string = "加油!";
writer.write(string);
System.out.println("追加写入成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
常用方法:
Writer append(char c)
将指定的字符附加到此作者。Writer append(CharSequence csq)
将指定的字符序列附加到此作者。Writer append(CharSequence csq, int start, int end)
将指定字符序列的子序列附加到此作者。abstract void close()
关闭流,先刷新。abstract void flush()
刷新流。void write(char[] cbuf)
写入一个字符数组。abstract void write(char[] cbuf, int off, int len)
写入字符数组的一部分。void write(int c)
写一个字符void write(String str)
写一个字符串void write(String str, int off, int len)
写一个字符串的一部分。
上述方法就可以对字符流进行写操作了!
文件操作案例
我们通过下面3个案例进一步巩固学习!
问题一:
扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件
//实例一
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
public class File4 {
public static void main(String[] args) throws IOException {
// 扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),
// 并且后续询问用户是否要删除该文件
Scanner scanner = new Scanner(System.in);
//获取要扫描的目录文件!
System.out.print("目录文件->");
String dirctoryString = scanner.next();
File dirctoryFile = new File(dirctoryString);
//获取要删除的文件
System.out.printf("要删除的文件->");
String fileString = scanner.next();
//扫描文件!
scanFile(dirctoryFile,fileString);
}
private static void scanFile(File dirctoryFile,String fileString) throws IOException {
//将该目录下的文件列出!
File[] files = dirctoryFile.listFiles();
for (File file:files) {
System.out.println(file.getCanonicalFile());
if(file.isDirectory()){
//目录文件继续遍历!
scanFile(file,fileString);
}else {
//文件判断是否要删除!
if(file.getName().contains(fileString)){
//进行删除!
file.delete();
}
}
}
}
}
我们可以找到test
文件,然后进行了删除操作!
问题二:
进行普通文件的复制
//实例二:
import java.io.*;
import java.util.Scanner;
public class File5 {
public static void main(String[] args) throws IOException {
//进行文件复制操作!
Scanner scanner = new Scanner(System.in);
//获取要复制的源文件!
System.out.print("源文件->");
String srcString = scanner.next();
File srcFile = new File(srcString);
//获取源文件目录:
String parentString = srcFile.getParent();
System.out.println("目录文件:"+parentString);
//目录文件
File parentFile = new File(parentString);
//获取目标文件
System.out.printf("目标文件->");
String desString = scanner.next();
File desFile = new File(desString);
//扫描文件!
scanner(parentFile,srcFile,desFile);
}
private static void scanner(File parentFile,File srcFile, File desFile) {
File[] files = parentFile.listFiles();
for (File file:files) {
System.out.println(file.getName());
if(file.isDirectory()) {
//目录文件继续遍历
scanner(file, srcFile,desFile);
}
if(file.getName().equals(desFile.getName())){
//找到了目标文件!
//进行复制操作!
copyfile(srcFile,desFile);
return;
}
}
}
private static void copyfile(File srcFile, File desFile) {
try(Reader reader = new FileReader(srcFile)){
//读src文件!
//存放读取结果
char[] chars = new char[1024];
while (true){
int len = reader.read(chars);
if(len==-1){
//读取结束!
break;
}
//写入到目标文件!
try(Writer writer = new FileWriter(desFile,true)){
String string = new String(chars,0,len);
System.out.println("复制内容:"+string);
writer.write(string);
}
System.out.println("复制成功!");
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
源文件:
目标文件:
注意这里的目标文件要写绝对或相对路径!
问题三:
扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)
注意 :我们现在的方案性能较差,所以尽量不要在太复杂的目录下或者大文件下实验!
//实例三
import java.io.*;
import java.util.Scanner;
public class File6 {
public static void main(String[] args) throws IOException {
// 扫描指定目录,并找到名称中包含指定字符或者内容的所有普通文件(不包含目录),
Scanner scanner = new Scanner(System.in);
//获取要扫描的目录文件!
System.out.print("目录文件->");
String dirctoryString = scanner.next();
File dirctoryFile = new File(dirctoryString);
//获取要删除的关键字
System.out.printf("要删除的关键字->");
String fileString = scanner.next();
//扫描文件!
scanFile(dirctoryFile,fileString);
}
private static void scanFile(File dirctoryFile,String fileString) throws IOException {
//将该目录下的文件列出!
File[] files = dirctoryFile.listFiles();
for (File file:files) {
System.out.println(file.getCanonicalFile());
if(file.isDirectory()){
//目录文件继续遍历!
scanFile(file,fileString);
}else {
//文件判断是否要删除!
if(file.getName().contains(fileString)){
//文件名称有该关键字进行删除!
file.delete();
System.out.println(file.getName()+" 删除成功!");
}else {
//查看文件内容是否含该关键字!
if(isdelete(file,fileString)){
file.delete();
System.out.println(file.getName()+" 删除成功!");
}
}
}
}
}
private static boolean isdelete(File file, String fileString) {
try(Reader reader = new FileReader(file)){
char[] chars = new char[1024];
while (true){
int len = reader.read(chars);
if(len==-1){
break;
}
String str = new String(chars,0,len);
System.out.println(file.getName()+" 内容:"+str);
if (str.contains(fileString)) {
//如果内容中包含了该关键字!
return true;
}
}
}catch (IOException e) {
e.printStackTrace();
}
return false;
}
}