文件操作--I/O

文件IO流

文章目录

  • 文件IO流
    • 认识文件
    • 一、File
    • 二、字节流
      • 2.1 InputStream (输入流)
        • 2.1.1 read() 读文件
        • 2.1.2 read(byte[] b) 读文件
        • 2.1.3 通过字符读
    • 2.2 OutputStream(输出流)
    • 三、案例实现
      • 3.1 案例一 (删除符合条件文件)
      • 3.2 案列二 (普通文件复制)
      • 3.3 案例三 (找到文件中包含指定字符)

认识文件

我们常说的I/O,指的就是,Input/Output。那么针对硬盘持久化存储的I/O设备,当我们进行数据保存的时候,往往保存的是独立成一个个单位进行保存,而不是保存一个整体。这种独立的单位就被抽象成文件的概念。

从狭义来说,我们通常说的文件,指的是存储在硬盘上的“数据” 普通文件。

从广义来说,文件概念会广泛,操作系统会把很多的 硬件设备/软件资源,也给抽象成文件,例如,读写 网卡,网络编程,就操作系统而言会把这些硬件设备/软件资源也给抽象成文件。

抽象文件能够让系统更加的统一和管理。

文件除了有数据内容外,还有一部份的信息,such as 文件名,日期,类型,文件大小等并不作为文件的数据而存在,我们把这部分的信息称之为文件的 元信息

文件操作--I/O_第1张图片

通过这一个个的文件,我们可以将这些文件给组织起来,更方便用户使用它,逻辑上也更好的理解它。

同时,随着文件越来越多,对文件的系统管理也被提上了日程,如何进行文件的组织呢,一种合乎自然
的想法出现了,就是按照层级结构进行组织 —— 也就是我们数据结构中学习过的树形结构。这样,一
种专门用来存放管理信息的特殊文件诞生了,也就是我们平时所谓文件夹(folder)或者目录(directory)的
概念。

Windows的树形结构组织:

文件操作--I/O_第2张图片

Linux上的树形结构组织:

文件操作--I/O_第3张图片

文件的路径

文件路径分为两种:

1、绝对路径(以 / 开头 或者 以盘符开头):从树根出发,依次记录下中间经历的路径。

2、相对路径(以 . 或者 … 开头):从当前工作目录出发,中间经历的路径。

/ 基准所在的最顶级目录即根目录

./ 基准所在的当前目录

…/ 基准所在的当前目录的上一级目录(当前目录的父级目录)

文件的类型:

文本文件,操作文件的时候,基本单位是 “字符”

二进制文件,操作文件的时候,基本单位是“字节”

如何在java中操作文件??

1、在文件系统的层面上来操作文件

​ 创建文件、删除文件、创建目录、拷贝文件、重命名文件…

2、操作文件的内容(操作文件里面保存的数据)

​ 读文件,写文件。

在java标准库中,提供了 java.io.File 这样的类,来描述以及操作文件;(io----->input/output,输入输出)

一、File

​ 通过一个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());

    }
}

文件操作--I/O_第4张图片

这是打印出来的结果,大家亦可以注意到,在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毫秒
    }
}

文件操作--I/O_第5张图片

可以从动画中看出 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());
    }
}

文件操作--I/O_第6张图片

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 的名字。

二、字节流

2.1 InputStream (输入流)

字节流中 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() 关闭字节流

2.1.1 read() 读文件

  • 读文件
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) 利用文件路径构造文件输入流

文件操作--I/O_第7张图片

重点说一说 read()方法,该操作的核心方法提供了三个版本 read(byte[] b) 、read()、read(byte[] b ,int off , int len) ,现讲讲这段代码的版本。

文件操作--I/O_第8张图片

❔❔❔❔❔ 尽然是读字节 为啥不用 byte ,而用 int 尼???

进入 read()源码,用的是 int 类型接收;int 的 Range (0~255),byte 的 Range(-128~127),此处不许要参与运算,因为读出来的只是一个单纯的 字节。通过翻译一颗得知读取到文件末尾没有字节就返回 -1 ,得到 -1 就表示这个文本文件读完了。

在进行各种各样输入输出的操作中,都可能会产生异常,读文件的时候,可能读着读着就磁盘就坏了,这种时候就抛出IOException异常。

运行结果:

文件操作--I/O_第9张图片

我们是以 字节流 来读,那么读出来的结果就是这些字符的 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 接口的。

2.1.2 read(byte[] b) 读文件

这个方法一次读若干个字节,尽可能的填满这个字节数组,方法返回值是实际读到的字节数。

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();
        }
    }
}

2.1.3 通过字符读

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。

2.2 OutputStream(输出流)

按照字节来写: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

三、案例实现

3.1 案例一 (删除符合条件文件)

扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件。

步骤:
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);
            }
        }
    }
}

3.2 案列二 (普通文件复制)

步骤:
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();
        }
    }
}

3.3 案例三 (找到文件中包含指定字符)

扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)

步骤:
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流的基本用法和基础概念,都是必掌握。

铁汁们,觉得笔者写的不错的可以点个赞哟❤,收藏关注呗,你们支持就是我写博客最大的动力!!!!

你可能感兴趣的:(java)