JavaEE|文件操作·上

一、认识文件

文件的概念

文件这个,这个概念在计算机里,也是“一词多用”,我们从狭义和广义两方面来解释它。

狭义上的文件(软件):指的我们的硬盘上的文件和目录。每个文件在硬盘上都会有一个具体的“路径”,OS用这个路径描述文件的位置。

广义的文件:泛指计算机中的软硬件资源。操作系统中,把很多的硬件设备和软件资源抽象成了文件。按照文件的方式来统一管理。比如,网络编程中,我们说的读网卡,其实就是操作系统把网卡当成了一个文件。而我们以后编程对文件的操作大头还是狭义上的文件,所以我们接下来讨论的还是狭义上的文件。

文件的管理

文件的管理,我们也可以理解成是文件的组织+文件的定位。我们管理文件,既需要零散的文件组织起来,也需要知道如何找到或者说定位到这些文件。

相关概念

(一)组织

我们日常工作学习中会产生大量的文件,那么如何高效管理组织这些文件值得我们好好思考。我们目前操作系统基本上都是按照层级/树形结构进行组织的。

这棵大树中的非叶子结点对应到计算机里边就是咱们所说的文件夹(folder)/目录(directory)。这个很容易理解,就不再演示了。

对于这些文件,我们组织是组织起来了,那么我们需要使用的时候怎么定位呢?怎么确保定位到的文件是唯一的呢?这时就不得不提文件路径(path)这个概念了。

(二)定位——路径

OS是按树形结构组织文件的,定位我们是通过文件的路径来做的。

(1)绝对路径:从根节点出发,到达每个节点的路径都是唯一的,这种描述方式称为文件的绝对路径(absolute path)。

我们可以用\(反斜杠/捺)/(斜杠/撇)分割不同的目录级别(windows系统下),建议我们优先使用/斜杠,因为反斜杠很可能有转义的风险,尽管windows默认是反斜杠分割的,但是正斜杠也完全没有问题。

例如:D:\Bin\QQLiveMPlayer这就是绝对路径。这里的D 是盘符分区(目前都是给硬盘分的),AB是一般是已经被淘汰的软盘盘符(价格高,内存小,性价比不高)。

(2)相对路径:有绝对路径就有相对路径,相对路径的概念就是从任意节点出发进行路径的描述。

例如,我们配置的环境变量

.;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar; 其实就是相对路径。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DNLNtCVr-1676547058297)(F:\typora插图\image-20230114221520543.png)]

换句话说,相对路径其实也就是以当前所在目录(也称为工作目录)为基准,出发找文件。

关于工作目录:每个程序运行的时候,都有一个工作目录。在控制台通过命令操作非常明显。后来演变成图形化界面,工作目录不是特别明显了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yMWzJZi7-1676547058298)(F:\typora插图\image-20230114220642869.png)]

后边如果从这里出发再拼接上/文件名即可。

例如我们通过命令行窗口打开软件。(软件资源都是以文件形式存储组织的)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YjQCA1p9-1676547058298)(F:\typora插图\image-20230114221417928.png)]

相对路径写法♋

除了了解绝对路径和相对路径的基本概念,我们需要完全掌握相对路径的写法!!后边网络编程中相对路径的写法必不可少!!!

首先,我们介绍相对路径特殊符号:

  • 核心规则:单点表示当前目录,双点表示上级目录,反斜杠/表示分割目录。

  • 以"/"开头就是,代表根目录

  • 以"./"开头,代表当前目录和文件目录在同一个目录,此时./可以省略不写(只有这种情况)

  • 以"…/"开头,代表,向上走一级,代表目标文件在当前文件所在的上一级目录

  • 以"…/…/"开头,代表向上走两级,代表目标文件的上上级目录

  • 以此类推

下边我们通过一个例子进行练习:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W20hMI17-1676547058299)(F:\typora插图\image-20230114222521929.png)]

当前工作目录是d:/tmp,那么定位到111这个目录就可以写成./111或者111

