目录
前言:
我们平时写的局部变量,数组之类的数据都是存储在内存中。内存中的数据只要电脑断电,数据就会丢失。而对于硬盘里的数据,只要硬盘不坏,数据就会一直保存。文件操作就是直接操作硬盘里的数据。
路径理解
绝对路径
在Windows中,描述某个文件从盘符开始写起,到指定文件。
例如:E:\java代码\SwordOffer
在Linux中,从/(根目录开始),到指定文件。pwd查看的就是绝对路径。
例如:/home/wh/Code
相对路径
相对路径是指相对于某个路径(一般称为工作目录),另一个文件的路径。idea的工作目录就是项目所在的目录。相对路径就可以用工作目录做为参考来描述另一个文件路径。.代表当前目录,..代表上级目录。
在windows中:./test.txt。意思为在当前工作目录下的test.txt文件。
在Linux中:./game。意思为当前工作目录下的game目录。
File类
构造方法
注意:创建的File实例就是明确的指向了一个文件,或者目录。通过这个实例就可以对于这个文件或者目录进行一些操作。
普通方法
这里的普通方法使用起来比较简单,我们看见方法名,就可以知道这个方法是做什么的。我直接用代码演示(带有详细注释)。
这里我介绍一下list()方法和listFiles()方法。第一个返回file对象代表的目录下所有文件名,如果代表文件则返回null。第二个返回file对象代表的目录下所有文件,以file对象表示。
代码演示
public class IODemo1 {
public static void main(String[] args) throws IOException {
//相对路径,file对象就指向这个文件
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());
//是否为目录
System.out.println(file.isDirectory());
//是否为文件
System.out.println(file.isFile());
//文件长度
System.out.println(file.length());
//是否支持写功能
System.out.println(file.canWrite());
//是否支持读功能
System.out.println(file.canRead());
//是否可以执行
System.out.println(file.canExecute());
//是否存在这个文件
System.out.println(file.exists());
//是否为隐藏文件
System.out.println(file.isHidden());
//返回file对象代表的目录下所有文件名,如果代表文件则返回null
String[] tmp = file.list();
System.out.println(Arrays.toString(tmp));
//返回file对象代表的目录下所有文件,以file对象表示
File[] tmp2 = file.listFiles();
System.out.println(tmp2.length);
File file0 = new File("./test.txt");
File tmp0 = new File("./test3.txt");
//修改文件名
//System.out.println(file.renameTo(tmp));
//创建目录(file对象所指向的目录)
System.out.println(file0.mkdir());
File file1 = new File("./test/test2/test3");
//创建目录(file对象所指向的一连串目录)
System.out.println(file1.mkdirs());
//删除file对象所指向的目录
System.out.println(tmp0.delete());
//程序退出时,删掉file对象所指向的文件(临时文件)
tmp0.deleteOnExit();
File file2 = new File("./test2.txt");
//创建一个file所指向的文件
System.out.println(file2.createNewFile());
System.out.println(file2.delete());
}
}
文件内容读写
这里涉及流对象。对于“流”的理解,我们可以认为数据的写入,读出就像水流一样。
例如我们想盛满一杯水,可以一次就盛满,也可以分多次。读写数据,我们一次可以写一部分数据,也可以一次写完。读数据也是如此。
字节流
InputStream方法介绍
1)尽可能一次读取这个数组最大长度的字节数,如果数据量不足以这个数组的最大长度,即有多少就读多少,并且返回读取到的长度。如果读到文件结束标志,则返回-1。
2)一次只读一个字节,返回读到的数据。由于读到文件结尾时,返回-1,所以返回值用int表示才够用。
3)关闭文件。创建流对象时,就会打开文件。在操作系统中一个进程的描述用pcb来表示。其中就包含了文件描述符表(其实就是一个数组),这个数组的大小是可以修改的,但不管怎么样它是有限的。每当我们打开一个文件,就会在文件描述符表中申请一块资源。如果我们只打开文件,不去关闭文件(释放文件描述符表中的资源),总会有表满的时候。因此在不需要这个文件时,就可以关闭文件,释放资源。
4)读取文件到这个字节数组,从off位置到len的长度。如果读到文件结束标志,就返回-1。
注意:
1)InputStream是抽象类,要使用还得实现具体的类。关于InputStream的实现类有很多,这里只关注读写文件,所以使用FileInputStream。
2)FileInputStream的构造方法中可以指定文件路径或者使用File对象。
代码演示
使用数组来读取
public class IODEmo8 {
public static void main(String[] args) throws IOException {
File file = new File("E:/tmp/dog.png");
InputStream inputStream = new FileInputStream(file);
while (true) {
//缓冲区数组。可以减少IO次数,相比于一次读一个字节
byte[] buffer = new byte[1024];
int len = inputStream.read(buffer);
System.out.println(len);
if(len == -1) {
break;
}
}
inputStream.close();
}
}
注意:可以看见读取数据量够1024时,就读取1024个字节,不足时读取剩下的数据。读到文件结束标志时返回-1。
一次读取一个字节
public static void main(String[] args) throws IOException {
File file = new File("./test.txt");
//创建操作这个文件的流对象,并且打开这个文件
//可以用file对象,或者路径的方式构造流对象
InputStream inputStream = new FileInputStream(file);
while (true) {
//读文件流
//不带参数以字节为单位,读取后返回
//带数组参数,读取数据存入数组中
int tmp = inputStream.read();
//读到文件结尾返回-1
if(tmp == -1) {
break;
}
System.out.printf("%x\n", tmp);
}
inputStream.close();
}
注意:一次读取一个字节,这里以16进制的方式表示。
OutputStream方法介绍
1)将字节数组写入指定的文件中。
2)将传入的数据b写入指定文件中。
3)往文件中写数据,首先是写到了文件缓冲区中,当这个文件缓冲区到达一定的数据量,就会刷新文件缓冲区,将内容写入文件中(同时清空文件缓冲区内容)。有时候我们为了确保数据写入到了文件中,就可以手动刷新文件缓冲区。close()方法也会触发刷新文件缓冲区的操作。
4)将字节数组,从off位置到len长度写入指定文件。
5)关闭文件,这里原理和字节流的关闭文件一致。
注意:
1)同样的OutputStream也是一个抽象类,使用需要具体的实现类。和上面一样这里使用FileOutputStream。
2)OutputStream在写文件时,如果这个文件不存在就会自动创建这个文件。
3)OutputStream在写文件时,会清空文件中原来的数据。
4)上文中解释了,close()方法的原理,但是像上文的写法,close()方法不一定会执行到。我们为了确保close()方法在不使用文件时一定可以执行,使用带有资源的try () {}的语法。这里流对象使用完就会自动调用close()方法(只有实现了Closeable类的方法,才可以这样写),下面代码就会使用。
代码演示
写入字符
public class IODEmo8 {
public static void main(String[] args) throws IOException {
//为了保证close被执行使用带有资源的try:try() {}
//只有实现了Closeable类的方法,才可以这样写
//这样会自动执行close()方法
File file = new File("E:/tmp/test.txt");
try(OutputStream outputStream = new FileOutputStream(file)) {
outputStream.write(97);
//手动刷新缓冲区
outputStream.flush();
}
//写操作,实际是将内容先写入缓冲区,等待缓冲区到达了一定程度,就会刷新到硬盘中
//flush()方法手动刷新缓冲区
}
}
注意:可以看见字符a已经写入了文件中。
写入字符数组
public class IODEmo8 {
public static void main(String[] args) throws IOException {
File file = new File("E:/tmp/test.txt");
try(OutputStream outputStream = new FileOutputStream(file)) {
outputStream.write(new byte[]{'a', 'b', 'c'});
//手动刷新缓冲区
outputStream.flush();
}
}
}
注意:可以清楚看见abc已经写入了文件,并且清空了文件原有的内容。
字符流
字符流和字节流操作几乎一致,这里不做过多介绍,直接代码演示,大家就可以明白。
读文件:Reader类。
写文件:Writer类或者PrintWriter类。
注意:
1)Writer和Reader都是抽象方法,使用需要其具体的实现类。Writer这里使用FileWrite类,Reader这里使用FileReader类。
2)PrintWriter类可以将字节流转换为字符流,直接将OutputStream字节流对象写入PrintWriter类的构造方法中即可。
代码演示
public class IODEmo8 {
public static void main(String[] args) throws IOException {
File file = new File("E:/tmp/test.txt");
try(PrintWriter printWriter = new PrintWriter(file);
Writer writer = new FileWriter(file);
Reader reader = new FileReader(file)) {
//写入字符串
printWriter.print("aa");
//writer.write("aa");
//刷新缓冲区
printWriter.flush();
//读文件
while (true) {
int tmp = reader.read();
System.out.println(tmp);
if(tmp == -1) {
break;
}
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
再谈Scanner
Scanner之前在使用时,参数写的System.in。这其实就是打开了标准输入流(键盘),close()方法也是关闭的这个流对象。
那我们也可以写我们创建的流对象,它就会从这个对象所打开的文件中读取数据。
代码演示
public class IODEmo8 {
public static void main(String[] args) throws IOException {
File file = new File("E:/tmp/test.txt");
try(InputStream inputStream = new FileInputStream(file)) {
//将指定流对象写入Scanner参数中
Scanner scanner = new Scanner(inputStream);
//读取数据
while (scanner.hasNext()) {
String tmp = scanner.next();
System.out.println(tmp);
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
注意:
这里next()方法的读取规则:一次读取遇到空白符即结束读取,读取的内容不包含空白符。
案例一
扫描指定目录,并找到名称中包含指定字符的所有指定文件(不包含目录)
核心思想:
进入指定目录后,显示目录中所有文件的File对象。遍历File数组,是目录就递归进去,否则判断文件名字是否包含指定字符,是就删除文件即可。
代码实现
public class IODemo5 {
private static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) {
System.out.println("请输入你要扫描目录的路径:");
String path = scanner.next();
File file = new File(path);
if(!file.isDirectory()) {
System.out.println("输入错误");
return;
}
System.out.println("请输入你要删除的文件的字符");
String str = scanner.next();
fun(file, str);
}
private static void fun(File root, String str) {
//获得该目录下所有文件包括目录对象
File[] files = root.listFiles();
//递归结束
if(files == null) {
return;
}
for(File file : files) {
//是目录就递归
if(file.isDirectory()) {
fun(file, str);
}else {
//删除文件
String name = file.getName();
if(name.contains(str)) {
file.delete();
}
}
}
}
}
案例二
拷贝文件到指定路径。
核心思想:
提高拷贝效率(减少IO次数,需要消耗系统资源)。设置缓冲数组,读数据到缓冲数组,将缓冲数组写入指定路径。直到文件读取结束,拷贝也就结束。
代码实现
public class IODemo6 {
private static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) {
System.out.println("输入你要拷贝文件路径");
String srcPath = scanner.next();
File srcFile = new File(srcPath);
//如果不是文件
if(!srcFile.isFile()) {
System.out.println("输入错误");
return;
}
System.out.println("输入拷贝目的地路径");
String destPath = scanner.next();
File destFile = new File(destPath);
//如果拷贝目的文件已存在
if(destFile.isFile()) {
System.out.println("输入错误");
return;
}
copyFiles(srcFile, destFile);
}
private static void copyFiles(File srcFile, File destFile) {
//边读边拷贝
//读源头文件,写目的地文件
try(InputStream inputStream = new FileInputStream(srcFile);
OutputStream outputStream = new FileOutputStream(destFile)) {
//边读边写
while (true) {
byte[] buffer = new byte[1024];
int len = inputStream.read(buffer);
if(len == -1) {
break;
}
outputStream.write(buffer);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
小结:
初心不变,奋斗不止!与大家共勉。