文件操作 ---- IO

本文介绍了Java中文件系统的基础概念,包括文件和文件系统的特性,以及如何通过Java.io.File类进行文件的创建、删除、目录操作和重命名。还详细讲解了字节流和字符流在文件内容操作中的应用,包括读写操作和常用类如FileInputStream和FileOutputStream的实例。最后提供了文件操作的实践练习,如删除文件、复制文件和查找文件的代码示例。
摘要由CSDN通过智能技术生成

本文将会从基础概念和实操方面讲起,实操主要是文件系统方面的操作(创建,删除,修改等)和文件内容方面的操作,最后再针对一些常用的文件操作进行一个代码练习

一、基础概念

注意,本文主要涉及实操方面,如果想知道更多关于理论部分请点击下方链接

  1. 什么是文件:一整块有关系的数据,一般存放在硬盘或外部设备中,底层全都是一长串的二进制。已经被操作系统封装,由文件系统管理,我们只要了解对应的API即可

  2. 文件管理系统
    (1)点击文件的属性,可以查看对应的文件系统。
    (2)通过查看磁盘管理,可以看见真实的磁盘,每一个盘都有单独的文件系统(ps.所以我们可以自定义给磁盘划分大小,但是这样极易出错,不建议尝试)

  3. 底层数据结构:数据组织主要是【N叉树】的结构,即“目录 – 文件”的形式

  4. 路径:为了能够准确查找文件,每个文件都有唯一的路径,不过路径的描述方法不同,主要是 “绝对路径” 和 “相对路径” 这两种。

    • 绝对路径:以盘符开头
    • 相对路径:看基准目录,【.】表示当前所在目录,【…】表示当前目录的上一级
  5. 文件分类:文件主要分为文本文件和二进制文件两种,当然,文件的底层都是一长串二进制数据。我们可以靠该文件能否被记事本正常打开判断类别,能被正常打开的是文本文件,反之二进制文件。因为记事本的原理,就是尝试把当前数据放在码表中查看。

    • 文本文件:存在是字符,字符集编码后的文本
    • 二进制文件:一长串二进制,按照标准格式保存的非被字符集编码过的文件

二、文件系统操作(java代码操作)

系统操作有很多,比如创建删除文件,创建目录等,下面将会从这三个方面介绍如何通过java代码实现

2.1 前置知识

为什么不用C语言写:C语言标准库不支持文件系统操作

如何通过 java 代码实现:依靠Java.io.File类,每一个File类都对应着一个具体文件(无论这个文件存在与否都可以对应)

  • io:指的是 input(输入) 和 output(输出)。
    注意,这个是要站在CPU的角度看待,内存相比硬盘离CPU更近,所以【从硬盘读取数据到内存的行为】是“读取”,从【从内存中读取数据然后写到硬盘上的行为】是“写入”

2.2 File类

属性

修饰符及类型属性说明
static StringpathSeparator依赖于系统的路径分隔符,String 类型的表示
static charpathSeparator依赖于系统的路径分隔符,char 类型的表示

方法
点击查看File的方法

2.3 创建文件

public static void createFile() throws IOException {
	   File file = new File("./","test1.txt");
	   file.createNewFile();
	   File file1 = new File("./test2.txt");
	   file1.createNewFile();
}

注意点

  1. 关于反斜杠
    Linux/Mac 只支持/,但是Windows /(需要写两个,因为要是转义字符) 和 \ 都支持
  2. 如果文件已经存在了
    createNewFile表示如果文件不存在,会按照路径创建一个,如果存在,再调用也不会有什么影响

2.4 删除文件

public static void delete(){
    File file = new File("./test1.txt");
    file.deleteOnExit();
    //file.delete();
}

注意点

  1. deleteOnExit():当程序正确退出时,删除文件。当程序异常关闭时,不会删除该文件
    一般用于临时文件(可以自动用户当前的中间状态,比如office的恢复数据操作)

2.5 创建目录

一次性创建单级目录

public static void createMadir(){
    File file = new File("./test");
    file.mkdir();
}

一次性创建多级目录

public static void createMadirs(){
    File file = new File("./test/first/second");
    file.mkdirs();
}

注意点

  1. mkdir:make directory
  2. mkdirs:make directories

2.6 文件重命名

要求括号里放的是不存在状态的File类,然后调用方法的要求存在

public static void renameDir() throws IOException {
	   File file1 = new File("./test/first/test.txt");
	   File file2 = new File("./test/test3.txt");
	   file2.createNewFile();
	   file2.renameTo(file1);
}

三、文件内容操作

文件内容方面的操作则主要依靠文件流,流分为字节流和字符流,其中字节流主要用于二进制文件,字符流主要用于文本文件。下面将会先概述一下通用操作,然后再从字节流和字符流两个方面详细讲解

3.1 通用操作

字节流or字符流?