假定当前工作目录是d:/,相对路径写作./tmp/111

假定当前工作目录是d:/tmp,相对路径写作./111或者111

假定当前工作目录是d:/tmp/222,相对路径写作…/111

假定当前工作目录是d:/tmp/222/bbb.相对路径写作…/…/111

文件的分类

按照存放数据的不同,我们可以把文件分为文本文件和二进制文件。文本文件存放的内容是被字符集编码的文本,二进制文件存放的是按照标准格式保存的没有被字符集编码过的文件。说白话就是文本文件就是我们能看懂的语言表示方式,二进制文件就是计算机能看懂的语言(即二进制文件)表示的。

Java中文件的操作

java中文件的操作分为对文件系统的操作和对文件内容的操作。

  • 对于文件系统的操作,java标准库中提供了一个专门的类——File类。
  • 对文件内容的操作,我们使用的是流对象进行操作。而这里的流对象又分为字节流对象和字符流对象两大类。针对这两大类对象,java标准库分别提供了两个类。

从某种意义上来讲,文件系统的操作是文件内容操作的基础,我们只有从计算机的一堆文件中找到目标文件,才能够进行文件内容的操作。

(Java文件的操作虽然面试考的不多,但是日常开发中使用的会比较多,所以我们需要完完全全掌握。)

以上,便是我们的文件的通识知识,(当然,有些也很关键,比如相对路径和绝对路径等)。下边,就要上干货了!!!

由于文件操作的重要性,所以我们分为上下两部分,上半部分总结理论知识,下半部分练习一些代码案例,进行巩固。

二、File类的使用

File类是存在于java.util.io包下的一个类。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lyCdmfCD-1676547058300)(F:\typora插图\image-20230207161002884.png)]

查看标准库,我们可以发现,file类中的方法,林林总总一大堆,我们并不是都会用到,这里我们主要学习一些与文件的元信息、路径操作有关的方法。file类

这里我们把这些常用的方法按照下边的规则进行分组,并逐一进行使用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QYss9GcO-1676547058300)(F:\typora插图\image-20230207160622005.png)]

在具体演示这些方法的使用之前,我们需要明确一个事情,就是有了file对象,并不代表在目录下真实存在这些文件。

构造方法

方法签名说明
File(File parent,String child)根据父目录+孩子文件路径,创建一个新的File对象
File(String pathname)根据文件路径创建一个新的File对象,路径可以是绝对路径,也可以是相对路径
File(String parent,String child)根据父目录+孩子文件路径,创建一个新的File实例,父目录用路径表示
public static void main(String[] args) {
    //创建方法一:直接使用绝对路径的字符串构造
    File file1=new File("f:/test.txt");
    //创建方法二:使用相对路径构造(说明是跟idea所在路径下进行构造的)
    File file2=new File("./test.txt");
    //创建方法三:使用父路径字符串+孩子路径字符串
    File file3=new File("f:/","./test.txt");

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WakaurWj-1676547058301)(F:\typora插图\image-20230207161959718.png)]

获得文件元信息

返回值类型方法签名说明
StringgetParent()返回File对象的父目录文件路径
StringgetName()返回File对象的纯文件名
StringgetPath()返回File对象的文件路径
StringgetAbsolutePath()返回File对象的绝对路径
StringgetCanonicalPath()返回File对象修饰过的绝对路径
public static void main(String[] args) throws IOException {
    //创建方法一:直接使用绝对路径的字符串构造
    File file1=new File("f:/test.txt");
    //创建方法二:使用相对路径构造(说明是跟idea所在路径下进行构造的)
    File file2=new File("./test.txt");
    //创建方法三:使用父路径字符串+孩子路径字符串
    File file3=new File("f:/","./test.txt");

    File file=new File("f:/test.txt");
    System.out.println(file.getPath());
    System.out.println(file.getName());
    System.out.println(file.getPath());
    System.out.println(file.getAbsolutePath());
    System.out.println(file.getCanonicalFile());//注意这里因为某些字符不能跨平台使用,需要进行异常处理
}

