文件与 IO

本文介绍了I/O流的基本概念,通过生活中的类比阐述了流的特性,包括输入流、输出流、字节流、字符流、节点流和处理流的区别。此外,还讨论了如何在Java中选择合适的流进行文件操作,特别是字符流与字节流的选择策略,以及节点流和处理流的实际应用场景和使用技巧。
摘要由CSDN通过智能技术生成

1.I/O流是什么?

对于陌生的东西,最好通过类比的方法去了解。我对于流的理解:流就是一系列数据。通过生活中常见的现象进行类比,比如:流中全部是水,我们称为“水流”,流中全是电子,我们称为“电流”。那我们可以根据流中的数据类型,来给流命名。再回到计算中,流中是字节的我们叫“字节流”,是字符的叫“字符流”,等等。

对于笔者来说,在Java开发中需要用到获取文件信息,经常需要用到流。那个时候不理解流,只知道抄着代码,这里改改那里修修。以至于,每当需要操作文件的时候不知道如何开始下手,对于整个流程不清楚。每当去百度文件操作的时候,又发现很多种实现方法,用的流都不一样,实现起来也有细微差别,搞得笔者也是心态爆炸,于是下决心把这块内容梳理好,方便以后使用。

关于这部分内容目前存在以下几个问题:

  • 如何理解文件在流中的角色?
  • 文件操作的整个流程?
  • 文件操作选用什么流?
  • 不同的流的差异与特点

**流的一些补充:**上面粗略解释了流,现在需要对流进行一个补充。我们说的电流和水流都是从一端流向另一端(可能并不是很准确),比如:电池的电子从负极流向正极,电池的负极像一个电子的生产者一样,而正极就像一个消费者或者接收者一般;还有地下水到你家的水龙头一样,地下水通过加工提取到人引用。通过这两个例子,可以得出一个结论,流的操作或者使用至少需要一个生产对象和一个接受对象。在计算机世界中已经为这两个对象命名,输入对象和输出对象。

2.流的分类

关于流的分类,在Java中有如下3种分类标准:

  1. 按照流的流向分,可以分为输入流和输出流。
  2. 按照操作单元划分,可以分为字节流和字符流。
  3. 按照流的角色划分,可以分为节点流和处理流

流的分类解读:

  • 根据流向分类:在上面的内容补充中,说流都是从一端流向另一端,说明了流有方向。所以根据流的方向可以分为输入流和输出流,辨别输入流和输出流需要选参照中心。我们以程序作为参照中心,读取本地磁盘上的文件,程序端接收,就说明该是输入流;将程序产生的数据保存到磁盘,程序段输出,就说明是输出流。在Java中所有的IO流的类都是从以下4个抽象类基类中派生出来的:InputStream/Reader所有输入流的基类,前者是字节输入流,后者是字符输入流;OutputStream/Writer所有输出流的基类,前者是字节输出流,后者是字符输出流
  • 根据操作单元划分:首先需要理解什么是操作单元,我把他理解为所操作数据的最小单位。我们知道一个字符等于两个字节,字节流取数据按照一个一个字节取,字符流取数据就是一个字符一个字符取。就好比,我们用调羹喝汤,一调羹的量就好比字节,而用勺子喝汤,一勺子的量就好比字符,只不过一勺子比一调羹的量多很多,而字符是一个字节的两倍。
  • 根据流的角色划分:节点流和处理流。节点流:可以从一个特定的数据源(称作节点)读写数据,如:内存,硬盘,文件等。处理流:可以理解为,根据实际需求在节点流的基础上进行的二次开发和封装。
          例子1:说明处理流和节点流的关系:想象一个场景,计算机每一次读写的时候都访问硬盘,如果访问次数很频繁的话,性能表现就不佳,而且硬盘的寿命也会减少。所以为了满足这种场景,就设计了缓存流,一次读取较多数据到缓存中,以后的每一次读取都从缓存中访问,直到缓存中的数据读取完毕。用一个生活的例子解释缓存流,吃饭的时候我们不用碗装饭(缓存流)的话,每吃一口都要去电饭锅里面铲,有了碗(缓存流)之后,我们可以把饭装进碗里,吃完了碗里的,我们再去电饭锅里铲。这样就大大减少了去电饭锅里装饭的次数,提高了效率。
                 例子2:因为我们的流的操作最小单位是字节或者字符,为了方便Java程序员使用对象,我们就需要在原来节点流的基础上进行二次开发和封装。比如我们读取一个本地的文本文件,需要一个字节流,又由于流里面是字节,而我们希望方便程序员使用对象来保存这些数据,就在该流的基础上进行开发(具体如何实现,本人暂时也不清楚),原来的字节流就变成了一个对象流,流里面的内容不在是字节,而是对象,这样就方便了程序员对于该信息的获取。

