文件操作-JavaEE

文件(file)这个概念,在计算机中是“一词多用”的,有狭义的文件,也有广义的文件。

狭义的文件:计算机硬盘上的 文件目录(日常用语即称文件夹)

广义的文件:泛指计算机中的很多软硬件资源。也就是操作系统中把很多硬件设备和软件资源都抽象成了文件,按照文件的方式进行统一管理。

本文只讨论狭义的文件。


之前所介绍的代码中,存储数据主要是靠变量,而变量是在内存中的,我们可以直接对内存进行操作。

现在的文件则是在硬盘上。在硬盘上操作数据则没那么直接,更麻烦一些。

每个文件在硬盘上都有一个具体的“路径”,通过文件右键菜单的属性中可以知道文件的路径。

如图我们读到了代码的路径为C:\Users\25803\Desktop,那么其第一个“大写字母:”为盘符,我们计算机有许多硬盘,比如说C:、D:、E:等,这样的盘符是通过“硬盘分区”来的。每个盘符可以是一个单独的硬盘,也可以是若干个盘符对应一个硬盘。

计算机中有两种表示路径的风格

  1. 绝对路径:以盘符开头的路径

  1. 相对路径:以当前所在目录为基准,以.或者..开头(.开头有时候可以省略),找到指定 路径

当前所在目录称为工作目录。每个程序运行的时候都有一个工作目录(在控制台通过命令操作的时候,是特别明显的,后来进化到图形化界面了,工作目录就不那么直观了)

假设我们假设当前的工作目录为d:/tmp,如果要定位到111这个目录,就可以表示成./111。同理./222和./111/aaa就能找到222、aaa所在目录。


文件的类型

在我们计算机中有许多中类型的文件,例如word、exe、图片、视频、音频、源代码、动态库……那么这些不同的文件,整体可以归纳到两类中。一类是文本文件,存的是文本、字符串(字符串,是由字符构成的。每个字符,都是通过一个数字表示的。这个文本文件里存的数据,一定是合法的字符,都是我们程序员指定字符编码的码表之内的数据。),一类是二进制文件,存的是二进制数据,不一定是字符串了(没有任何限制,可以存储任何需要的数据)。

那么如何区分一个文件是文本还是二进制?

直接使用记事本打开,如果乱码了,就说明是二进制,如果没乱,说明就是文本。

实际些代码的时候,这两类文件的处理方式略有差别。

了解了背景知识之后,我们来看一下Java中对于文件的操作。

  1. 针对文件系统操作(文件的创建、删除、重命名……)。

  1. 针对文件内容操作(文件的读和写)。


Java中操作文件

文件系统操作

Java 中通过 java.io.File 类来对一个文件(包括目录)进行抽象的描述。注意,有 File 对象,并不

代表真实存在该文件。

File概述

我们先来看File 类中的常见属性、构造方法和方法

属性

修饰符及类型

属性

说明

static String

pathSeparator

依赖于系统的路径分隔符,String类型的表示

static char

pathSeparator

依赖于系统的路径分隔符,char类型的表示

构造方法

方法名

说明

File(File parent, String child)

根据父目录 + 孩子文件路径,创建一个新的 File 实例

File(String pathname)

根据文件路径创建一个新的 File 实例,路径可以是绝对路径或者相对路径

File(String parent, String child)

根据父目录 + 孩子文件路径,创建一个新的 File 实例,父目录用路径表示

注:

  1. 在new File对象的时候,构造方法参数中,可以指定一个路径。此时File对象就代表这个路径对应的文件了;

  1. 这里的路径可以是绝对路径,也可以是相对路径

  1. parent为当前文件所在目录,child为自身的文件名

方法

修饰符及返回值类型

方法签名

说明

String

getParent()

返回File对象的父目录文件路径

String

getName()

返回File对象的纯文件名称

String

getPath()

返回File对象的文件路径

String

getAbsolutePath()

返回File对象的绝对路径

String

getCanonicalPath()

返回File对象的修饰过的绝对路径

boolean

exists()

判断File对象描述的文件是否真实存在

boolean

isDirectory()

判断File对象代表的文件是否是一个目录

boolean

isFile()

判断File对象代表的文件是否是一个普通文件

boolean

createNewFile()

根据File对象,自动创建一个空文件。成功创建后返回true

boolean

delete()

根据File对象,删除该文件。成功删除后返回true

void

deleteOnExit()

根据File对象,标注文件将被删除,删除动作会到JVM结束时才会进行

String[]

list()

返回File对象代表的目录下的所有文件名

File[]

listFiles()

返回File对象代表的目录下的所有文件,以File对象表示

boolean

mkdir()

创建File对象代表的目录

boolean

mkdirs()