下边三个有什么区别,暂时从这个例子里边看不出来

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DDif9JQK-1676547058302)(F:\typora插图\image-20230207163308155.png)]

判断的相关方法

返回值方法签名说明
booleanexists()判断File对象描述的文件是否真实有效
booleanisDirectory()判断File对象代表的文件是否是一个目录
booleanisFile()判断File对象代表的文件是否是一个普通文件
booleancreateNewFile()根据File对象,自动创建一个空文件。成功创建后返回true
booleancanRead()判断用户是否对文件有可读权限
booleancanWrite()判断用户是否对文件有可写权限
    public static void main(String[] args) throws IOException {
        //创建方法一:直接使用绝对路径的字符串构造
        File file1=new File("f:/test.txt");
        //创建方法二:使用相对路径构造(说明是跟idea所在路径下进行构造的)
        File file2=new File("./test.txt");
        //创建方法三:使用父路径字符串+孩子路径字符串
        File file3=new File("f:/","./test.txt");

        File file=new File("f:/test.txt");
//        System.out.println(file.getPath());
//        System.out.println(file.getName());
//        System.out.println(file.getPath());
//        System.out.println(file.getAbsolutePath());
//        System.out.println(file.getCanonicalFile());//注意这里因为某些字符不能跨平台使用,需要进行异常处理

        System.out.println(file.exists());
        System.out.println(file.isDirectory());
        System.out.println(file.isFile());
        System.out.println(file.canRead());
        System.out.println(file.canWrite());
        System.out.println(file.createNewFile());
        System.out.println(file.exists());
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uo7wMD0N-1676547058302)(F:\typora插图\image-20230207170849995.png)]

删除的方法

返回值方法签名说明
booleandelete()根据File对象,删除该文件。成功删除返回true,失败返回false
voiddeleteOnExit()根据File对象,标注文件即将被删除,删除动作到JVM运行结束才会进行
    public static void main(String[] args) throws IOException {
        //创建方法一:直接使用绝对路径的字符串构造
        File file1=new File("f:/test.txt");
        //创建方法二:使用相对路径构造(说明是跟idea所在路径下进行构造的)
        File file2=new File("./test.txt");
        //创建方法三:使用父路径字符串+孩子路径字符串
        File file3=new File("f:/","./test.txt");

        File file=new File("f:/test.txt");
//        System.out.println(file.getPath());
//        System.out.println(file.getName());
//        System.out.println(file.getPath());
//        System.out.println(file.getAbsolutePath());
//        System.out.println(file.getCanonicalFile());//注意这里因为某些字符不能跨平台使用,需要进行异常处理

//        System.out.println(file.exists());
//        System.out.println(file.isDirectory());
//        System.out.println(file.isFile());
//        System.out.println(file.canRead());
//        System.out.println(file.canWrite());
//        System.out.println(file.createNewFile());
//        System.out.println(file.exists());

        file.deleteOnExit();
        System.out.println(file.exists());
        System.out.println(file.delete());
        System.out.println(file.exists());
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c4H5IGs1-1676547058303)(F:\typora插图\image-20230207171037473.png)]

与目录有关的方法

返回值方法签名说明
String[]list()返回File对象代表的目录下的所有文件名
File[]listFiles()返回File对象代表的目录下的所有文件,以File对象表示
booleanmkdir()返回File对象代表的目录
booleanmkdirs()返回File对象代表的目录,如果必要,会创建中间目录
    public static void main(String[] args) throws IOException {
        //创建方法一:直接使用绝对路径的字符串构造
        File file1=new File("f:/test.txt");
        //创建方法二:使用相对路径构造(说明是跟idea所在路径下进行构造的)
        File file2=new File("./test.txt");
        //创建方法三:使用父路径字符串+孩子路径字符串
        File file3=new File("f:/","./test.txt");

        File file=new File("f:/test.txt");
//        System.out.println(file.getPath());
//        System.out.println(file.getName());
//        System.out.println(file.getPath());
//        System.out.println(file.getAbsolutePath());
//        System.out.println(file.getCanonicalFile());//注意这里因为某些字符不能跨平台使用,需要进行异常处理

//        System.out.println(file.exists());
//        System.out.println(file.isDirectory());
//        System.out.println(file.isFile());
//        System.out.println(file.canRead());
//        System.out.println(file.canWrite());
//        System.out.println(file.createNewFile());
//        System.out.println(file.exists());

//        file.deleteOnExit();
//        System.out.println(file.exists());
//        System.out.println(file.delete());
//        System.out.println(file.exists());

        File file4=new File("f:/test/111");
        String[] s=file4.list();
        File[] f=file4.listFiles();
        for (String t:s) {
            System.out.print(t+" ");
        }
        System.out.println();
        for (File t:f) {
            System.out.print(t.getName()+" ");
        }
        System.out.println();

        //已经存在了,所以创建都是失败的
        System.out.println(file4.mkdir());
        System.out.println(file4.mkdirs());

        //不需要创建中间目录
        File file5=new File("f:/test/333");
        System.out.println(file5.mkdir());

        //有可能需要创建中间目录
        File file6=new File("f:/test/444/aaa");
        System.out.println(file6.mkdirs());
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7lnZpCHe-1676547058303)(F:\typora插图\image-20230207173305161.png)]

修改名字

返回值方法签名说明
booleanrenameTo(File dest)进行文件改名,也可以视为我们平时的剪切、粘贴操作
    public static void main(String[] args) throws IOException {
        //创建方法一:直接使用绝对路径的字符串构造
        File file1=new File("f:/test.txt");
        //创建方法二:使用相对路径构造(说明是跟idea所在路径下进行构造的)
        File file2=new File("./test.txt");
        //创建方法三:使用父路径字符串+孩子路径字符串
        File file3=new File("f:/","./test.txt");

        File file=new File("f:/test.txt");
//        System.out.println(file.getPath());
//        System.out.println(file.getName());
//        System.out.println(file.getPath());
//        System.out.println(file.getAbsolutePath());
//        System.out.println(file.getCanonicalFile());//注意这里因为某些字符不能跨平台使用,需要进行异常处理

//        System.out.println(file.exists());
//        System.out.println(file.isDirectory());
//        System.out.println(file.isFile());
//        System.out.println(file.canRead());
//        System.out.println(file.canWrite());
//        System.out.println(file.createNewFile());
//        System.out.println(file.exists());

//        file.deleteOnExit();
//        System.out.println(file.exists());
//        System.out.println(file.delete());
//        System.out.println(file.exists());

        File file4=new File("f:/test/111");
        String[] s=file4.list();
        File[] f=file4.listFiles();
        for (String t:s) {
            System.out.print(t+" ");
        }
        System.out.println();
        for (File t:f) {
            System.out.print(t.getName()+" ");
        }
        System.out.println();

        //已经存在了,所以创建都是失败的
        System.out.println(file4.mkdir());
        System.out.println(file4.mkdirs());

        //不需要创建中间目录
        File file5=new File("f:/test/333");
        System.out.println(file5.mkdir());

        //有可能需要创建中间目录
        File file6=new File("f:/test/444/aaa");
        System.out.println(file6.mkdirs());

        File file7=new File("f:/test/555");
        System.out.println(file6.renameTo(file7));
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iVIoCwsc-1676547058304)(F:\typora插图\image-20230207174231509.png)]

这里的false是因为上一次运行已经创建了。

三、流对象的使用

我们已经知道文件内容的操作需要流对象来帮助,所以在正式介绍标准库中类之前,我们首先来认识流的概念。

什么是流

猛的一听,好像很抽象。但其实,也不难理解。这里的流是计算机对于读取文件方式的一种形象化的比喻。

与水流类似的是,假设我们有一个水龙头,我们可以采用小水杯接水,也可以采用大水桶接水,更可以直接采用锅接水……接收的水流量完全取决于我们使用的容器,工艺允许的前提下,水量可以无限小,也可以无限大。

类比于此,假设我们有一个缓冲区,我们可以采取标准库提供的字节流对象和字符流对象读取内容。**这里的字节流对象和字符流对象其实就是一种盛装内容的容器。**正如水的基本单位是原子,而这些内容的基本单位是比特,结合实际需要,我们往往不是直接取一个基本单位,而是采用更大一些的容器少次多量的取它。

这里的字节流对象就是以字节为单位从缓冲区读取内容,字符流就是以字符为单位从缓冲区读取内容。换而言之,字节流对象一般应用于二进制文件内容的读取,字符流对象一般应用于文本文件的读取。字节流对象每次读取一个字节,字符流每次读取一个字符,至于几个字节,这跟编译器采用的编码方式有关。例如,utf8字符集规定每个中文字符占3个字节,每个英文字符占1个字节;gbk编码集规定每个中文字符占2个字节,每个英文字符占1个字节。

文件内容操作涉及内容

其实不管是字节流对象也好,字符流对象也罢,他们主要内容也就这几方面:

  1. 打开文件
  2. 读文件
  3. 写文件
  4. 关闭文件

所以接下来,我们进行讨论的话,也是主要围绕这四个方面来说的。

说大事专用分割线=======

其实说到上边四个方面这个小部分应该已经算完了,但是由于我自己懵过的圈,我还是想请各位朋友注意一下这个问题——对于计算机而言,哪个方向是输入,哪个方向是输出?

首先说结论:从硬盘读文件是输入,向硬盘写文件是输出。

为什么会这样呢?我们不妨看下边这张图。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZQYmshJ4-1676547058304)(F:\typora插图\image-20230207183903544.png)]

字节流对象

字节流对象的话,标准库为我们提供了两个抽象类,分别是InputStream和OutputStream,还有很多继承这两个抽象类的类,比如FileInputStream和FileOutputStream等。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R4kv01Ql-1676547058304)(F:\typora插图\image-20230207182246911.png)]

但万变不离其宗,对于这一堆类,最关键的还是InputStream和OutputStream这两个抽象类中的几个方法,所以,我们下边重点解读这些方法,之后进行方法的演示。

InputStream

InputStream是输入流,通过回顾上边我们已经明确过的文件内容操作,不难发现,对于输入流对象而言,它主要的方法就是打开文件、写文件和关闭文件。【注意:这里去回顾对于计算机而言,到底什么是读、什么是写,什么是输入,什么是输出】对应到InputStream这个类,对应的功能/方法也就是构造对象、read方法(从硬盘角度看),close方法。

对于构造方法而言,我们知道,抽象类无法直接构造对象,所以,我们这里介绍它其中一个子类(FileInputStream)的构造方法,其他的与之类似。

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

目前的话,我经常使用第二种,直接传入一个代表绝对路径的字符串。

对于close方法,没有什么好说的,直接在本文件流对象使用完毕后就进行close操作,关闭字节流即可。如果不管,有一定概率会有bug,这个问题,在最后进行讨论。

接下来,重头戏来了,那就是read方法的使用。

下边就是read方法及其重载方法。

返回值方法签名说明
intread()(字符编码/结束标志)读取一个字节的数据,返回-1代表已经完全读取完成
intread(byte[] b)(字符串组实际存放数据量/结束标志)若剩余量大于等于b.length,读取b.length字节数据到b中,否则,返回实际读取的量;-1代表已经读取完成,而不是0!!!
intread(byte[] b,int off,int len)最多读取len-off字节的数据到b中,放在从off开始,返回实际读取到的数量;-1代表已经读取完成

对于read方法,我们需要重点理解的就是下边这两个问题

  • 既然这是字节流对象,为什么返回值不是byte而是int?
  • 当传入字节数组时,不同的文件“余额”下,进行一次读取后,数组的内容是什么?

首先,我们解释一下返回值的问题。

这里的返回值是解决是否读取完成判断的问题。因为我们当返回值是非-1时我们很难通过拿到数据来判断是不是读取完成,而通过-1这个标志位的设置,我们就可以很容易的知道。

而对于数组内容,不能很容易的确定,它是跟“文件余额”有关系的。

当“文件余额”>=字节数组长度时,数组每次读取都装满;

当“文件余额”<字节数组长度时,数组读到多少装多少,剩下的???

下边,我们通过代码进行演示。

public static void main(String[] args) throws IOException {
     InputStream inputStream1=new FileInputStream("f:/test/111/aaa.txt");//异常处理
     //先演示一下无参版本
     while(true){
         int b=inputStream1.read();//异常处理,由于此处声明的异常是上次的父类,所以覆盖了
         if(b==-1){
             System.out.println("本次读取完毕。");
             break;
         }
         System.out.printf("%x ",(byte)b);
     }
     System.out.println();
     //因为我们不确定到底文件内容有多少,所以我们一般采取固定大小字节数组+while死循环
     InputStream inputStream2=new FileInputStream("f:/test/111/aaa.txt");
     while(true){
         byte[] buffer=new byte[1024];
         int len=inputStream2.read(buffer);
         System.out.println("本次读取长度:"+len);
         if(len==-1){
             break;
         }
         //注意这里for循环的位置,因为数组每次循环都会被刷新
         //所以for循环必须是在while循环里边
         //实际开发过程中基本也是这样读取一次,处理一次,否则会有数据丢失
         for (int i = 0; i < len; i++) {
             System.out.printf("%x ",buffer[i]);
         }
     }
     inputStream1.close();
     inputStream2.close();
 }
 /**
     * 因为这里是使用的一个输入流对象读取的,所以下边用数组读的时候就会直接返回-1
     * 要想达到那样的效果就使用两个输入流对象
     */

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-11PYbjvs-1676547058305)(F:\typora插图\image-20230207200010743.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6jM58IEO-1676547058306)(F:\typora插图\image-20230207200243349.png)]

因为这里是utf8编码,所以一共是7*3=21个16进制数字。通过在线编码网页,可以发现这些转译过来恰好是此文件显示的内容

另外,这里有一个知识点就是,这里read(字符数组)采用的是输出型参数的设计,在java中是比较少见的,而在C++中比较常见。

具体什么叫做输出型参数呢?一般而言,java是输入信息作为参数,输出信息作为返回值,输出型参数就是输入信息即做参数,也做返回值。

比如这里的read,返回的要不是输入信息的容量/输入信息的内容要不是结束标志。

OutputStream

与InputStream对应的,对于OutputStream,我们主要关注输出流对象构造、write方法、输出流对象关闭close,除此以外,多了一个flush刷新的操作。

另外,我们在这里特别说明一下,对于OutputStream及其子类,默认情况下,打开一个文件会清空文件原有的内容,如果不想清空,流对象还提供了一个“追加写”对象。这里暂时不介绍,使用到再进行补充。

对于构造方法,也是看它的子类FileOutputStream,构造代码与之前类似,待会一块演示。close除了不close可能会bug这个问题外没什么好说的。

对于write方法,我们需要掌握下边它的几种形式,使用比较简单。

返回值方法签名说明
voidwrite(int b)写入要给字节的数据
voidwirte(byte[] b)将b这个字符数组中的数据全部写入os内
voidwrite(byte[] b,int off,int len)将b这个字符数组中从off开始的数据写入os中,一共写len个

对于flush方法,其实就是缓冲区与将内容写入文件之间的桥梁。

我们知道 I/O 的速度是很慢的,所以,大多的 OutputStream 为 了减少设备操作的次数,在写数据的时候都会将数据先暂时写入内存的 一个指定区域里,直到该区域满了或者其他指定条件时才真正将数据写 入设备中,这个区域一般称为缓冲区。但造成一个结果,就是我们写的 数据,很可能会遗留一部分在缓冲区中。需要在最后或者合适的位置, 调用 flush(刷新)操作,将数据刷到设备中。

返回值方法签名
voidflush()

下边我们通过代码进行实验

public static void main(String[] args) throws IOException {
 OutputStream outputStream=new FileOutputStream("f:/test/111/bbb");
 try {
     outputStream.write(97);
     outputStream.write(98);
     outputStream.write(99);
     outputStream.write(100);
 } catch (IOException e) {
     throw new RuntimeException(e);
 } finally {
     outputStream.close();
 }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QhRflqOF-1676547058306)(F:\typora插图\image-20230207203941436.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CFZFg110-1676547058307)(F:\typora插图\image-20230207203855672.png)]

【其实中间异常捕捉去掉也可以,因为上边已经有它父类异常的声明了】

看起来好像是没什么问题,那实际呢?

我们来思考这样一个问题,当多线程环境下,正准备执行close时,发生了线程切换,多次重复,文件描述符表满了怎么办?

再进一步探讨此问题之前,我们来了解一下什么叫做文件描述符表,其实也就是前边我们遗留下来的问题——为什么close不关闭可能会产生bug。

谈及这个问题,我们就必须要了解close操作的本质。

close操作含义是关闭流对象,也就等价于关闭文件。

我们前边已经知道线程是有pcb唯一标识的,pcb中有一个重要的属性——文件描述符表,它记录这该线程打开了哪些文件。补充一点,一个进程里多个线程的文件描述符表是共用的,而它的容量是有限的 。

那就意味着,当我们再一个进程/线程中,如果打开很多文件不关闭,总是等待进程结束JVM帮我们关闭,工作内容足够多,打开的文件足够多,总共有一个瞬间及往后,这个文件描述符表是会满的,从而会产生bug。

close的工作内容就是此流对象对应的文件从这个文件描述符表释放掉。

搞清楚了close的工作内容,我们回到上边那个问题,很显然上述代码无法保证当前流对象一定被关掉,那么我们这里提供一个解决方案:

try with resources

直接将流对象的定义放到try()里边,这样try执行完了,流对象也就自动销毁了,就不需要考虑文件关闭的问题了。

但并不是所有的对象放进try括号里都会自动释放,必须是实现Closeable接口的类的对象才可以。

public static void main(String[] args) throws IOException {
 try (OutputStream outputStream=new FileOutputStream("f:/test/111/bbb")){
     outputStream.write(97);
     outputStream.write(95);
     outputStream.write(99);
     outputStream.write(100);
 }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TOQcp3Zf-1676547058308)(F:\typora插图\image-20230207210120254.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bu9GhHJl-1676547058308)(F:\typora插图\image-20230207210148740.png)]

字符流对象

字符流对象的话,标准库为我们提供了两个抽象类,分别是Reader和Writer,还有很多继承这两个抽象类的类,比如FileReader和FileWriter等。与字节流对象类似的是,这一堆类里边,最终要的还是Reader和Writer这一组抽象类里边的几个方法,重点解读过后,我们再进行演示。

Reader

Reader的话,用法与InputStream类似。通过看官方文档,read方法也是类似,但是,相较于InputStream,它是字符数组,另外,它的read方法多了一个版本,即read(CharBuffer target),但是它只是对字符数组的封装。

这里就不再过多说明了。

public static void main(String[] args) {
    try(Reader reader=new FileReader("f:/test.txt")){
        while(true){
            int ch=reader.read();
            if(ch==-1){
                break;
            }
            System.out.println((char)ch);
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
Writer
public static void main(String[] args) {
    try(Reader reader=new FileReader("f:/test/111/aaa.txt")){
        while(true){
            int ch=reader.read();
            if(ch==-1){
                break;
            }
            System.out.println((char)ch);
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

相对路径理解参考

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值