对于文件内容的操作,Java也提供了一些接口

  • 二进制文件:使用字节流,操作字节,InputStream(输入流)、OutputStream(输出流)
  • 文本文件:使用字符流,操作字符,Reader(输入流)、Writer(输出流)

ps.
(1)后续的一些其他的类,基本是上述四个抽象类的子类
(2)理论上不管什么文件都可以用字节流打开,只是文本文件用字符流更加方面而已

基本步骤

  1. 构造方法打开文件
  2. 【1】如果衍生自 InputStream/Reader,可以用read读数据(从硬盘读到内存,站在CPU角度)
    【2】如果衍生自OutPutStream/Writer,可以用write写数据(从内存写到硬盘,站在CPU角度)
  3. close关闭文件

close()

【1】为什么要close
释放必要资源。一个进程打开一个文件,需要像系统申请一定资源(会占用PCB的文件描述符表中的一个表项,而这个文件描述符表就像是个不会自动扩容的顺序表,长度有限,如果不及时关闭,一旦被占满,就无法打开新的文件了)

【2】 如何正确close

  • 通过finally,避免遇到抛出异常,return等原因,使得close方法没有执行到的情况
  • try with resources:try(),括号里面放资源创建语句

3.2 字节流使用

InputStream

修饰符及返回值类型方法签名说明
intread()读取一个字节的数据,返回 -1 代表已经完全读完了
intread(byte[] b)最多读取 b.length 字节的数据到 b 中,返回实际读到的数量;-1 代表以及读完了
intread(byte[] b, int off, int len)最多读取 len - off 字节的数据到 b 中,放在从 off 开始,返回实际读到的数量;-1 代表以及读完了
voidclose关闭字节流

InputStream 只是一个抽象类,要使用还需要具体的实现类。关于 InputStream 的实现类有很多,基本可以认为不同的输入设备都可以对应一个 InputStream 类,我们现在只关心从文件中读取,所以使用 FileInputStream

FileInputStream

签名说明
FileInputStream(File file)利用 File 构造文件输入流
FileInputStream(String name)利用文件路径构造文件输入流

代码示例 — 读取

读取英文字符:其实读出来的都是一个个数字,我们需要用 %c 用字符表示出来

public static void readFile() throws IOException {
	   try(InputStream inputStream = new FileInputStream("./test/first/test.txt")){
	       while (true){
	           int b = inputStream.read();
	           if (b == -1){
	               break;
	           }
	
	           System.out.printf("%c", b);
	       }
	   }
}
public static void readFile1() throws IOException {
	   try(InputStream inputStream = new FileInputStream("./test/first/test.txt")){
	       byte[] buf = new byte[1024];
	       int len;
	
	       while (true){
	           len = inputStream.read(buf);
	           if (len == -1){
	               break;
	           }
	
	           for (int i = 0; i < len; i++) {
	               System.out.printf("%c", buf[i]);
	           }
	       }
	   }
}

读取中文字符
用FileInputStream读取中文字符十分麻烦,因为我们要指定字节。为了提高效率,使用Scanner类进行读取

构造方法说明
Scanner(InputStream is, String charset)使用 charset 字符集进行 is 的扫描读取
public static void readFile2() throws IOException {
	   try(InputStream inputStream = new FileInputStream("./test/first/test.txt")){
	       try(Scanner scanner = new Scanner(inputStream,"UTF-8")){
	           while(scanner.hasNext()){
	               String s = scanner.next();
	               System.out.print(s);
	           }
	       }
	   }
}

OutPutStream

输出流对象会在打开文件之后,清空文件内容(不管是字节流还是字符流)。如果想要不清空,就需要用追加写方式,即在构造方法后面加一个true.

try(Writer writer = new FileWriter(“路径”, true)){
}

修饰符及返回值类型方法签名说明
Scanner(InputStream is, String charset)使用 charset 字符集进行 is 的扫描读取使用 charset 字符集进行 is 的扫描读取
voidwrite(int b)写入要给字节的数据
voidwrite(byte[] b)将 b 这个字符数组中的数据全部写入 os 中
intwrite(byte[] b, int off, int len)将 b 这个字符数组中从 off 开始的数据写入 os 中,一共写 len 个
voidclose()关闭字节流
voidflush()重要:我们知道 I/O 的速度是很慢的,所以,大多的 OutputStream 为了减少设备操作的次数,在写数据的时候都会将数据先暂时写入内存的一个指定区域里,直到该区域满了或者其他指定条件时才真正将数据写入设备中,这个区域一般称为缓冲区。但造成一个结果,就是我们写的数据,很可能会遗留一部分在缓冲区中。需要在最后或者合适的位置,调用 flush(刷新)操作,将数据刷到设备中。

代码示例 — 写入

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.2 字符流使用

和字节流使用方式差不多只不过是类名换了一下而已。

Reader类

Writer类

三、练习

3.1 删除指定文件

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

import java.io.File;
import java.util.Scanner;

public class Solution {
    private static Scanner scanner = new Scanner(System.in);