创建File对象代表的目录,如果必要,会创建中间目录

boolean

renameTo(File dest)

进行文件改名,也可以视为我们平时的剪切、粘贴操作

boolean

canRead()

判断用户是否对文件有可读权限

boolean

canWrite()

判断用户是否对文件有可写权限

代码示例

示例一
import java.io.File;
import java.io.IOException;

public class IODemo1 {
    public static void main(String[] args) throws IOException {
        File file = new File("./test.txt");
        System.out.println(file.getName());
        System.out.println(file.getParent());
        System.out.println(file.getPath());
        System.out.println(file.getAbsolutePath());
        System.out.println(file.getCanonicalPath());

    }
}
示例二
import java.io.File;
import java.io.IOException;

public class IODemo2 {
    public static void main(String[] args) throws IOException {
        File file = new File("./test.txt");
        file.createNewFile();
        System.out.println(file.exists());
        System.out.println(file.isFile());
        System.out.println(file.isDirectory());
    }
}
示例三
import java.io.File;

public class IODemo3 {
    public static void main(String[] args) {
        File file = new File("./test.txt");
        file.delete();
    }
}
示例四
import java.io.File;

public class IODemo4 {
    public static void main(String[] args) {
        File dir = new File("./test/aaa/bbb");
        dir.mkdirs();
    }
}
示例五
import java.io.File;

public class IODemo5 {
    public static void main(String[] args) {
        File file = new File("./test");
        File dest = new File("./testAAA");
        file.renameTo(dest);
    }
}

文件内容操作

我们使用一组“流对象”针对文件内容进行操作。

什么叫做“流”?这是一个形象的比喻。计算机里面很多概念都采用了一定的修辞手法,比喻是一种常见的方式。再比如使用一个链表的头节点/二叉树的根节点来表示整个链表/二叉树,这也是属于一种借代的修辞手法。

谈到“流”我们可能想到的是水流,水流的特点是生生不息,绵延不断,这是我们对于水流的一个感受。

那么这个“流对象”也跟水流差不多,想象一下,有个水龙头,通过这个水龙头可以接水,比如说要接100ml水,我们可以一次接100ml,一次接完;也可以一次接50ml,分两次接,还可以一次接10ml,分10次来接……。

所以文件的读写也是类似的,比如说要从文件中读100个字节,就可以一次读100字节,一次读完,一次读50字节,两次读完……也是可以随心所欲的读的。所以我们就给它起了一个形象的名字“流”。写文件同理。

因此我们就把读写文件的相关对象称为“流对象”,这个比喻并非是Java独有的,其实是操作系统的api就是这样设定的,进一步的各种的编程语言,操作文件也是继承了这个概念。

Java标准库的流对象

从类型上,分成两个大类,每个大类里面有很多小类,这里虽然涉及到的类很多,但是规律性很强。

  1. 字节流:以字节为单位,操作二进制数据

字节流中提供的主要的类有InputStream和OutputStream,那么由于这两个类都是抽象类,我们在实现的时候都要new其实现类FileInputStream或OutputStream

  1. 字符流:以字符为单位操作文本数据

字符流主要提供的类有Reader和Writer,那么由于这两个类都是抽象类,我们在实现的时候都要new其实现类FileReader或FileReader

这些类的使用方式是非常固定的,核心就是四个操作

(1)打开文件(构造对象)

(2)关闭文件(close)

(3)读文件(read)=>针对InputStream / Reader

(4)写文件(write)=>针对OutputStream / Writer


使用字节流操作文件
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * Description:使用字节流读取文件
 */
public class IODemo6 {
    public static void main(String[] args) throws IOException {
        //创建InputStream对象的时候,使用绝对路径或者相对路径,或者File对象都是可以的
        InputStream inputStream = new FileInputStream("d:/test.txt");

        //进行读操作
        //read():一次读取一个字节
        while (true) {
            int b = inputStream.read();
            if (b == -1) {
                //读取完毕
                break;
            }
            System.out.printf("%x ", (byte)b);
        }
        inputStream.close();
    }
}
        //read的第二个用法,一次读取若干个字节

        while (true) {
            byte[] buffer = new byte[1024];//read的第二个版本,需要调用者提前准备好一个数组
            int len = inputStream.read(buffer); 
            //表示实际上读到几个字节
            System.out.println("len:" + len);
            if (len == -1) {
                break;
            }
            //此时读取结果就被放到byte数组中
            for (int i = 0; i < len; i++) {
                System.out.printf("%x ", buffer[i]);
            }
        }

注:注意理解第二种用法中read的行为和返回值,read会尽可能的把参数传进来的数组都填满。

