1. 什么是文件?
2. 文件的分类
3. 目录结构
4. 绝对路径和相对路径
5. 文件系统的操作(File类)
6. 文件内容的操作
InputStream和OutputStream
Reader和Writer
7. 文件操作的案例
删除指定文件
复制文件内容
在计算机中, 文件是以计算机的磁盘为载体存储在计算机上的数据集合, 这些文件不仅包括普通文件(如txt, rar, zip, mp4等文件), 还可以包含目录.
按照数据在文件中存储方式的不同, 我们将文件分成两类:
在计算机中, 保存管理文件是通过操作系统中的"文件系统"模块来负责的, 文件系统中一般是通过"树形结构"来组织磁盘中的文件, 一个普通文件就是一个叶子结点, 一个目录文件, 目录中就可以包含子树, 这就是一个非叶子结点, 每棵树的非叶子结点都可以有多个子节点, 这些树就都是N叉搜索树.
例如, 上面这张图中, 每个文件夹都是一个非叶子节点, 而文件夹里的普通文件(例如上面的.jpg文件)就是一个叶子结点.
操作系统中, 通过"路径"来描述一个文件/目录的具体位置, 路径共有两种描述风格:
绝对路径:以盘符开头(例如:d:\test.txt)
相对路径:以 . 或者 … 开头, 其中 . 表示当前路径, … 表示当前路径的父目录
相对路径必须有一个基准目录, 相对路径的出发点就是基准目录
即使是同一个文件, 基准目录不同, 相对目录也不同
文件系统相关操作就是通过文件资源管理器能够完成的功能, 例如:
在Java中提供了一个File类, 通过这个类完成上述操作, File的构造方法能够传入一个路径(可以是绝对路径和相对路径)来指定一个文件:
//指定文件, 文件路径为 d:/test.txt
File file = new File("d:/test.txt");
注意:在Java语言中, 反斜杠"“表示转义字符, 如果将文件路径写成这样的形式:“d:\test.txt”, 编译器会先识别”\t", 因此, 可以将路径中的反斜杠"\" 替换为"/", 就不会出现路径与转义字符产生冲突的情况了.
例如:
对一个已存在的文件进行操作:
//指定一个存在的文件
File file = new File("d:/test.txt");
//getName方法, 打印文件名称(不打印路径)
System.out.println(file.getName());//结果:test.txt
//getParent方法, 返回父目录的文件路径
System.out.println(file.getParent());//结果:d:\
//exists方法,判断当前指定的文件是否真实存在
System.out.println(file.exists());//结果:true
//getAbsoluteFile方法, 获取到当前文件夹的绝对路径
System.out.println(file.getAbsoluteFile());//结果:d:\test.txt
//isFile方法, 判断当前文件是否为一个普通文件(不是一个目录)
System.out.println(file.isFile());//结果:true
对一个不存在的文件进行操作:
// "d:/"路径下不存在test2.txt文件
File file2 = new File("d:/test2.txt");
//判断当前文件是否存在
System.out.println(file2.exists());//结果:false
//如果当前指定的文件不存在, 则创建出一个这样的文件, 创建成功返回true, 失败返回false
System.out.println(file2.createNewFile());//结果:true
//已经创建好了这个文件, 再创建一次, 会创建失败
System.out.println(file2.createNewFile());//结果:false
//删除当前指定的文件, 成功返回true, 失败返回false
System.out.println(file2.delete());//结果:true
针对文件内容的读写, Java标准库提供了一组类, 按照文件的内容, 可以分为:
这四个类都是抽象类, 因此一般实例化的是这些类的子类, 分别为FileInputStream, FileOutputStream, FileReader, FileWriter
使用InputStream每次读取一个字符:
public static void main(String[] args) {
//注意:
//try后面的括号里必须定义一个实现了Closeable接口的类的变量
//所有的流对象都实现了Closeable接口
try (InputStream inputStream = new FileInputStream("d:/test.txt")){
while (true){
int b = inputStream.read();
//read()方法返回-1代表读取到了文件末尾
if(b==-1) break;
System.out.println(b);
}
}catch (IOException e){
e.getStackTrace();
}
//在这个代码中, 不需要显式地调用close(), try会帮我们自动调用
//当try语句块执行完后, 就会自动调用close()
}
这种操作每次只读一个字符, 而读取磁盘的速度非常慢, 多次读取磁盘的效率非常低下, 下面这种写法可以实现一次读取多个字符.
public static void main(String[] args) {
try (InputStream inputStream = new FileInputStream("d:/test.txt")){
byte[] bytes = new byte[1024];
while (true){
//注意:如果read()方法不带参数的话, 返回的是读到的数值
//read()方法中一旦有参数, 返回的就是读取到的字符的个数了
//而字符被存放在了数组里(这个数组就是调用read方法时传入的数组)
int b = inputStream.read(bytes);
//read()方法返回-1代表读取到了文件末尾
if(b==-1) break;
for(int i=0;i<b;i++){
System.out.println(bytes[i]);
}
}
}catch (IOException e){
e.printStackTrace();
}
}
使用OutputStream:
public static void main(String[] args) {
try(OutputStream outputStream = new FileOutputStream("d:/test.txt")){
//每次写入一个字符
//注意:此时这里写入的是ascii值, 并没有写入整数
//例如a的ascii值为97, 那么参数为97时写入的就是a
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
//写入整个数组中的内容
byte[] bytes = new byte[]{97,98,99};
for (int i=0;i< bytes.length;++i){
outputStream.write(bytes[i]);
}
}catch (IOException e){
e.printStackTrace();
}
}
注意:这种写入方式会先删除文件内的所有内容, 然后再写入, 下面会提到如何不删除文件内容, 直接在文件末尾写入
使用Reader:
public static void main(String[] args) {
try(Reader reader = new FileReader("d:/test.txt")){
//按照字符方式来读
char[] chars = new char[1024];
while (true){
//len表示实际读到的字符个数
int len = reader.read(chars);
//len = -1,代表读取到了文件末尾
if (len == -1) break;
//这种方式是直接打印数组元素
for (int i=0;i<len;++i){
System.out.println(chars[i]);
}
//这种方式是将数组元素转换为字符串进行打印
String s = new String(chars,0,len);
System.out.println(s);
}
}catch (IOException e){
e.printStackTrace();
}
}
使用Writer:
//使用字符流写入文件
public class Demo7 {
public static void main(String[] args) {
try(Writer writer = new FileWriter("d:/test.txt")){
char[] chars = new char[]{97,98,99};
//写入一个字符数组, 就直接将字符数组里的所有内容写入文件中
writer.write(chars);
//我们也可以直接写入一个字符串
writer.write("abc");
//写入一个字符串, 从字符串的off位置开始写入(off=0,也就是a位置),写入的长度为len
writer.write("abcdef",0,3); //这条语句的写入结果就是"abc"
//形参给一个整数, write()就会写入这个整数对应的字符
//例如给一个97, write就会写入a
writer.write(97);
}catch (IOException e){
e.printStackTrace();
}
//创建一个线程来使用字符流读取文件
Thread thread = new Thread(()->{
try (Reader reader = new FileReader("d:/test.txt")){
char[] chars = new char[1024];
while (true){
int len = reader.read(chars);
if (len == -1) break;
String s = new String(chars,0,len);
System.out.println(s);
}
}catch (IOException e){
e.printStackTrace();
}
});
thread.start();
}
}
//结果:
abcabcabca
如果想写入时不删除文件内容, 而是直接在文件末尾写入, 我们只需在创建对象时加上一个参数true即可(字节流和字符流同理):
OutputStream outputStream = new FileOutputStream("d:/test.txt",true);
Writer writer = new FileWriter("d:/test.txt",true);
import java.io.File;
import java.util.Scanner;
public class Demo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("输入文件路径:");
String rootDirPath = scanner.next();
System.out.println("输入要删除的文件名:");
String toDeleteFile = scanner.next();
File file = new File(rootDirPath);
//输入的内容不是一个路径
if(!file.isDirectory()){
System.out.println("输入有误!");
return;
}
deleteFile(file,toDeleteFile);
}
//通过递归遍历文件列表, 找到并删除指定名称的文件
public static void deleteFile(File file, String toDeleteFile){
File[] files = file.listFiles();
if(files == null){
return ;
}
//遍历文件列表, 如果查询到文件, 判断是否需要删除
//如果是目录, 递归遍历
for(int i=0;i< files.length;++i){
//如果遍历到文件
if(files[i].isFile()){
//当前遍历到的文件名与要删除的文件名相等
if(files[i].getName().equals(toDeleteFile)){
//删除文件
files[i].delete();
}
}
//遍历到目录,递归遍历
else if(files[i].isDirectory()){
deleteFile(files[i],toDeleteFile);
}
}
}
}
注意:这里删除文件时路径不要设置的太大, 比如在D盘中找一个普通文件, 这样可能会导致程序很长时间找不到要删除的文件, 最好的做法是输入一个文件夹, 这样程序可以快速地找到指定的文件并删除.
用户输入两个文件, 一个是源文件(被复制的文件), 一个是目标文件
import java.io.*;
import java.util.Scanner;
public class Demo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
//被复制的文件
System.out.println("请输入需要复制的文件路径");
String src = scanner.next();
//要复制生成的文件
System.out.println("请输入要复制生成的文件路径:");
String dest = scanner.next();
File file = new File(src);
//输入的不是文件
if(!file.isFile()){
System.out.println("输入有误!");
return;
}
//不需要检查目标文件是否存在, OutputStream会自动创建不存在的文件
//读取文件
try (InputStream inputStream = new FileInputStream(src)){
//写入文件
try (OutputStream outputStream = new FileOutputStream(dest)){
//将inputStream中的文件读出并写入到outputStream中
byte[] bytes = new byte[1024];
while (true){
int len = inputStream.read(bytes);
if(len == -1) break;
//将读取出的字节写入
outputStream.write(bytes,0,len);
}
}
}catch (IOException e){
e.printStackTrace();
}
}
}