    public static void main(String[] args) {
        // 1. 让用户输入一个目录. 后续的查找都是针对这个目录来进行的.
        System.out.println("请输入要搜索的根目录: ");
        File rootPath = new File(scanner.next());
        // 2. 再让用户输入要搜索/要删除的关键词.
        System.out.println("请输入要删除的关键词: ");
        String word = scanner.next();
        // 3. 判定一下当前输入的目录是否有效.
        if (!rootPath.isDirectory()) {
            System.out.println("您此时输入的路径不是合法目录!");
            return;
        }
        // 4. 遍历目录. 从根目录出发, 按照 深度优先(递归) 的方式, 进行遍历
        scanDir(rootPath, word);
    }

    public static void scanDir(File currentDir, String word) {
        // 1. 先列出当前目录中都包含哪些内容.
        File[] files = currentDir.listFiles();
        if (files == null || files.length == 0) {
            // 空的目录或者非法的目录
            return;
        }
        // 2. 遍历列出的文件, 分两个情况分别讨论.
        for (File f : files) {
            // 加个日志, 方便看程序执行的过程.
            System.out.println(f.getAbsolutePath());

            if (f.isFile()) {
                // 3. 如果当前文件是普通文件, 看看文件名是否包含了 word, 来决定是否要删除.
                dealFile(f, word);
            } else {
                // 4. 如果当前文件是目录文件, 就递归执行 scanDir
                scanDir(f, word);
            }
        }
    }

    private static void dealFile(File f, String word) {
        // 1. 先判定当前文件名是否包含 word
        if (!f.getName().contains(word)) {
            // 此时这个文件不包含 word 关键词. 直接跳过.
            return;
        }
        // 2. 包含 word 就需要询问用户是否要删除该文件?
        System.out.println("该文件是: " + f.getAbsolutePath() + ", 是否要确认删除? (Y/N)");
        String choice = scanner.next();
        if (choice.equals("Y") || choice.equals("y")) {
            f.delete();
        }
        // 如果是其他值, 都忽略.
    }
}

3.2 复制文件

import java.io.*;
import java.util.Scanner;

// 完成文件复制.
public class Solution {
    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        // 1. 输入路径并且合法性判定
        System.out.println("请输入要复制的源文件路径: ");
        String src = scanner.next();
        File srcFile = new File(src);
        if (!srcFile.isFile()) {
            System.out.println("您输入的源文件路径非法!");
            return;
        }
        System.out.println("请输入要复制到的目标路径: ");
        String dest = scanner.next();
        File destFile = new File(dest);
        // 不要求目标文件本身存在. 但是得保证目标文件所在的目录, 得是存在的.
        // 假设目标文件写作 d:/tmp/cat2.jpg, 就需要保证 d:/tmp 目录是存在的.
        if (!destFile.getParentFile().isDirectory()) {
            System.out.println("您输入的目标文件路径非法!");
            return;
        }

        // 2. 进行复制操作的过程. 按照字节流打开.
        try (InputStream inputStream = new FileInputStream(srcFile);
             OutputStream outputStream = new FileOutputStream(destFile)) {
            while (true) {
                byte[] buffer = new byte[20480];
                int n = inputStream.read(buffer);
                System.out.println("n = " + n);
                if (n == -1) {
                    System.out.println("读取到 eof, 循环结束. ");
                    break;
                }
                outputStream.write(buffer, 0, n);
            }
        }
    }
}

3.3 查找文件

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

public static void search() throws IOException {
    Scanner scanner = new Scanner(System.in);
    System.out.println("请输入要查找的根目录:");
    File rootPath = new File(scanner.next());
    if (!rootPath.isDirectory()){
        System.out.println("您输入的根目录有误");
        return;
    }

    System.out.println("请输入要查找的文件名");
    String fileName = scanner.next();
    List<File> res = new ArrayList<>();

    scanDir(rootPath, fileName, res);
    System.out.println("共找到了符合条件的文件 " + res.size() + "个,它们分别是" );

    for (File file : res) {
        System.out.println(file.getCanonicalPath());
    }
}

public static void scanDir(File currentDir, String searchName, List<File> res) throws IOException {
    File[] files = currentDir.listFiles();

    if (files == null || files.length == 0){
        return;
    }

    for (File f : files){
        if (f.isDirectory()){
            scanDir(f, searchName, res);
        }else{
            if (isContentContains(f, searchName)) {
                res.add(f.getAbsoluteFile());
            }
        }
    }
}

public static boolean isContentContains(File currentFile, String searchName) throws IOException {
    StringBuilder stringBuilder = new StringBuilder();
    try(InputStream inputStream = new FileInputStream(currentFile)){
        try(Scanner scanner = new Scanner(inputStream, "UTF-8")){
            while (scanner.hasNextLine()){
                stringBuilder.append(scanner.nextLine());
                stringBuilder.append("\r\n");
            }
        }
    }

    return stringBuilder.indexOf(searchName) != -1;
}
  • 18
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值