上面代码中给的数组长度是1024,read就会尽可能的读取1024个字节,填到数组里。但实际上,文件的剩余长度是有限的,如果剩余长度超过1024,此时1024个字节就都会填满,返回值就是1024了。如果当前剩余的长度不如1024,此时有多少就填多少,read方法就会返回当前实际读取的长度。


import java.io.IOException;
import java.io.OutputStream;

public class IODemo7 {
    //进行写文件
    public static void main(String[] args) throws IOException {
        try (OutputStream outputStream = new FileOutputStream("d:/test1.txt")) {
            outputStream.write(97);
            outputStream.write(98);
            outputStream.write(99);
            outputStream.write(100);
        }
    }
}

对于OutputStream来说,默认情况下,打开一个文件,会先清空文件原有的内容。

如果不想清空,流对象还提供了一个“追加写”对象,通过这个对象就可以实现不清空文件,把新的内容追加写到后面。


注意:input和output的方向,是以CPU为中心来看待这个方向的。

一般我们认为,内存更接近于CPU,硬盘离CPU更远。

以CPU为中心,数据朝着CPU的方向流向就是输入,所以就把数据从硬盘到内存的这个过程称为读(input);那么数据朝远离CPU的方向流向,就是输出,所以就把数据从内存到硬盘的这个过程称为写(write)。


使用字符流操作文件

字符流和字节流用法类似。

以下代码可以看到reader的用法,基本的用法也和前文的inputStream操作类似,也是通过read方法进行操作。

import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;

public class IODemo8 {
    //字符流的操作
    public static void main(String[] args) {
        try (Reader reader = new FileReader("d:/test.txt")) {
            while (true) {
                int ch = reader.read();
                if (ch == -1) {
                    break;
                }
                System.out.print(" " + (char) ch);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

字符流的写操作也是与前文outputStream类似的。

package IO;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

public class IODemo9 {
    public static void main(String[] args) {
        try(Writer writer = new FileWriter("d:/test.txt")){
            writer.write("hello world");
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Scanner是搭配流对象进行使用的。

我们以前经常使用的下面这句代码中的System.in就是一个输入流对象。

        Scanner scanner = new Scanner(System.in);

所以我们可以知道在创建scanner对象的时候是对构造方法传输入流类型的参数,那么我们就可以对文件进行写入操作。

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;

public class IODemo10 {
    public static void main(String[] args) {
        try (InputStream inputStream = new FileInputStream("d:/test1.txt")){
            Scanner scanner = new Scanner(inputStream);
            scanner.next();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

小程序练习

  1. 给定一个目录,目录里包含很多的文件和子目录。用户输入一个要查询的词,看看当前目录下(以及子目录里)是否有匹配的结果,如果有匹配结果,就进行删除。

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

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

    public static void main(String[] args) {
        System.out.println("请输入要搜索的路径:");
        String basePath = scanner.next();
        File root = new File(basePath);
        if (!root.isDirectory()) {
            System.out.println("输入的目录有误!");
            return;
        }
        System.out.println("请输入要删除的文件名:");
        String nameToDelete = scanner.next();
        //递归查找文件
        scanDir(root, nameToDelete);

    }

    private static void scanDir(File root, String nameToDelete) {
        File[] files = root.listFiles();
        if (files == null) {
            return;
        }
        for (File f : files) {
            if (f.isDirectory()) {
                scanDir(f, nameToDelete);
            } else {
                if (f.getName().contains(nameToDelete)) {
                    System.out.println("请确认是否要删除" + f.getAbsolutePath() + "吗?");
                    String choice = scanner.next();
                    if (choice.equals("y") || choice.equals("Y")) {
                        f.delete();
                        System.out.println("删除成功");
                    } else {
                        System.out.println("删除取消");
                    }
                }
            }
        }
    }
}
  1. 把一个文件拷贝成另一个文件

把第一个文件按照字节依次读取,把结果写入另一个文件中。

import javax.sound.midi.Soundbank;
import java.io.*;
import java.sql.SQLOutput;
import java.util.Scanner;

public class IODemo12 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入你要拷贝哪些文件");
        String srcPath = scanner.next();
        System.out.println("请输入你要拷贝到的位置");
        String destPath = scanner.next();

        File srcFile = new File(srcPath);
        if (!srcFile.isFile()) {
            System.out.println("源路径有误");
            return;
        }
        File destFile = new File(destPath);
        if (destFile.isFile()) {
            System.out.println("文件已存在,无法拷贝");
            return;
        }

        try (InputStream inputStream = new FileInputStream(srcFile);
             OutputStream outputStream = new FileOutputStream(destFile)) {
            while (true) {
                int b = inputStream.read();
                if (b == -1) {
                    break;
                }
                outputStream.write(b);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值