以下是关于字节流和字符流,以及节点流和处理流的详细分类图

3.关于字符流和字节流的选择

之前在学校做课设的时候,每次做到文件关于文件读取的时候,关于选择字节流还是字符流的选择都是含糊不清,每次都是去网上找代码,这部分知识不是很清楚。现在重新把知识捡起来好好梳理一遍。首先不管是文件读写还是网络发送接收,信息的最小存储的单位是字节。由此可能读者又会问了,既然最小存储单元是字节,那么有了字节流为什么还要字符流呢?

**解答:**在编程领域中,由于语言的不同,中文等一些其他语言一个字就需要两个字节表示,即一个字符,所以如果不使用字符流的话,就会出现乱码等问题。在日常使用中遇到图片,音频等媒体文件采用字节流比较好,而涉及到字符操作的使用字符流比较好。

使用文件对象

说明: 一个File对象就是一个具体的文件或者文件夹目录(作用:指定文件或文件夹保存的路径)。让然,指定的文件或文件夹目录在电脑上不一定存在。以下代码演示了文件对象的用法以及常用的方法。

//创建一个文件对象,指定他的目录:D盘目录下的java文件夹
File file = new File("D:\\java\\test");

boolean exists()  //判断文件或者文件夹是否存在
boolean isDirectory()  //判断是否是文件夹
boolean isFile()  //判断是否是文件

long length()  //获取文件的长度
long lastModified() //获取文件最后修改时间
boolean setLastModified(long time)  //设置文件最后修改时间
boolean renameTo(File dest)  //文件重命名
    
注:renameto方法重新命名,指的是在磁盘上文件"1.txt"变成"2.txt"但是通过file.getName()获得的文件名还是“1.txt”,无论是命名前还是命名后。

String[] list()  // 以字符串数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)
File[] listFiles() // 以文件数组的形式,返回当前文件夹下的所有文件(不包含子文件及子文件夹)
      
String getParent()   // 以字符串形式返回当前文件所在文件夹
File getParentFile() // 以文件形式返回当前文件所在文件夹

boolean mkdir()    // 创建文件夹,如果父文件夹不存在,创建就无效。举例:使用第一行的file对象,如果D盘				   // 根目录下没有java文件夹,则目录创建失败,java目录,test目录都没有创建。
    				
boolean mkdirs()   // 创建文件夹,如果父文件夹不存在,就会创建父文件夹。举例:如果D盘根目录下没有java				   // 文件夹,就会连同java文件夹一并创建,java目录和test目录都创建成功。 

boolean createNewFile()  // 创建一个空文件,如果父文件夹不存在,就会抛出异常。

/*
	由于创建文件,父文件夹不存在就会抛出异常,所以创建文件的时候需要先创建父文件夹。使用上面提到的方法创建文件夹。例如:file.getParentFile().mkdirs();
*/
File[] listRoots()  // 列出电脑上所有的盘符c: d: e: 等等
boolean delete()    //删除文件或者文件夹
void deleteOnExit() // JVM结束的时候,刪除文件,常用于临时文件的删除

流与文件

       在做课设的时候经常会遇到文件的读取等功能,在还没弄清楚流的时候就已经强迫使用文件操作,导致每次遇到文件读取问题都是网上copy类似代码,却不能很好的理解代码执行流程,或者理解了也很快就忘记了,因为这个记忆点并不深。所以现在打算简单的梳理下文件的读取操作。

       开始之前,我们需要思考下几个问题:1.先要搞清楚”流向”,我们把我们的程序作为参照物,我们的程序是要读取文件还是写入文件?2.是哪个文件?(即文件所在的位置)3.根据文件的类型,我们该选用什么类型的流?通过思考这几个问题,使得我们对于文件的读取流程就会很清晰了。

  1. 假设我们程序需要读取D盘根目录下的xxx.txt文本文件,通过使用File构建一个对象file,并告诉我们的程序文件位于D:\\xxx.txt

    File file = new File("D:\\xxx.txt");
  2. 告诉程序文件的位置之后,我们就需要构建一个流了。让文件数据通过流,流向程序。构建流的时候我们又需要考虑了,是输入流还是输出流,显然我们在流的章节中说道,数据是流向程序,所以我们要选用输入流。选用输入流之后还要思考,是要字节流还是字符流呢?这个问题我们要确定文件类型,显然我们这里是文本类型的,对于文本前面也提到了最好使用字符流,防止文本里面有中文字符。所以综合来说我们选用字符输入流

    /*
    *try后面加括号是Java7中的技术,这样的好处是:把流定义在try()里,try,catch或者finally结束的时候,会自
    *动关闭流。这样就不要像以前一样,需要自己写代码关闭了。
    * 1.首先我们要使用上面生成的文件对象file,用来构建一个输入字符流fis。
    */
    try (FileReader fis = new FileReader(file)) {
    //2.因为我们选用的是字符流,所以我们新建一个字符数组用来保存流中的数据,数组长度就是文件的长度
        char[] content = new char[(int) file.length()];
    //3.使用read方法把流中的数据全部保存到content字符数组中。
        fis.read(content);
    } catch (IOException e) {
        e.printStackTrace();
         }
  3. 通过上面的步骤,我们就完成了把D盘目录下的xxx.txt文件内容保存到了content数组中,接下来我们可以   对这个数组进行很多操作,比如将数据写入一个txt文件等等。

  4. 继续完善我们的操作,我们把刚才读取的文件数据重新写入到一个新的文件中。根据步骤2中关于选择流的思考,这里我们同样需要思考,流的选择。在了解步骤2的基础下,我们可以快速的得出,我们是需要一个字符输出流

    	File file = new File("D:\\xxx.txt");
    //这里我们告诉程序,我们的文件是D盘根目录下的zz.txt文件
    	File file1 = new File("D:\\xx.txt");
    //通过file1对象构建字符输出流
        try (FileReader fis = new FileReader(file);FileWriter fileWriter = new FileWriter(file1)) {
                char[] content = new char[(int) file.length()];
                fis.read(content);
    //将从zzf.txt文件中读取保存的数据content,写入到新文件zz.txt中
                fileWriter.write(content);
    
         } catch (IOException e) {
                e.printStackTrace();
            }

  5. 这样我们就简单的实现了一个文件的读取。有人或许会问,你前面说的节点流和处理流你咋没     用到呢?其实回去看第一部分,再仔细分析的话我们这个字符输入输出流就是一个节点流,所       以我们可以把效率提高一点,在这个基础上我们使用缓存流。

  6. 加上缓存流,提高效率。前面也说了处理流是在节点流上进行二次开发和封装的,所以用到处理流肯定就需要节点流。就像通过文件对象构建流一样,处理流通过节点流来进行构建。

    File file = new File("D:\\xxx.txt");
         File file1 = new File("D:\\xx.txt");
         try (FileReader fis = new FileReader(file);FileWriter fileWriter = new FileWriter(file1)) {
             char[] content = new char[(int) file.length()];
             //使用节点流FileReader对象构建处理流BufferedReader
             BufferedReader bufferedReader = new BufferedReader(fis);
             //读取文件数据
             bufferedReader.read(content);
    //使用节点流FileWriter对象构建处理流BufferedWriter
             BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
             //写入文件数据
             bufferedWriter.write(content);
             //有的时候,需要立即把数据写入到文件,而不是等缓存满了才写出去,这时候就需要用到flush
             bufferedWriter.flush();
    
         } catch (IOException e) {
             e.printStackTrace();
         }
  7. 注意事项:

    1. 我们使用流的时候一般都是需要手动关闭的,上面的代码都没有写出关闭的代码,是因为我们把流的构建这一步写进了try()里面,所以不需要手动关闭代码,自己写的话,要么和我代码一样,要么就在流使用完之后就写一行代码关闭流。
    2. 还有一个大家可能没有考虑到的情况,我们刚才使用了缓冲流之后,如果手动关闭的话是不是需要关闭两个流呢?实际上,我们只需要关闭节点流就行了,因为处理流是在节点流的基础上构建的,我们关闭了节点流,处理流就失去了节点流,就会自动销毁。
    3. 最后说一个关于缓存流写入的问题,因为缓存流是有缓存空间的,只有缓存空间满了,缓存空间的数据才会写入到文件中。通过查看源代码发现默认设置的缓存空间是8kb。因此,当你使用缓存流读取文件的时候,如果文件大小小于8kb,没有使用flush方法的话,要写入的文件就没有数据,因为数据都在缓存区中。我们使用flush方法就可以将缓存区的数据强制写入到文件中,无论缓冲区的数据大小。
  • 29
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值