我们先来认识狭义上的⽂件(file)。针对硬盘这种持久化存储的I/O设备,当我们想要进⾏数据保存时,往往不是保存成⼀个整体,⽽是独⽴成⼀个个的单位进⾏保存,这个独⽴的单位就被抽象成⽂件的概念,就类似办公桌上的⼀份份真实的⽂件⼀般.
⽂件除了有数据内容之外,还有⼀部分信息,例如⽂件名、⽂件类型、⽂件⼤⼩等并不作为⽂件的数据⽽存在,我们把这部分信息可以视为⽂件的元信息.
树型结构组织 和 ⽬录
同时,随着⽂件越来越多,对⽂件的系统管理也被提上了⽇程,如何进⾏⽂件的组织呢,⼀种合乎⾃然的想法出现了,就是按照层级结构进⾏组织⸺也就是我们数据结构中学习过的树形结构。这样,⼀种专⻔⽤来存放管理信息的特殊⽂件诞⽣了,也就是我们平时所谓⽂件夹(folder)或者⽬录(directory)的概念。
例如:我们点开一个文件,里面又有很多个文件,就和树的子类一样
文件路径(Path)
如何在⽂件系统中如何定位我们的⼀个唯⼀的⽂件就成为当前要解决的问题,但这难不倒计算机科学家,因为从树型结构的⻆度来看,树中的每个结点都可以被⼀条从根开始,⼀直到达的结点的路径所描述,⽽这种描述⽅式就被称为⽂件的绝对路径(absolute path)。
除了可以从根开始进⾏路径的描述,我们可以从任意结点出发,进⾏路径的描述,⽽这种描述⽅式就被称为相对路径(relative path),相对于当前所在结点的⼀条路径。
其他知识
即使是普通⽂件,根据其保存数据的不同,也经常被分为不同的类型,我们⼀般简单的划分为⽂本⽂件和⼆进制⽂件,分别指代保存被字符集编码的⽂本和按照标准格式保存的⾮被字符集编码过的⽂件。
Windows操作系统上,会按照⽂件名中的后缀来确定⽂件类型以及该类型⽂件的默认打开程序。但这个习俗并不是通⽤的,在OSX、Unix、Linux等操作系统上,就没有这样的习惯,⼀般不对⽂件类型做如此精确地分类。
⽂件由于被操作系统进⾏了管理,所以根据不同的⽤⼾,会赋予⽤⼾不同的对待该⽂件的权限,⼀般地可以认为有可读、可写、可执⾏权限。
Windows操作系统上,还有⼀类⽂件⽐较特殊,就是平时我们看到的快捷⽅式(shortcut),这种⽂件只是对真实⽂件的⼀种引⽤⽽已。其他操作系统上也有类似的概念,例如,软链接(soft link)等。
最后,很多操作系统为了实现接⼝的统⼀性,将所有的I/O设备都抽象成了⽂件的概念,使⽤这⼀理念最为知名的就是Unix、Linux\操作系统⸺万物皆⽂件。
Java操作文件(正文)
Java中通过 java.io.File 类来对⼀个⽂件(包括⽬录)进⾏抽象的描述。注意,有File?对象,
并不代表真实存在该⽂件。
File 概述
我们先来看看 File 类中的常⻅属性、构造⽅法和⽅法
属性
修饰符及类型 属性 说明
static String pathSeparator 依赖于系统的路径分隔符,String类型的表⽰
static char pathSeparator 依赖于系统的路径分隔符,char类型的表⽰
构造方法
签名 说明
File(File parent,String child) 根据⽗⽬录+孩⼦⽂件路径,创建⼀个新的File实例
File(String pathname) 根据⽂件路径创建⼀个新的File实例,路径可以是绝对路径或者相对路径
File(String parent,String child) 根据⽗⽬录+孩⼦⽂件路径,创建⼀个新的File实
例,⽗⽬录⽤路径表⽰
方法
修饰符及返回值类型 ⽅法签名 说明
String getParent() 返回File对象的⽗⽬录⽂件路径
String getName() 返回FIle对象的纯⽂件名称
String getPath() 返回File对象的⽂件路径
String getAbsolutePath() 返回File对象的绝对路径
String getCanonicalPath() 返回File对象的修饰过的绝对路径
boolean exists() 判断File对象描述的⽂件是否真实存在
boolean isDirectory() 判断File对象代表的⽂件是否是⼀个⽬录
boolean isFile() 判断File对象代表的⽂件是否是⼀个普通⽂件
boolean createNewFile() 根据File对象,⾃动创建⼀个空⽂件。成功创建后返回true
boolean delete() 根据File对象,删除该⽂件。成功删除后返回true
void deleteOnExit() 根据File对象,标注⽂件将被删 除,删除动作会到JVM运⾏结束时才会进⾏
String[] list() 返回File对象代表的⽬录下的所有⽂件名
File[] listFiles() 返回File对象代表的⽬录下的所有⽂件,以File对象表⽰
boolean mkdir() 创建File对象代表的⽬录
boolean mkdirs() 创建File对象代表的⽬录,如果必要,会创建中间⽬录
boolean renameTo(File dest) 进⾏⽂件改名,也可以视为我们平时的剪切、粘贴操作
boolean canRead() 判断⽤⼾是否对⽂件有可读权限
boolean canWrite() 判断⽤⼾是否对⽂件有可写权限
File的方法代码示例:
1.(示例)观察get系列的特点和差异
import java.io.File;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
File file = new File("..\\hello-world.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());
}
}
运行结果:
..
hello-world.txt
..\hello-world.txt
D:\代码练习\⽂件⽰例1\..\hello-world.txt
D:\代码练习\hello-world.txt
2.(示例)普通文件的创建和删除
import java.io.File;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
File file = new File("hello-world.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());
System.out.println(file.createNewFile());
}
}
运行结果:
false
false
false
true
true
false
true
false
3.(示例)普通文件的删除
import java.io.File;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
File file = new File("some-file.txt"); // 要求该⽂件不存在,才能看到相
System.out.println(file.exists());
System.out.println(file.createNewFile());
System.out.println(file.exists());
System.out.println(file.delete());
System.out.println(file.exists());
}
}
运行结果:
false
true
true
true
false
4.(示例)观察deleteOnExit的现象
import java.io.File;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
File file = new File("some-file.txt"); // 要求该⽂件不存在,才能看到相
System.out.println(file.exists());
System.out.println(file.createNewFile());
System.out.println(file.exists());
file.deleteOnExit();
System.out.println(file.exists());
}
}
运行结果:
false
true
true
true
程序运⾏结束后,⽂件还是被删除了
5.(示例)观察目录的创建
import java.io.File;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
File dir = new File("some-dir"); // 要求该⽬录不存在,才能看到相同的现
System.out.println(dir.isDirectory());
System.out.println(dir.isFile());
System.out.println(dir.mkdir());
System.out.println(dir.isDirectory());
System.out.println(dir.isFile());
}
}
运行结果:
false
false
true
true
false
6.(示例)观察目录创建2
import java.io.File;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
File dir = new File("some-parent\\some-dir"); // some-parent 和 so
System.out.println(dir.isDirectory());
System.out.println(dir.isFile());
System.out.println(dir.mkdir());
System.out.println(dir.isDirectory());
System.out.println(dir.isFile());
}
}
运行结果:
false
false
false
false
false
mkdir()的时候,如果中间⽬录不存在,则⽆法创建成功;mkdirs()可以解决这个问题。
import java.io.File;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
File dir = new File("some-parent\\some-dir"); // some-parent 和 so
System.out.println(dir.isDirectory());
System.out.println(dir.isFile());
System.out.println(dir.mkdirs());
System.out.println(dir.isDirectory());
System.out.println(dir.isFile());
}
}
运行结果:
false
false
true
true
false
7.(示例)观察文件重命名
import java.io.File;
import java.io.IOException;
public class Main {
public static void main(String[] args) throws IOException {
File file = new File("some-file.txt"); // 要求 some-file.txt 得存在
File dest = new File("dest.txt"); // 要求 dest.txt 不存在
System.out.println(file.exists());
System.out.println(dest.exists());
System.out.println(file.renameTo(dest));
System.out.println(file.exists());
System.out.println(dest.exists());
}
}
运行结果:
true
false
true
false
true
文件内容的读写--数据流
InputStream概述
方法:
说明
InputStream只是⼀个抽象类,要使⽤还需要具体的实现类。关于InputStream的实现类有很多,基本可以认为不同的输⼊设备都可以对应⼀个InputStream类,我们现在只关⼼从⽂件中读取,所以使⽤FileInputStream
FileInputStream概述
代码示例:
(1)
将⽂件完全读完的两种⽅式。相⽐较⽽⾔,后⼀种的IO次数更少,性能更好。
import java.io.*;
// 需要先在项⽬⽬录下准备好⼀个 hello.txt 的⽂件,⾥⾯填充 "Hello" 的内容
public class Main {
public static void main(String[] args) throws IOException {
try (InputStream is = new FileInputStream("hello.txt")) {
while (true) {
int b = is.read();
if (b == -1) {
// 代表⽂件已经全部读完
break;
}
System.out.printf("%c", b);
}
}
}
}
import java.io.*;
// 需要先在项⽬⽬录下准备好⼀个 hello.txt 的⽂件,⾥⾯填充 "Hello" 的内容
public class Main {
public static void main(String[] args) throws IOException {
try (InputStream is = new FileInputStream("hello.txt")) {
byte[] buf = new byte[1024];
int len;
while (true) {
len = is.read(buf);
if (len == -1) {
// 代表⽂件已经全部读完
break;
}
for (int i = 0; i < len; i++) {
System.out.printf("%c", buf[i]);
}
}
}
}
}
(2)
这⾥我们把⽂件内容中填充中⽂看看,注意,写中⽂的时候使⽤UTF-8编码。text.txt中填写"你好
中国"
注意:这⾥我利⽤了这⼏个中⽂的UTF-8编码后⻓度刚好是3个字节和⻓度不超过1024字节的现状,但这种⽅式并不是通⽤的
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
public class Demo94 {
public static void main(String[] args) throws IOException {
try(InputStream inputStream = new FileInputStream("./text.txt")){
byte[] buffer = new byte[1024];
int len =0;
while(true ){
len = inputStream.read(buffer);
if(len==-1){
break;
}
for (int i = 0; i <len ; i+=3) {
String s = new String(buffer,i,3);
System.out.println(s );
}
}
}
}
}
利⽤Scanner进⾏字符读取
上述例⼦中,我们看到了对字符类型直接使⽤InputStream进⾏读取是⾮常⿇烦且困难的,所以,我们使⽤⼀种我们之前⽐较熟悉的类来完成该⼯作,就是Scanner类
构造⽅法 说明
Scanner(InputStream is,String charset) 使⽤charset字符集进⾏is的扫描读取
示例:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class Demo83 {
public static void main(String[] args) throws FileNotFoundException {
try(InputStream inputStream = new FileInputStream("./text.txt")){
Scanner scanner = new Scanner(inputStream);
while(scanner.hasNext()){
String s = scanner.next();
System.out.print(s);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
OutputStream概述
方法
说明:
OutputStream同样只是⼀个抽象类,要使⽤还需要具体的实现类。我们现在还是只关⼼写⼊⽂件
中,所以使⽤FileOutputStream
⽤OutputStreamWriter进⾏字符写⼊
示例:
(1)
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class Demo76 {
public static void main(String[] args) throws FileNotFoundException {
try (OutputStream outputStream = new FileOutputStream("./text.txt")){
outputStream.write('1');
outputStream.write('s');
outputStream.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
(2)
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class Demo86 {
public static void main(String[] args) {
try(OutputStream outputStream = new FileOutputStream("./text.txt",true)){
byte[] buffer = new byte[]{'a','v','s','y'};
outputStream.write(buffer);
outputStream.flush();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
(3)
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
try (OutputStream os = new FileOutputStream("output.txt")) {
byte[] b = new byte[] {
(byte)'G', (byte)'o', (byte)'o', (byte)'d', (byte)'B', (byte)'a'
};
os.write(b, 0, 4);
// 不要忘记 flush
os.flush();
}
}
}
(4)
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
try (OutputStream os = new FileOutputStream("output.txt")) {
String s = "你好中国";
byte[] b = s.getBytes("utf-8");
os.write(b);
// 不要忘记 flush
os.flush();
}
利⽤PrintWriter找到我们熟悉的⽅法
上述,我们其实已经完成输出⼯作,但总是有所不⽅便,我们接来下将OutputStream处理下,使⽤PrintWriter类来完成输出,因为PrintWriter类中提供了我们熟悉的print/println/printf⽅法
import java.io.*;
public class Demo856 {
public static void main(String[] args) throws FileNotFoundException {
try(OutputStream outputStream = new FileOutputStream("./text.txt")){
PrintStream printStream = new PrintStream(outputStream);
printStream.printf("我是第一行");
printStream.println("我是第二行");
printStream.println("我是第三行");
outputStream.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
小程序练习
⽰例1
扫描指定⽬录,并找到名称中包含指定字符的所有普通⽂件(不包含⽬录),并且后续询问⽤⼾是否要删除该⽂件
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Demo867 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要搜索的目录根节点");
String rootDirPath = scanner.next();
File rootDir = new File(rootDirPath);
if(rootDir.isDirectory()){
System.out.println("不是目录,退出");
return;
}
System.out.println("请输入要搜索文件包含的名字");
String token = scanner.next();
List<File> result = new ArrayList<>();
scanDir(rootDir,token,result);
System.out.println("共找到了->");
for (File file:result
) {
System.out.println("请问您是否需要删除该文件");
String str = scanner.next();
if(str.toLowerCase().equals("yes")){
file.delete();
}
}
}
private static void scanDir(File rootDir, String token, List<File> result) {
File[] files = rootDir.listFiles();
if(files==null || files.length==0){
return;
}
for (int i = 0; i < files.length; i++) {
if(files[i].isDirectory()){
scanDir(files[i],token,result);
}
else if(files[i].getName().contains(token)){
result.add(files[i]);
}
}
}
}
⽰例2
进⾏普通⽂件的复制
import java.io.*;
import java.util.Scanner;
public class Demo462 {
public static void main(String[] args) throws FileNotFoundException {
Scanner scanner =new Scanner(System.in);
System.out.println("请输入要复制的文件的目录");
String rootDirPath = scanner.next();
File rootDir = new File(rootDirPath);
if(!rootDir.exists()){
return;
}
if(!rootDir.isFile()){
return;
}
System.out.println("请输入要复制到的路径");
String destDirPath = scanner.next();
File destDir = new File(destDirPath);
if(!destDir.exists()){
return;
}
if(destDir.isDirectory()){
return;
}
try(InputStream inputStream = new FileInputStream(rootDir);
OutputStream outputStream = new FileOutputStream(destDir)){
byte[] buffer = new byte[1024];
int b = 0;
while(true){
b = inputStream.read(buffer );
if(b==-1){
break;
}
outputStream.write(buffer);
}
outputStream.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
⽰例3
扫描指定⽬录,并找到名称或者内容中包含指定字符的所有普通⽂件(不包含⽬录)
注意:我们现在的⽅案性能较差,所以尽量不要在太复杂的⽬录下或者⼤⽂件下实验
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Demo853 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要搜索的目录根节点");
String rootDirPath = scanner.next();
File rootDir = new File(rootDirPath);
if(rootDir.isDirectory()){
System.out.println("不是目录,退出");
return;
}
System.out.println("请输入要搜索文件包含的名字");
String token = scanner.next();
List<File> result = new ArrayList<>();
scanDir(rootDir,token,result);
System.out.println("共找到了->");
for (File file:result
) {
System.out.println("请问您是否需要删除该文件");
String str = scanner.next();
if(str.toLowerCase().equals("yes")){
file.delete();
}
}
}
private static void scanDir(File rootDir, String token, List<File> result) {
File[] files = rootDir.listFiles();
if(files==null || files.length==0){
return;
}
for (int i = 0; i < files.length; i++) {
if(files[i].isDirectory()){
scanDir(files[i],token,result);
}
else if(true){
StringBuffer stringBuffer = new StringBuffer(files[i].getName());
if(stringBuffer.indexOf(token) != -1){
result.add(files[i]);
}
}
else if(isContain(files[i],token)){
result.add(files[i].getAbsoluteFile());
}
}
}
private static boolean isContain(File file, String token) {
StringBuffer stringBuffer = new StringBuffer();
try(InputStream inputStream = new FileInputStream(file)){
Scanner scanner = new Scanner(inputStream);
while(scanner.hasNext()){
String str = scanner.nextLine();
stringBuffer.append(str);
}
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
return stringBuffer.indexOf(token) != -1;
}
}