目录
1.文件概述
1.1 文件的概念
1.2 文件的存储
1.3 文件的分类
1.4 目录结构
1.5 文件操作
1.5.1 文件系统操作
1.5.2 文件内容操作
2. Java文件系统操作
2.1 File类所处的包
2.2 构造方法
2.3 方法
2.3.1 与文件路径、文件名有关的方法
2.3.2 文件是否存在与普通文件、目录的判定的方法
2.3.3 文件的创建与删除的方法
2.3.4 目录的创建方法
2.3.5 罗列目录文件的方法
2.3.6 文件的重命名操作
3.Java文件内容操作
3.1 FileInputStream 字节流的读操作
3.2 FileOutputStream 字节流的写操作
3.3 Reader 字符流的读操作
3.4 Writer 字符流的写操作
4. 文件操作的应用
1.文件概述
1.1 文件的概念
狭义文件概念:
平时说的文件一般都是存储在硬盘上的普通文件:形如.txt .jpg .mp4 rar等都可认为是普通文件,他们都是在硬盘上存储的;
广义文件概念:
在计算机中文件也可以是一个广义的概念,就不只是包含普通文件,还可以包含目录(目录文件),也就是俗称的文件夹,操作系统中还会使用文件来描述一些其他的硬件设备或软件资源:比如操作系统中将网卡、显示器、键盘等这样的硬件设备也抽象成了一个文件;
当前讨论的文件仍然是针对普通文件;
1.2 文件的存储
普通文件是保存在硬盘上的;
机械硬盘包括磁头与存储数据的介质:盘片,机械硬盘一旦上电,盘片就会高速运转,磁头在盘片上找到对应的数据。
但是由于机械硬盘的机械构造,盘片转速越高,读写速度就越快,但是因为工艺的限制,盘片转速也不可能无限高,机械硬盘的读写速度已经多年停滞不前,而是向大容量方向发展;
故而从硬盘上读取数据就要比从内存读取数据要慢很多,大约是3~4个数量级;
因为flash芯片的使用,固态硬盘的速度就要比机械硬盘高很多;
当前学习中就以机械硬盘为主,即文件数据读取的特点为:存储空间更大,读取速度更慢;
1.3 文件的分类
一般情况下文件主要分为两类:文本文件与二进制文件,两种文件在编程时会存在差异;
文本文件中存储的是字符,二进制文件中存储的是字节;
判断一个文件是文本文件还是二进制文件最简单的方法就是使用记事本打开该文件,如果打开后是乱码则为二进制文件,否则就是文本文件:
日常中使用的.txt .c .java都属于文本文件
而.doc .ppt .exe .zip .class 等都是二进制文件;
(word保存的不是一个单纯的文本,而是一个带有各种格式化信息的“富文本”)
1.4 目录结构
计算机中保存管理文件是通过操作系统中的“文件系统“模块”进行管理的,在文件系统中一般是通过“树形”结构来组织磁盘上的目录和文件的,如:
如果是一个普通文件,就是树的叶子结点;
如果是一个目录文件,目录中就可以包含子树,这个目录就是非叶子结点;
在操作系统中,就通过“路径”概念来描述一个具体文件或目录的位置:
(1)绝对路径:以盘符开头,如:E:\JavaEE_FilesAndIO\src\Main.java 就是一个绝对路径;
(2)相对路径:以.或..开头,其中.表示当前路径,..表示当前路径的父目录(上级路径)。
相对路径实现必须有一个基准目录,相对路径就是从基准目录出发,按照一个路径找到对应文件,
例如:以E:\JavaEE_FilesAndIO为基准目录,找到Main.java文件,就可以表示为.\src\Main.java;
E:\Java_String\src目录中也存在一个Main.java文件,如果此时仍以E:\JavaEE_FilesAndIO为基准目录,找到Main.java文件,就需要表示为:..\src\Main.java;
即:.表示基准路径,..表示基准路径的上一级目录;
即使定位到同一个文件,如果基准目录不同,此时相对路径也不同;
1.5 文件操作
java中的文件操作主要包括两类:文件系统相关操作与文件内容相关操作;
1.5.1 文件系统操作
文件系统操作指的是通过“文件资源管理器”能够完成的一些功能,如:
① 列出目录下的文件 ② 创建文件 ③ 创建目录 ④ 删除目录 ⑤ 重命名文件等等;
Java提供了一个File类来完成上述操作,这个File类描述了一个文件或目录,基于这个对象就可以实现上面的功能;
1.5.2 文件内容操作
类似于C语言的文件操作,即:
① 打开文件 ② 读文件 ③ 写文件 ④ 关闭文件
Java提供了一组类来实现对文件内容的操作:
(1)按照文件内容,分为两个系列:
字节流对象(针对二进制文件,以字节为单位进行读写)与字符流对象(针对文本文件,以字符为单位进行读写);
(2)
以下内容将对于java文件的系统操作与内容操作分别进行阐述:
2. Java文件系统操作
2.1 File类所处的包
import java.io.File;
2.2 构造方法
//绝对路径
File f1= new File("E:/JavaEE_FilesAndIO:/file2.txt");
//相对路径:基准路径取决于运行java程序的方式
File f2= new File("./file3.txt");
运行java程序的方式:
(1)命令行方式(java Demo1),此时执行命令所在的目录就是基准路径,实际不考虑该情况;
(2)IDEA方式,此时基准路径就是当前java项目所在路径,如此时的项目所在位置为:
(3)如果将一个java程序达成war包置于tomcat上运行,此时基准路径就是tomcat的bin目录;
如果路径指定错了,很容易出现找不到文件的情况;
2.3 方法
2.3.1 与文件路径、文件名有关的方法
//绝对路径
File f1= new File("E:/file2.txt");
System.out.println(f1.getParent()); //获取父目录
System.out.println(f1.getName()); //获取文件名
System.out.println(f1.getPath()); //获取文件路径(构造File时指定的路径)
System.out.println(f1.getAbsolutePath());//获取绝对路径
System.out.println(f1.getCanonicalPath());// 获取绝对路径
System.out.println("-----------------------------");
//相对路径:基准路径取决于运行java程序的方式
File f2= new File("./file3.txt");
System.out.println(f2.getParent()); //获取父目录
System.out.println(f2.getName()); //获取文件名
System.out.println(f2.getPath()); //获取文件路径(构造File时指定的路径)
System.out.println(f2.getAbsolutePath());//获取绝对路径
System.out.println(f2.getCanonicalPath());// 获取绝对路径
输出结果为:
注:"/" 读作斜杠, "\"读作反斜杠;
File类中pathSeparator属性就是相邻路径之间的分隔符,一般情况下,大部分操作系统上路径的分隔符都是斜杠,而Windows默认使用的是反斜杠,但斜杠与反斜杠都可被系统识别;
2.3.2 文件是否存在与普通文件、目录的判定的方法
基于当前项目路径下包含的目录与文件如下:
试运行以下代码:
File f= new File("./file1.txt");
System.out.println(f.exists()); //文件是否存在
System.out.println(f.isDirectory()); //文件是否为目录
System.out.println(f.isFile()); // 文件是否是一个普通文件
输出结果为:
2.3.3 文件的创建与删除的方法
File f = new File("./file2.txt");
System.out.println(f.exists());
System.out.println("The file is absent,create a file now.");
f.createNewFile();
System.out.println("Finish creating a file.");
System.out.println(f.exists());
f.delete();
System.out.println("Finish deleting the file");
System.out.println(f.exists());
输出结果为:
注:java还提供了一个deleteOnExit()的方法进行程序退出后再删除,使用较少;
2.3.4 目录的创建方法
(1)单个目录:
File f = new File("./aaa");
f.mkdir(); //创建目录
System.out.println(f.isDirectory()); //判断是否为目录
输出结果为:
(2)多级目录:
File f = new File("./aaa/bbb/ccc/ddd");
f.mkdirs(); //创建多级目录
System.out.println(f.isDirectory()); //判断是否为目录
输出结果为:
可从左侧Project栏查看对应目录与文件:
2.3.5 罗列目录文件的方法
File f = new File("./");
System.out.println(Arrays.toString(f.list())); //返回字符串数组
System.out.println(Arrays.toString(f.listFiles())); //返回File数组
输出结果为:
2.3.6 文件的重命名操作
File f1 = new File("./aaa");
File f2 = new File("./zzz");
f1.renameTo(f2); //将aaa改名为zzz
可从左侧Project栏查看对应目录与文件:
3.Java文件内容操作
3.1 FileInputStream 字节流的读操作
read提供了三个版本的重载:
① 无参:一次读一个字节;
② 一参:一次读若干个字节,将读到的结果存入参数数组中,返回值是读到的字节数;
③ 三参:一次读若干个字节,将读到的结果存入参数数组中,返回值是读到的字节数,可指定元素放置数组的位置下标off以及最多能放多少个元素len
(字符流也有类似的设定,为了方便char类型的读取,选择了int而非short)
基于E:\JavaEE_FilesAndIO路径下file1.txt文件内存有abcdef,试运行:
代码示例1:一次读取一个字节:
//方法中需要指定打开文件的路径(绝对路径、相对路径或File对象)
try {
//创建对象,同时也打开文件
InputStream inputStream = new FileInputStream("./file1.txt");
//尝试按照字节进行读取
while(true) {
int b = inputStream.read();
if (b == -1) {
//读到了文件末尾
break;
}
System.out.println(b);
}
//关闭文件并释放资源
inputStream.close();
}catch(FileNotFoundException e) {
//FileNotFoundException是IOException的一个子类,故而可以进行合并
e.printStackTrace();
}catch(IOException e){
//读操作可能发生的异常:IO操作失败的可能性是非常大的
e.printStackTrace();
}
输出结果为:
这些数字就是每个字符的ASCII码值(英文字符本身就是一个字节)
代码改进1:
但是这种编写方式会导致:如果文件不存在抛出异常时会跳过关闭文件,导致释放资源失败,故而需要将close方法置于finally中,修改代码如下:
//方法中需要指定打开文件的路径(绝对路径、相对路径或File对象)
InputStream inputStream = null;
try {
//创建对象,同时也打开文件
inputStream = new FileInputStream("./file1.txt");
//尝试按照字节进行读取
while(true) {
int b = inputStream.read();
if (b == -1) {
//读到了文件末尾
break;
}
System.out.println(b);
}
}catch(FileNotFoundException e) {
//FileNotFoundException是IOException的一个子类,故而可以进行合并
e.printStackTrace();
}catch(IOException e){
//读操作可能发生的异常:IO操作失败的可能性是非常大的
e.printStackTrace();
}finally{
try {
//关闭文件并释放资源
inputStream.close();
}catch(IOException e){
e.printStackTrace();
}
}
代码改进2:
但是这样的编写方式是非常麻烦的,java中提供了一个语法:try with resources:
try(InputStream inputStream = new FileInputStream("./file1.txt")){
while(true){
int b = inputStream.read();
if(b == -1){
break;
}
System.out.println(b);
}
}catch(IOException e){
e.printStackTrace();
}
以上代码中没有显式手动调用close,但是try会帮我们自动调用:
当代码执行完这里的try语句块之后,就会自动调用close;
注:需要实现Closeable interface才能置于try()中,所有的流对象都实现了Closeable;
代码示例2:一次读取若干个字节:
try(InputStream inputStream = new FileInputStream("./file1.txt")){
//一次读取若干个字节
while(true){
//将读出的结果置于buffer数组中,相当于用参数表示方法返回值,这种方法称为输出型参数
byte[] buffer = new byte[1024];
int len = inputStream.read(buffer);
if(len == -1){
break;
}
for(int i=0;i<len;i++){
System.out.println(buffer[i]);
}
}
}catch(IOException e){
e.printStackTrace();
}
实际使用中这样的读取方式才是常见且高效的;
3.2 FileOutputStream 字节流的写操作
现将E:\JavaEE_FilesAndIO路径下file1.txt文件原存内容全部清空,试运行:
代码示例1:一次写入一个字节:
try(OutputStream outputStream = new FileOutputStream("./file1.txt")){
outputStream.write('a');
outputStream.write(98);
outputStream.write(99);
}catch(IOException e){
e.printStackTrace();
}
打开对应文件:
代码示例2:一次写入多个字节:
try(OutputStream outputStream = new FileOutputStream("./file1.txt")){
byte[] buffer = new byte[]{97,98,99};
outputStream.write(buffer);
}catch(IOException e){
e.printStackTrace();
}
打开对应文件:
注:每次按照“写”方式打开文件,都会清空文件原有内容,再从起始位置往后写;
故而也存在追加写的流对象,打开之后不清空,继上次文件内容后继续往后写;
3.3 Reader 字符流的读操作
基于对应文件中存储有“abc”的file1.txt文件,试运行如下代码:
try(Reader reader = new FileReader("./file1.txt")){
//按照字符读
while(true){
char[] buffer = new char[1024];
int len = reader.read(buffer);
if(len == -1)
break;
//如果传入的是byte数组,还可以手动指定utf8字符集避免乱码
String s =new String(buffer,0,len);
System.out.println(s);
}
}catch(IOException e){
e.printStackTrace();
}
输出结果为:
3.4 Writer 字符流的写操作
试运行以下代码:
try(Writer writer = new FileWriter("./file1.txt")){
writer.write("xyz");
}catch(IOException e){
e.printStackTrace();
}
打开对应文件:
注:与FileOutputStream类似,每次读文件时文件原有内容会被清空;
4. 文件操作的应用
例1:扫描指定目录并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件:
实现思路:
输入目录——>输入要删除的文件名——>询问是否要删除文件;
代码如下:
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
//例1: 扫描到指定目录,并找到名称中含有指定字符的所有普通文件,并且后续询问是否要删除该文件
public class Demo5{
public static void main(String[] args) {
System.out.println("请输入要扫描的路径:");
Scanner scanner = new Scanner(System.in);
String rootDirPath = scanner.next();
System.out.println("请输入要删除的文件:");
String toDeleteName = scanner.next();
File rootDir = new File(rootDirPath);
if(!rootDir.isDirectory()){
System.out.println("输入的扫描路径有误!");
}
scanDir(rootDir,toDeleteName);
}
public static void scanDir(File rootDir,String toDeleteName){
File[] files = rootDir.listFiles();
if(files == null){
System.out.println("目录为空!");
return;
}
for(File f:files){
if(f.isFile()){
if(f.getName().contains(toDeleteName)){
//只要文件名含有关键字即可,不要求完全相同
deleteFile(f);
}
}
else if(f.isDirectory()){
scanDir(f, toDeleteName);
}
}
}
public static void deleteFile(File f){
try {
System.out.print(f.getCanonicalPath() + " ");
System.out.println("是否要删除该文件?(Y / N)");
Scanner scanner = new Scanner(System.in);
String choice = scanner.next();
if (choice.equals("Y")|| choice.equals("y")) {
f.delete();
System.out.println("文件删除成功!");
} else {
System.out.println("文件删除取消!");
}
}catch(IOException e){
e.printStackTrace();
}
}
}
原路径下目录及文件如下:
试运行后,输出结果为:
根据路径查找对应文件:
例2:进行普通文件的复制
实现思路:
指定一个被复制的原文件路径与一个复制之后生成的目标文件路径。打开原路径文件读取内容后写入到目标文件;
代码如下:
import java.io.*;
import java.util.Scanner;
public class Demo6{
public static void main(String[] args) {
//1. 输入原路径与目标路径
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要拷贝的原文件:");
String src = scanner.next();
System.out.println("请输入要拷贝的目标文件:");
String dest = scanner.next();
File srcFile = new File(src);
if(!srcFile.isFile()){
System.out.println("输入的原路径有误!");
return;
}
//无需检查目标文件是否存在,OutputStream写文件时会自动创建不存在的文件
//2. 拷贝:读取原文件拷贝到目标文件
try(InputStream inputStream = new FileInputStream(src)){
try(OutputStream outputStream = new FileOutputStream(dest)){
//将inputStream中的内容读取出来放到outputStream中
byte[] buffer = new byte[1024];
while(true){
int len = inputStream.read(buffer);
if(len==-1){
//读取结束
break;
}
//buffer中可能只有一部分是有效数据,不能全部写入
outputStream.write(buffer,0,len);
}
}
}catch(IOException e){
e.printStackTrace();
}
}
}
试运行:
查看对应路径文件内容:
例3:扫描指定目录,并找到名称或内容中包含指定字符的所有普通文件:
代码如下:
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Scanner;
public class Demo7{
public static void main(String[] args) throws IOException {
//1.输入要扫描的路径
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要扫描的路径:");
String rootDirPath = scanner.next();
System.out.println("请输入要查找的关键词:");
String word = scanner.next();
File rootDir = new File(rootDirPath);
if(!rootDir.isDirectory()){
System.out.println("输入的路径非法!");
return;
}
//2.递归进行遍历
scanDir(rootDir, word);
}
public static void scanDir(File rootDir,String word) throws IOException {
//1.先列出rootDir中的内容
File[] files = rootDir.listFiles();
if(files == null){
return;
}
//2.遍历每个元素,针对普通文件和目录文件分别进行处理
for(File f:files){
if(f.isFile()){
//针对普通文件进行关键词查找
if(containsWord(f, word)){
System.out.println(f.getCanonicalPath());
}
}
else if(f.isDirectory()){
scanDir(f, word);
}
}
}
public static boolean containsWord(File f,String word){
//将f中的内容都读出来放在一个StringBuilder中
StringBuilder stringBuilder = new StringBuilder();
try(Reader reader = new FileReader(f)){
char[] buffer = new char[1024];
while(true){
int len = reader.read(buffer);
if(len == -1){
break;
}
//将此处的结果放置到StringBuilder中去
stringBuilder.append(buffer, 0, len);
}
}catch(IOException e){
e.printStackTrace();
}
//indexOf返回的是子串的下标,如果word在stringBuilder中不存在就返回-1
return stringBuilder.indexOf(word)!=-1;
}
}
原路径下目录及文件内容如下:
试运行后,输出结果为: