文章目录
1. 认识文件
针对硬盘这种持久化存储的I/O设备,当我们想要进行数据保存时,往往不是保存成一个整体,而是独立成一个个的单位进行保存,这个独立的单位就被抽象成文件的概念,类似于办公桌上的一份份真实的文件
文件除了有数据内容之外,还有一部分信息,例如文件名,文件类型,文件大小等并不作为文件的数据而存在,我们把这部分信息可以视为文件的元信息
文件的组织形式:按照层级结构进行组织,也就是树形结构,这样一种专门用来存放管理信息的特殊文件也就是我们平时所谓的文件夹或者目录
文件路径:从树形结构的角度来看,树中的每个节点都可以被从一条根开始,一直到达节点的路径所描述,这种描述方式就被称为文件的绝对路径,除了可以从根开始进行路径的描述,我们可以从任意节点出发,进行路径的描述,而这种描述方式就被称为相对路径,相当于当前所在节点的一条路径
2. Java中操作文件
2.1 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对象代表i的文件是否是一个目录 |
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() | 判断用户是否对文件有可写权限 |
示例1:
get系列方法的特点和差异
public class Main {
public static void main(String[] args) throws IOException {
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());//返回 File 对象的绝对路径
System.out.println(file.getCanonicalPath());//返回 File 对象的修饰过的绝对路径
}
}
//执行结果
.
test.txt
.\test.txt
C:\Users\Freedom\IdeaProjects\test3\.\test.txt
C:\Users\Freedom\IdeaProjects\test3\Test.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());//文件不存在 false
System.out.println(file.isDirectory());//文件不存在,false
System.out.println(file.isFile());//文件不存在 false
System.out.println(file.createNewFile());//创建成功 true
System.out.println(file.exists());//文件存在 true
System.out.println(file.isDirectory());//文件不是目录 fasle
System.out.println(file.isFile());//文件是普通文件 true
System.out.println(file.createNewFile());//文件已经存在,创建失败 false
}
}
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());//文件不存在,false
System.out.println(file.createNewFile());//创建成功 true
System.out.println(file.exists());//文件存在 true
System.out.println(file.delete());//文件删除成功 true
System.out.println(file.exists());//文件不再存在,已被删除,false
}
}
示例3
观察 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());//false
System.out.println(file.createNewFile());//true
System.out.println(file.exists());//true
file.deleteOnExit();//将文件标注为即将删除的文件
System.out.println(file.exists());//true
}
}//代码执行完毕后,才会删除文件,所以上面判断时,文件存在返回true
示例4:
目录的创建
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());//目录不存在,false
System.out.println(dir.isFile());//文件不存在 false
System.out.println(dir.mkdir());//目录创建成功 true
System.out.println(dir.isDirectory());//文件是一个目录,true
System.out.println(dir.isFile());//不是普通文件 false
}
}
示例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-parent\\some-dir"); // some-parent 和 some-dir 都不存在
System.out.println(dir.isDirectory());//false
System.out.println(dir.isFile());//false
System.out.println(dir.mkdir());//false 只能创建一个目录
System.out.println(dir.isDirectory());//false
System.out.println(dir.isFile());//false
}
}
//mkdir() 的时候,如果中间目录不存在,则无法创建成功;
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 和 some-dir 都不存在
System.out.println(dir.isDirectory());//false 文件不存在
System.out.println(dir.isFile());//false 文件不存在
System.out.println(dir.mkdirs());//true 创建多级目录成功
System.out.println(dir.isDirectory());//true 文件是目录
System.out.println(dir.isFile());//false 不是普通文件
}
}
示例6:
文件重命名
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());//true
System.out.println(dest.exists());//false
System.out.println(file.renameTo(dest));//true
System.out.println(file.exists());//false
System.out.println(dest.exists());//true
}
}
2.2 文件内容的读写——数据流
InputStream类
修饰符及返回值类型 | 方法名 | 说明 |
---|---|---|
int | read() | 读取一个字节的数据,返回-1表示读取完毕 |
int | read(byte [] b) | 最多读取b.length字节的数据到b中,返回实际读到的数量,-1代表已经读取完毕了 |
int | read(byte [] b,int off,int len) | 最多读取len-off字节的数据到b中,在从off开始,返回实际读取到的数量,-1代表已经读取完毕了 |
void | close() | 关闭字节流 |
InputStream只是一个抽象类,要使用还需要具体的实现类,关于InputStream的实现类有很多,基本可以认为不同的输入设备都可以对应要给InputStream类,从文件中读取使用FileInputStream
FileInputStream类
构造方法
方法名 | 说明 |
---|---|
FileInputStream(File file) | 利用File构造文件输入流 |
FileInputStream(String name) | 利用文件路径构造文件输入流 |
示例1:
将文件完全读完的两种方式,第二种的IO次数更少,性能更好
public class Main {
public static void main(String[] args) {
try(InputStream inputStream = new FileInputStream("Test.txt")){
//使用try()...catch执行完毕后,自动调用inputStream.close()关闭资源
while(true){
int b = inputStream.read();
if(b == -1){
break;
}
System.out.printf("%c",b);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class Main {
public static void main(String[] args) {
//一次读取1024个字节
try(InputStream inputStream = new FileInputStream("Test.txt")){
byte [] buffer = new byte[1024];
while(true){
int len = inputStream.read(buffer);//返回值是buffer数组的长度
if(len == -1){
break;
}
for (int i = 0; i < len; i++) {//遍历这个buffer数组,打印每个元素
System.out.printf("%c",buffer[i]);
}
}
}catch (IOException e){
e.printStackTrace();
}
}
}
示例2:
这里我们把文件内容中填充中文看看,注意,写中文的时候使用 UTF-8 编码。hello.txt 中填写 “你好中国”
public class Test {
public static void main(String[] args) throws IOException {
try(InputStream inputStream = new FileInputStream("hello.txt")){
byte [] buffer = new byte[1024];
while(true){
int len = inputStream.read(buffer);
if(len == -1){
break;
}
//这个操作只适用于纯中文的文件,如果文件中包含英文加汉字,就会出错
for (int i = 0; i < len; i+=3) {
String s = new String(buffer,i,3,"UTF-8");
System.out.print(s);
}
}
}catch (IOException e){
e.printStackTrace();
}
}
}
注意:这里利用了这几个中文的 UTF-8 编码后长度刚好是 3 个字节和长度不超过 1024 字节的现状,但这种方式并不是通用的
利用Scanner进行字符读取
上述例子中,我们看到了对字符类型直接使用 InputStream 进行读取是非常麻烦且困难的,所以,我们使用一种我们之前比较熟悉的类来完成该工作,就是 Scanner 类。
public class Test11 {
public static void main(String[] args) {
try (InputStream inputStream = new FileInputStream("hello.txt")) {
//scanner不仅可以从控制台读取,也可以从文件读取
//sanner也有close方法,这个close方法也是用来关闭scanner中的inputStream
try (Scanner scanner = new Scanner(inputStream, "UTF-8")) {
while (scanner.hasNext()) {
String s = scanner.next();
System.out.println(s);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
OutputStream类
修饰符及返回值类型 | 方法名 | 说明 |
---|---|---|
void | write(int b) | 写入要给字节的数据 |
void | write(byte [] b) | 将b这个字符数组中的数据全部写入os中 |
int | write(byte [] b,int off,int len) | 将b这个字符数组中从off开始的数据写入os中,一共写len个数据 |
void | close() | 关闭字节流 |
void | flush() | 事实上I/O速度很慢,所以大多的OutputStream为了减少设备操作的次数,在写数据的时候都会将数据先暂时写入内存的一个指定区域里,直到该区域满了摸着其他指定条件时,才真正将数据写入设备中,这个区域一般称为缓冲区。但这可能造成我们写的数据会遗留一部分在缓冲区中,所以我们需要在最后或者合适的位置,调用flush(刷新)操作,将数据刷新到设备中 |
OutputStream同样是一个抽象类,要使用还需要具体的实现类,写入文件使用FileOutputStream,用OutputStream的方式打开文件,会将文件原来的内容清空掉
利用OutputStreamWriter进行字符写入
示例:
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
try (OutputStream os = new FileOutputStream("output.txt")) {
os.write('H');
os.write('e');
os.write('l');
os.write('l');
os.write('o');
// 不要忘记 flush
os.flush();
}
}
}
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'
};
os.write(b);//write(byte [] b)
// 不要忘记 flush
os.flush();
}
}
}
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', (byte)'d'
};
os.write(b, 0, 4);//write(byte [] b,int off,int len)
// 不要忘记 flush
os.flush();
}
}
}
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
try (OutputStream os = new FileOutputStream("output.txt")) {
String s = "Nothing";
byte[] b = s.getBytes();
os.write(b);
// 不要忘记 flush
os.flush();
}
}
}
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写入
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
try (OutputStream os = new FileOutputStream("output.txt")) {
try (OutputStreamWriter osWriter = new OutputStreamWriter(os, "UTF-8")) {
try (PrintWriter writer = new PrintWriter(osWriter)) {
writer.println("我是第一行");
writer.print("我的第二行\r\n");
writer.printf("%d: 我的第三行\r\n", 1 + 1);
writer.flush();
}
}
}
}
}
3. 文件操作练习
示例1:
扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件
public class Test {
public static void main(String[] args) throws IOException {
//1.让用户输入一个待扫描的根目录,和要查询的关键词
System.out.println("请输入要扫描的根目录(绝对路径):");
Scanner scanner = new Scanner(System.in);
String root = scanner.next();
File rootDir = new File(root);
if(!rootDir.isDirectory()){
System.out.println("输入的根目录有误");
return;
}
System.out.println("输入要查找的文件名包含的关键词");
String token = scanner.next();
List<File> result = new ArrayList<>();
//result表示递归遍历的结果,包含所有带有token关键词的文件
//递归遍历目录
scanDir(rootDir,token,result);
//遍历result,根据用户的输入判断是否要删除该文件
for (File file:result) {
System.out.println(file.getCanonicalPath()+ " 是否要删除(Y/N)");
String input = scanner.next();
if(input.equals("Y")){
file.delete();
}
}
}
//递归遍历目录,找出所有符合关键词的文件
private static void scanDir(File rootDir, String token, List<File> result) throws IOException {
//.list()返回的是文件名(String) .listFiles()得到的是file对象,返回值是file类型的数组
File [] files = rootDir.listFiles();
if(files == null || files.length == 0){
return;//说明当前的目录是一个空目录
}
//遍历files数组中的每个文件
for (File file:files) {
//如果当前文件是一个目录,就递归查找
if (file.isDirectory()){
scanDir(file,token,result);
}else{
//如果文件是一个普通文件,就判断文件名是否包含关键词
if(file.getName().contains(token)){
result.add(file.getCanonicalFile());
}
}
}
}
}
示例2:
进行普通文件的复制
public class Test15 {
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;
}
System.out.println("请输入要复制到的目标路径(绝对路径)");
String destPath = scanner.next();
File destFile = new File(destPath);
if (destFile.exists()){
System.out.println("目标路径已存在");
return;
}
//父目录不存在,就创建目录
if(!destFile.getParentFile().exists()){
destFile.mkdirs();
}
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){
//len等于-1表示文件读取完毕
break;
}
outputStream.write(buffer,0,len);
}
}catch(IOException e){
e.printStackTrace();
}
System.out.println("复制完成");
}
}
示例3:
扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)
public class Test16 {
public static void main(String[] args) throws IOException {
//1.用户输入要查询的根目录,判断是否为目录,如果不是,就退出程序
System.out.println("输入要查询的根目录");
Scanner scanner = new Scanner(System.in);
String root = scanner.next();
File rootDir = new File(root);
if(!rootDir.isDirectory()){
System.out.println("输入的目录有误");
return;
}
//2.用户输入要查询的关键字
System.out.println("输入要查询的关键字");
String token = scanner.next();
//result用来存储递归查找出的文件结果
List<File> result = new ArrayList<>();
//3.遍历目录,进行匹配判断
scanDir(rootDir,token,result);
//4。打印结果
for (File f:result) {
System.out.println(f.getCanonicalPath());
}
}
private static void scanDir(File rootDir, String token, List<File> result) throws IOException {
File [] files = rootDir.listFiles();//将rootDir中的所有内容以File类型的数组的形式组织
//如果是一个空目录,直接返回
if (files == null || files.length == 0){
return;
}
//files不空,遍历这个目录
for (File f:files) {
if(f.isDirectory()){
//如果是目录,就递归查询
scanDir(f,token,result);
}else{
//是普通文件,判断文件名称中是否包含关键词
if (f.getCanonicalPath().contains(token)){
result.add(f);
//判断文件内容是否包含关键词
}else if(isContentContains(f,token)){
result.add(f);
}
}
}
}
private static boolean isContentContains(File f, String token) {
StringBuilder stringBuilder = new StringBuilder();
//打开f这个文件,依次取出每一行的结果,创建StringBuilder用来拼接每一行的内容,
try(InputStream inputStream = new FileInputStream(f)){
Scanner scanner = new Scanner(inputStream,"UTF-8");
while(scanner.hasNextLine()){
String line = scanner.nextLine();
stringBuilder.append(line + "\n");//拼接line 并且换行
}
}catch (IOException e){
e.printStackTrace();
}
//判断stringBuider中是否包含token,如果不等于-1说明包含,返回true。等于-1,说明不包含
return stringBuilder.indexOf(token) != -1;
//indexOf如果包含会返回具体的下标值,不包含就会返回-1
}
}
代码参考
如何按字节进行数据读
try (InputStream inputStream = ...) {
byte[] buf = new byte[1024];
while (true) {
int n = is.read(buf);
if (n == -1) {
break;
}
// buf 的 [0, n) 表示读到的数据,按业务进行处理
}
}
如何按字节进行数据写
try (OutputStream outputStream = ...) {
byte[] buf = new byte[1024];
while (/* 还有未完成的业务数据 */) {
// 将业务数据填入 buf 中,长度为 n
int n = ...;
os.write(buf, 0, n);
}
os.flush(); // 进行数据刷新操作
}
如何按字符进行数据读
try (InputStream inputStream = ...) {
try (Scanner scanner = new Scanner(is, "UTF-8")) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
// 根据 line 做业务处理
}
}
}
如何按字符进行数据写
try (OutputStream outputStream = ...) {
try (OutputStreamWriter osWriter = new OutputStreamWriter(os, "UTF-8")) {
try (PrintWriter writer = new PrintWriter(osWriter)) {
while (/* 还有未完成的业务数据 */) {
writer.println(...);
}
writer.flush(); // 进行数据刷新操作
}
}
}