我们常说的I/O,指的就是,Input/Output。那么针对硬盘持久化存储的I/O设备,当我们进行数据保存的时候,往往保存的是独立成一个个单位进行保存,而不是保存一个整体。这种独立的单位就被抽象成文件的概念。
从狭义来说,我们通常说的文件,指的是存储在硬盘上的“数据” 普通文件。
从广义来说,文件概念会广泛,操作系统会把很多的 硬件设备/软件资源,也给抽象成文件,例如,读写 网卡,网络编程,就操作系统而言会把这些硬件设备/软件资源也给抽象成文件。
抽象文件能够让系统更加的统一和管理。
文件除了有数据内容外,还有一部份的信息,such as 文件名,日期,类型,文件大小等并不作为文件的数据而存在,我们把这部分的信息称之为文件的 元信息
通过这一个个的文件,我们可以将这些文件给组织起来,更方便用户使用它,逻辑上也更好的理解它。
同时,随着文件越来越多,对文件的系统管理也被提上了日程,如何进行文件的组织呢,一种合乎自然
的想法出现了,就是按照层级结构进行组织 —— 也就是我们数据结构中学习过的树形结构。这样,一
种专门用来存放管理信息的特殊文件诞生了,也就是我们平时所谓文件夹(folder)或者目录(directory)的
概念。
Windows的树形结构组织:
Linux上的树形结构组织:
文件的路径
文件路径分为两种:
1、绝对路径(以 / 开头 或者 以盘符开头):从树根出发,依次记录下中间经历的路径。
2、相对路径(以 . 或者 … 开头):从当前工作目录出发,中间经历的路径。
/ 基准所在的最顶级目录即根目录
./ 基准所在的当前目录
…/ 基准所在的当前目录的上一级目录(当前目录的父级目录)
文件的类型:
文本文件,操作文件的时候,基本单位是 “字符”
。
二进制文件,操作文件的时候,基本单位是“字节”
。
如何在java中操作文件??
1、在文件系统的层面上来操作文件
创建文件、删除文件、创建目录、拷贝文件、重命名文件…
2、操作文件的内容(操作文件里面保存的数据)
读文件,写文件。
在java标准库中,提供了 java.io.File 这样的类,来描述以及操作文件;(io----->input/output,输入输出)
通过一个File类实例,就可以描述一个具体的文件。
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 对象的修饰过的绝对路径 |
重点说一下 getAbsolutePath() 和 getCanonicalPath():
1、getAbsolutePath() 表示的是绝对路径,eg:c:/././././text.txt
2、getCanonicalPath() 表示修饰过的绝对路径,此时就会对绝对路径进行修改简介,eg:C:/text.txt
看一下方法的实现:
import java.io.File;
import java.io.IOException;
public class Demo01 {
public static void main(String[] args) throws IOException {
File file1 =new File("d:/text.txt"); // 绝对路径
System.out.println(file1.getParent());
System.out.println(file1.getName());
System.out.println(file1.getPath());
System.out.println(file1.getAbsolutePath());
System.out.println(file1.getCanonicalPath());
System.out.println("-------------------------------------");
File file2 = new File("./text.txt");// 有File对象,但是并不代表真实存在该文件。
System.out.println(file2.getParent());
System.out.println(file2.getName());
System.out.println(file2.getPath());
System.out.println(file2.getAbsolutePath());
System.out.println(file2.getCanonicalPath());
}
}
这是打印出来的结果,大家亦可以注意到,在IDEA里面我们用的是 “/”
,但是打印出来是 “\”
。
大部分都是使用 ”/ “ 作为目录之间的分隔符;但是对于 Windows来说 “\” 作为分隔符,这要扯就要扯到Windows的历史了…(锅是产品经理的锅)
所以记住结论:
- Windows 是 “\” 作为分割符
- Linux/Mac 大部分的操作系统都是 “/” 作为分隔符
❓❓❓❓ 为啥大部分的操作系统都是使用 “/” 分割符??
很简单,在编程语言中 “\” 表示的是转义字符亚,转义字符都是有特殊含义的,可能在编程中你非要写 “\” 那么你就得加两个“\ \” 。
boolean | exists() | 判断 File 对象描述的文件是否真实存在 |
---|---|---|
boolean | isDirectory() | 判断 File 对象代表的文件是否是一个目录 |
boolean | isFile() | 判断 File 对象代表的文件是否是一个普通文件 |
boolean | createNewFile() | 根据 File 对象,自动创建一个空文件。成功创建后返 回 true |
boolean | delete() | 根据 File 对象,删除该文件。成功删除后返回 true |
String[] | list() | 返回 File 对象代表的目录下的所有文件名 |
File[] | listFiles() | 返回 File 对象代表的目录下的所有文件,以 File 对象 表示 |
import java.io.File;
import java.io.IOException;
public class Demo02 {
public static void main(String[] args) throws IOException {
File file1 = new File("./text.txt");
System.out.println(file1.exists()); //false
System.out.println(file1.isDirectory()); // false
System.out.println(file1.isFile()); // false
System.out.println("=========================");
System.out.println(file1.createNewFile()); // 创建了一个新文件 true
System.out.println(file1.exists()); // true
System.out.println(file1.isDirectory()); // false
System.out.println(file1.isFile()); //true
System.out.println("删除文件之前");
System.out.println(file1.delete()); // true
System.out.println("删除文件之后");
System.out.println(file1.exists()); // false
}
}
注意:
1、file.createNewFile() 创建文件的操作是不一定成功的,可能会导致没有权限,创建失败,这里就需要直接抛出IOException异常了。
2、delete()也不一定轻易删除文件成功,一方面是权限,一方面是你的文件路径不对
void | deleteOnExit() | 根据 File 对象,标注文件将被删除,删除动作会到 JVM 运行结束时才会进行 |
---|
这个方法可以说一下,这个方法操作不是立即删除,而是等程序退出的时候再删除,这个功能一般适用于临时文件。
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
public class Demo04 {
public static void main(String[] args) throws IOException, InterruptedException {
File file1 = new File("./111.txt");
file1.createNewFile();
file1.deleteOnExit();
TimeUnit.MILLISECONDS.sleep(3000);//等待3000毫秒
}
}
可以从动画中看出 deleteOnExit()
这个功能 会在程序结束的时候把临时文件删除
boolean | mkdir() | 创建 File 对象代表的目录 |
---|---|---|
boolean | mkdirs() | 创建 File 对象代表的目录,如果必要,会创建中间目 录 |
创建目录,mkdir()只能创建一级目录;
mkdirs()创建多级目录 ,eg:./111/222/333/
import java.io.File;
public class Demo05 {
public static void main(String[] args){
File f1 = new File("./111/22/33/");
f1.mkdirs();
System.out.println(f1.isDirectory());
}
}
boolean | renameTo(File dest) | 进行文件改名,也可以视为我们平时的剪切、粘贴操 作 |
---|
重命名文件。
import java.io.File;
public class Demo06 {
public static void main(String[] args) {
File f1 = new File("./text.txt");
File f2 = new File("./text2.txt");
f1.renameTo(f2);
}
}
把文件 f1 的名字重命名为 f2 的名字。
字节流中 InputStream(负责读) / OutputStream(负责写) ,这个系列是针对二进制文件进行读写,基本单位都是字节。
主要的功能实现:
1、打开文件(构造方法)
2、读文件
3、写文件
4、关闭文件
方法:
修饰符及 返回值类 型 | 方法签名 | 说明 |
---|---|---|
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() | 关闭字节流 |
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class Demo07 {
public static void main(String[] args) {
/**
* InputStream 输入流,是一个抽象类,要使用需要具体实现类。
* FileInputStream 是一个典型的实现
*/
InputStream inputStream = null;
try {
inputStream = new FileInputStream("./text.txt");
// 需要读文件,就需要字节一个一个的读出来
while (true){
// 为什么是 int接收 ,看一下 read()的注释
int b = inputStream.read();
if (b == -1){
break;
}
System.out.println(b);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
InputStream 抽象类起到总览全局的作用,要使用需要具体是实现类,FileInputStream就是只关心文件中的读取,所以FileInputStream就是一个典型的类实现。
签名 | 说明 |
---|---|
FileInputStream(File file) | 利用 File 构造文件输入流 |
FileInputStream(String name) | 利用文件路径构造文件输入流 |
重点说一说 read()
方法,该操作的核心方法提供了三个版本 read(byte[] b) 、read()、read(byte[] b ,int off , int len)
,现讲讲这段代码的版本。
❔❔❔❔❔ 尽然是读字节 为啥不用 byte ,而用 int 尼???
进入 read()源码,用的是 int 类型接收;int 的 Range (0~255),byte 的 Range(-128~127),此处不许要参与运算,因为读出来的只是一个单纯的 字节。通过翻译一颗得知读取到文件末尾没有字节就返回 -1 ,得到 -1 就表示这个文本文件读完了。
在进行各种各样输入输出的操作中,都可能会产生异常,读文件的时候,可能读着读着就磁盘就坏了,这种时候就抛出IOException异常。
运行结果:
我们是以 字节流 来读,那么读出来的结果就是这些字符的 ASCII 码;如果存的是中文,读出来的中文就是每个字节的编码(GBK/UTF8),因为我的IDEA是UTF8所以读出来一个中文三个字节。
第二种写法(简单不冗长):
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class Demo08 {
public static void main(String[] args) {
try (InputStream inputStream = new FileInputStream("./text.txt")){
while(true){
int b = inputStream.read();
if (b == -1){
break;
}
System.out.println(b);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
把要释放的资源放到 try()
里面,然后就会自动调用到关闭,无论是否异常,这种写法 JDK1.5就有了。
前提是 try()里的对象能够实现 Closeable
接口,但是文件流对象,都是实现了 Closeable 接口的。
这个方法一次读若干个字节,尽可能的填满这个字节数组,方法返回值是实际读到的字节数。
1、此处的参数 b 是一个用来表示方法返回结果的参数,称之为“输出型参数”。
2、如果文件中剩余的数据比较多,超过了参数数组的长度,就会直接返回数组长度(把数组填满)。
3、如果文件中剩余的数据比较少,不超过参数数组的长度,此时就会直接返回实际的元素个数。
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class Demo09 {
public static void main(String[] args){
// 尝试一次性读取 1024 个字节
try(InputStream inputStream = new FileInputStream("./text.txt")) {
byte[] buffer = new byte[1024];
while (true){
// 因为实际长度 < 1024 所以返回的是实际元素个数
int len = inputStream.read(buffer);
if (len == -1){
break;
}
// 这个操作是把 1024个数组全部遍历打印出来
// for (byte b: buffer){
// System.out.println(b);
// }
// 如需只打印字符串,直接转换即可
String str = new String(buffer,0,len,"UTF-8");
System.out.println(str);
}
}catch(IOException e){
e.printStackTrace();
}
}
}
InputStream 这个是按照字节来读取,我们也可以通过 Reader/ FileReader 按照字符
来读。
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class Dome10 {
public static void main(String[] args){
try(FileReader fileReader = new FileReader("./text.txt")) {
while(true){
// 这里也是通过 int 来接收 字符
int c = fileReader.read();
if (c == -1){
break;
}
// 需要转换成 char 类型才行,不然就是 ascii 码值。
System.out.println((char)c);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
总结下来:读字节,一次读的是一个 byte ,读字符,一次读的是一个 char。
按照字节来写:OutputSteam / FileOutputStream。
修饰 符及 返回 值类 型 | 方法签名 | 说明 |
---|---|---|
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 打开的文件就会默认清空之前这个文件。
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class Demo11 {
public static void main(String[] args){
try (OutputStream outputStream = new FileOutputStream("./text.txt")){
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
outputStream.flush();
} catch (FileNotFoundException e) { // FileNotFoundException 是 IOException的子类是可以合并简化的。
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
按照字符来写:Writer / FileWriter
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class Demo12 {
public static void main(String[] args){
try(Writer writer = new FileWriter("./text.txt")) {
writer.write('x');
writer.write('x');
writer.write('x');
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
1、读文件,InputStream / FileInputStream / Reader / FileReader ------> read
2、写文件,OutputStream / FileOutputStream / Writer / FileWriter ------> write
扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件。
步骤:
1、让用户输入一个带扫描的路径;
2、判断路径是否合法;
3、输入要删除的文件;
4、遍历当前目录,遍历所有文件名和带删除文件名匹配的文件;
5、进行删除操作,把result里找到所有文件,都依次进行删除。
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class DeleteFile {
public static void main(String[] args) throws IOException {
// 1、先让用户输入带扫描的目录路径
Scanner scan = new Scanner(System.in);
System.out.println("请输入要扫描的目录路径");
String rootDirPath = scan.next();
// 2、判断文件的路径合不合法
File rootDir = new File(rootDirPath);
if (!rootDir.isDirectory()){
System.out.println("你扫描的目录路径不合法,请重新输入目录路径");
return;
}
// 3、输入要删除的文件
System.out.println("请输入你要删除的文件名:");
String deleteFileName = scan.next();
// 4、遍历当前这个目录下的文件,找到所有文件名和待删除文件匹配的文件。
List<File> result = new ArrayList<>();
// 通过 scanDir 这个方法, 把所有和 deleteFileName 匹配的文件, 都给找出来, 放到 result 中.
scanDir(rootDir, deleteFileName ,result);
// 5、进行文件的删除操作,把result 里面找到的文件,依次删除.
for (File f:result){
System.out.println(f.getCanonicalPath()+"该文件是否删除?Y/N");
String choice = scan.next();
if (choice.equals("Y")){
f.delete();
System.out.println(f.getCanonicalPath()+"该文件删除成功!!");
}
}
}
public static void scanDir(File rootDir ,String deleteFileName , List<File> result) throws IOException {
// 1、首先罗列出 rootDir 目录下的文件 ----》 需要用到方法 :listFiles()
File[] files = rootDir.listFiles();
// 遍历,扫描所有文件
for (File f:files) {
System.out.println("扫描了文件:"+f.getCanonicalPath());
// 判断是否是普通文件
if(f.isFile()){
// 判定文件名字
if (f.getName().equals(deleteFileName)){
result.add(f);
}
// 文件是目录就递归
} else if(f.isDirectory()) {
scanDir(f,deleteFileName,result);
}
}
}
}
步骤:
1、输入需要复制的原文件以及目标文件;
2、判定 原文件是否存在;
3、进行复制操作,用到InputStream(负责读),OutputStream(负责写);
import java.io.*;
import java.util.Scanner;
public class CopyFile {
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
System.out.println("请输入原文件的路径");
String src = scan.next();
System.out.println("请输入目标文件的路径");
String destination = scan.next();
// 判断 src 是否存在
File srcFile = new File(src);
if (!srcFile.exists()) {
System.out.println("当前路径文件不存在,请重新输入");
return;
}
// 进行复制操作,关键用到 InputStream and OutputStream
try(InputStream inputStream = new FileInputStream(src)) {
try(OutputStream outputStream = new FileOutputStream(destination)) {
byte[] buffer = new byte[1024];
while(true){
int len = inputStream.read(buffer);
if (len == -1){
break;
}
// 读取数据
outputStream.write(buffer,0,len);
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)
步骤:
1、输入目录和要查找的内容;
2、进行递归遍历,找到所有符合要求的文件(这里面实际了两个方法);
3、打印找到的结果;
其实就是案例一 和 案例二 的结合。
import java.io.*;
import java.sql.Array;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class FindContentFile {
public static void main(String[] args) throws IOException {
Scanner scan = new Scanner(System.in);
System.out.println("请输入要遍历的目录");
String rootDirPath = scan.next();
System.out.println("请输入要指定查找的内容");
String content = scan.next();
// 判断一下 目录路径是否存在
File rootDir = new File(rootDirPath);
if (!rootDir.exists()){
System.out.println("您输入的路径不存在!");
return;
}
// 存在进行递归遍历
List<File> results = new ArrayList<>();
scanDirWithContent(rootDir,content,results);
// 打印文件结果
for (File f:results){
System.out.println(f.getCanonicalPath());
}
}
private static void scanDirWithContent(File rootDir,String content,List<File> results){
// 列出 rootDir 都有哪些文件
File[] files = rootDir.listFiles();
if (rootDir == null){
return;
}
// 2. 依次遍历每个文件, 进行判定. 如果是目录就进行递归.
for (File f:files){
if(f.isFile()){
// 是普通文件就进行判断 content 是否包含其中
if (isContentExist(f,content)){
results.add(f);
}
} else if(f.isDirectory()){
scanDirWithContent(f,content,results);
}
}
}
private static boolean isContentExist(File f, String content) {
StringBuilder sb = new StringBuilder();
try(Reader reader = new FileReader(f)){
while (true){
int c = reader.read();
if (c == -1){
break;
}
sb.append((char) c);
}
} catch (IOException e) {
e.printStackTrace();
}
// indexOf 如果找到了子串,就返回正常的下标,没找的就返回-1;
return sb.indexOf(content) != -1;
}
}
注意:这段代码是低效代码,尽量不要在太复杂的目录下或者大文件下实验 。
这篇帖子介绍了文件操作以及I/O流的基本用法和基础概念,都是必掌握。
铁汁们,觉得笔者写的不错的可以点个赞哟❤,收藏关注呗,你们支持就是我写博客最大的动力!!!!