【JavaEE】文件操作和IO

news2024/11/26 8:45:27

目录

1、认识文件

 1.1、路径

1.1.1 、绝对路径 

1.1.2、相对路径 

1.2、文本文件 vs 二进制文件 

 2、文件系统操作

3、文件内容操作

3.1、字节流用来输入的抽象类InputStream的方法使用

3.1.1、FileInputStream类的构造方法

 3.1.2、字节流读操作

 3.1.3、字节流写操作

3.2、 字符流的读写操作

4、文件操作的案例


1、认识文件

我们平时谈到的"文件",指的都是硬盘上的文件。我们在进行数据保存的时候,往往不是保存成一个整体,而是独立成一个个单位进行保存,这个独立的单位就被抽象成文件的概念,就类似办公桌上的一份一份真是的文件。

硬盘(外存)和内存相比

  • 速度:内存比硬盘快很多
  • 空间:内存空间比硬盘小
  • 成本:内存比硬盘贵
  • 持久化:内存掉电后数据会丢失,外存掉电后数据还在。

🎉文件的元信息:文件除了有数据内容之外,还有一部分信息,例如文件名,文件类型、文件大小等并不作为文件的数据而存在,我们把这部分信息可以视为文件的元信息。 

 我们之前博客中的代码,绝大部分是围绕内存展开的(JavaSE和数据结构),定义一个变量其实就是在内存上申请空间。MySQL主要就是操作硬盘。我们这个博客中的文件IO也是操作硬盘。


 1.1、路径

🎃路径文件系统上一个文件/目录(文件夹)具体位置

✨文件系统是以树形结构来组织文件和目录——这个树形结构为(N叉树) 。

🎉文件路径:就是从数根结点出发,沿着树杈,一路往下走,到达目标文件,此时这中间经过的内容就是文件路径。

📙 实际表示路径,是通过一个字符串表示,每个目录之间使用\\或者/来分割

  1.  上边的图片中我们看到每个目录之间是使用\表示的,反斜杠(\)只是在Windows中适用,我们在写代码的时候需要写成\\,用转义字符将\转换。所以我们在写代码的时候,还是比较建议使用、。
  2. 上述看见两个图中一个路径存在此电脑,一个不存在此电脑。存在此电脑的是因为我们是按照层级打开目录,所以电脑上会显示。另外一个不存在此电脑的是因为,我们想要找(或者是表示)一个文件的时候,默认是从盘符开始的。表示路径的时候,可以把"此电脑"省略,直接从盘符开始表示。

1.1.1 、绝对路径 

从盘符开始,一层一层往下找,这个过程,得到的路径,就是绝对路径。

1.1.2、相对路径 

从给定的某个目录出发,一层一层往下诏,这个过程得到的路径,就是相对路径。

 . 在相对路径中,表示当前目录;

.. 在相对路径中,表示的是当前目录的上级目录。

❗❗❗注意:

  • 相对路径,一定要明确工作目录(基准目录)是什么。 
  •  文件系统上任何一个文件,对应的路径是唯一的,不会存在两个路径相同,但是文件不同的情况。在Linux上可能存在一个文件,有两个不同的路径能找到它;但是在Windows上不存在,Windows上可以认为,路径和文件是一一对应的,路径就相当于一个文件的"身份标识"。

1.2、文本文件 vs 二进制文件 

1️⃣文本文件:存储的是文本(文本文件的内容都是由ASCII表字符构成)。文本文件里存储的数据,就是遵守ASCII或者其他字符集编码(例如:utf8),所得到的文件,本质上存的是字符(不仅仅是char)。

2️⃣二进制文件:存储的是二进制数据,(存储不受任何字符集的限制)

 ✨判定一个文件是文本文件还是二进制文件的方法。

  • 直接使用记事本打开某个文件,如果打开之后的内容你能够看懂,这个文件就是文本文件;如果你看不懂,内容乱糟糟的,这个文件就是二进制文件。(因为记事本是默认按照文本的形式来解析显示的,解析成功就是文本文件,解释失败就是二进制文件)
  • 文本文件:文件后缀为.txt,.java,.c
  • 二进制文件:文件后缀为 .class ,.exe,.jpg,.mp3

 2、文件系统操作

Java标准库给我们提供了File这个类,Flie对象是对硬盘上的一个文件的"抽象"表示。文件是存储在硬盘上的,直接通过代码操作硬盘,不太方便,就在内存中创建一个对象,操作这个内存中的对象,就可以间接的影响到硬盘的文件情况了。

1️⃣构造File对象

构造的过程中,可以使用绝对路径或者相对路径进行初始化,这个路径指向的文件,可以是真实存在的,也可以是不存在的。

public class IODemo1 {
    public static void main(String[] args) {
        //初始化这个对象的时候,这个文件可以真实存在也可以不存在。
        File file = new File("d:/dog.jpg");
    }
}

2️⃣File类提供的一些方法

 写一些代码来了解这些方法的使用

🎉观察get系列方法的特点和差异

public class IODemo1 {
    public static void main(String[] args) throws IOException {
        //初始化这个对象的时候,这个文件可以真实存在也可以不存在。
        File file = new File("d:/dog.jpg");
        //获取File对象的父目录文件路径
        System.out.println(file.getParent());
        //获取File对象的文件名
        System.out.println(file.getName());
        //获取File对象的全路径
        System.out.println(file.getPath());
        //获取File对象的绝对路径
        System.out.println(file.getAbsoluteFile());
        //获取File对象的修饰过的绝对路径
        System.out.println(file.getCanonicalFile());
    }
}

 🎉普通文件的创建、删除。

public class IODemo2 {
    public static void main(String[] args) throws IOException {
        //初始化的时候,这个文件目录的写法,是相对路径的一种写发,./通常可以省略
        File file = new File("hello_world.txt");
        //判断这个文件是否真实存在
        System.out.println(file.exists());   //false
        //判断File对象代表的文件是否是一个目录
        System.out.println(file.isDirectory());  //false
        //判断File对象代表的文件是否是一个普通文件
        System.out.println(file.isFile());   //false

        //创建文件
        file.createNewFile();
        System.out.println(file.exists());  //true
        System.out.println(file.isDirectory());   //false
        System.out.println(file.isFile());  //true
    }
}

❗❗❗注意:IEDA工作目录就是项目所在目录,写相对路径时,就是以system_code这一级为基准,来展开的。

🎉创建目录 

public class IODemo3 {
    public static void main(String[] args) {
        File file = new File("text-dir/aaa/bbb");
        //只能创建一级目录
        file.mkdir();
        //可以创建多级目录
        file.mkdirs();

    }
}

🎉列出一个目录下包含那些内容

public class IODemo5 {
    public static void main(String[] args) {
        File file = new File("text-dir");
        String[] results1 =  file.list();
        //数组的打印,需要使用数组的工具类调用toString方法进行打印,才能打印出数组当中的内容,要不然打印的是数组的hash值
        System.out.println(Arrays.toString(results1));
        File[] results2 = file.listFiles();
        System.out.println(Arrays.toString(results2));

    }
}

🎉针对文件或目录重命名

public class IODemo6 {
    public static void main(String[] args) {
        File src = new File("./text-dir");
        File dest = new File("./test222");
        src.renameTo(dest);
    }
}


3、文件内容操作

  • 针对文本文件,提供了一组类,统称为"字符流"(典型代表,Reader,Writer),字符流读写的基本单位是字符(根据字符集来确定一个字符占几个字节),字符流每次读写最少是一个字符。
  • 针对二进制文件,提供了一组类,统称为"字节流"(典型代表,InputStream,OutputStream),字节流读写的基本单位是字节(8个bit位),字节流每次读写最少是一个字节。

每种流对象,又分为两种

  • 一种是用来输入的:Reader,InputStream
  • 一种是用来输出的:Writer,OutputStream

 

3.1、字节流用来输入的抽象类InputStream的方法使用

InputStream只是一个抽象类,要使用这个类,需要实例化实现了这个抽象类的子类。InputStream类使用来进行输入的,它的实现类很多,基本上可以认为不同的输入设备都可以对应一个InputStream类,不仅仅是读写硬盘文件,还可以是读写网络编程,或者是读写网卡。本章博客只是针对硬盘文件的输入,所以这里我们只需要实例化FileInputStream类即可。

3.1.1、FileInputStream类的构造方法

public class IODemo7 {
    public static void main(String[] args) throws IOException {
        //这个过程就相当于文件打开操作
        //让当前的inputStream变量和硬盘上的文件关联起来
        InputStream inputStream = new FileInputStream("D:/test.txt");
        //释放资源
        //释放资源这部操作非常重要,不能省略
        inputStream.close();

    }
}
  • 上述代码中的资源释放的操作非常重要,千万不要忘记!因为Java有垃圾回收机制,可以帮助我们将使用过的资源释放掉,但是在文件操作这里垃圾回收机制并不能帮我们把资源释放掉。需要我们手动释放这里说的文件操作的时候需要释放资源,释放的资源主要是文件描述符表
  • 文件描述符表:记载了当前进程打开了那些文件,每次打开一个文件,就会在这个表里,申请到一个位置,这个表可以当成一个数组,数组下标就是文件描述符,数组元素就是这个文件在内核中的结构体的表示。但是这个表的长度是有限制的,不能无休止的打开文件,但是又不释放。一旦文件表述符表满了,继续打开就会打开失败,这就导致了文件资源泄露

上述的这种写法虽然写到了将文件释放,但是这样写,在执行的时候,中间出现一些问题,比如return或者抛异常,就会导致close执行不到了。所以为了保证close一定被执行到,所以这里我们了解一下比较稳妥的写法。

 1️⃣第一种写法:这种写法虽然可以保证一定执行到close,但是这种写法并不好看。

public class IODemo7 {
    public static void main(String[] args) throws IOException {
        InputStream inputStream = null;
        try{
            inputStream = new FileInputStream("D:/test.txt");
        } finally{
            //释放资源
            inputStream.close();
        }
    }
}

2️⃣第二种写法try with resources:带有资源的try操作,会在try代码块结束时,自动执行close关闭操作。

public class IODemo7 {
    public static void main(String[] args) throws IOException {
        try(InputStream inputStream = new FileInputStream("D:/test.txt")){
            
        }
    }
}

之所以上述的写法能够释放资源是因为InputStream实现了一个特定的接口Closeable

 3.1.2、字节流读操作

InputStream类中提供了3种读操作的方法。

 先来了解一下无参的read方法.

public class IODemo7 {
    public static void main(String[] args) throws IOException {
        try(InputStream inputStream = new FileInputStream("D:/test.txt")){
            //读文件
            //无参数的read,相当于一次读一个字节,但是返回类型是int,当读到末尾返回-1
            while(true){
                int b = inputStream.read();
                if(b==-1){
                    //读到末尾了,结束循环
                    break;
                }
                System.out.println(b);
            }
        }
    }
}

❓❓❓上述代码中read方法读取的是二进制文件中的内容,读取出来的数据应该是字节,应该使用byte来接收,为什么使用int类型的变量来接收??


❗❗❗因为源码当中的注释写到read返回的是一个0~255之间的数字,但是如果文件读取完毕,再次读取就会返回-1,这个时候-1没有在这个范围中,所以就要使用int来接收,但是说到这里有的人会说到short也可以,但是应为内存对齐的原因,short并没有int快。

 ✨再来看一下上述程序的执行结果与test.txt文件中的内容之间的关系。

1️⃣二进制文本中内容为英文

2️⃣如果test文件中将内容编辑为中文出现的结果会是什么(test文件中输入中文“世界你好”)

 可以看到上述控制台上输出的10进制数和我们查到的不一样,这时因为我们查到的是把3个字节放在一起,搞了一个大10进制,和咱们控制台上3个字节分开打印是不一样的,我们通过控制它将汉字以16进制打印出来观察结果。

 //按照16进制打印
System.out.printf("%x\n",b);

 

✨读操作的总结:

  1.  read():无参数版本,返回该字节的内容
  2. read(byte[ ] buffer,int offset):这个版本,读到的内容放到参数buffer数组中,此时返回值是读到的字节数。
  3. read(byte[ ] buffer,int offset ,int length):这个版本也是读到buffer数组中,但是不是从头放,是从offset位置放,最多放length长度,返回值也是实际读的长度。

 3.1.3、字节流写操作

可以看到写操作也有多种方法。这里我们来了解每次写一个的字符的方法。

 代码示例

public class IODemo8 {
    public static void main(String[] args) {
        try(OutputStream outputStream = new FileOutputStream("d:/test.txt")){
            outputStream.write(97);
            outputStream.write(101);
            outputStream.write(100);
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}

 写操作的执行结果我们要在对应的二进制文件中寻找。

✨总结:

  • read和write还可以一次读写多个字节,使用byte[]来表示。
  • read会尽可能把byte[]填满。如果读到文件内容末尾,返回-1 
  • write会把byte[]所有数据都写入文件

3.2、 字符流的读写操作

字符流文件读写操作和字节流核心逻辑一样,使用Reader,Writer这两个抽象类的子类的构造方法来打开文件(FileReader,FileWriter).read方法来读(一次读一个char或者char[]);write方法来写(一次写一个char或者char[]或者String),close释放资源。

public class IODemo9 {
    public static void main(String[] args) {
        try(Reader reader = new FileReader("d:/test.txt")) {
            while(true){
                int c = reader.read();
                if(c == -1){
                    break;
                }
                char ch = (char)c;
                System.out.println(ch);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这个代码和上面的字节流读取代码逻辑基本相似,就是在打印的时候,将int类型的c强制类型转换为char类型,这样每次读取打印的内容就是字符。


4、文件操作的案例

遍历目录,在里面的文件内容中查找。

在你的电脑上,有很多目录,目录里面有很多文件,每个文件里有很多内容,假设某些文件中,包含"hello"关键词。下面的程序就是找出那些文件,是包含这个关键词的。

我们这里的实现方式是一个简单粗暴的方式:

  1. 先以递归的方式遍历目录。比如给定一个d:/去递归的把这里包含的所有文件都列出来。
  2. 每次找到一个文件,都打开,并读取文件内容(这个时候就得到一串字符)
  3. 再判定要查询的词,是否在上述文件内容中存在,如果存在,即为所求。
package io;

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

public class IODemo10 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        //1.先让用户指定一个要搜索的根目录
        System.out.println("请输入要扫描的根目录:");
        //将用户输入的内容构造常File对象
        File rootDir = new File(scanner.next());
        //判断一下输入的路径名表示的文件是否为一个目录
        if(!rootDir.isDirectory()){
            System.out.println("输入有误,你输入的目录不存在!");
            return;
        }
        //2.让用户输入一个要查的词
        System.out.println("请输入要查询的词:");
        String word = scanner.next();

        //3.递归的进行目录或者文件的遍历
        scanDir(rootDir,word);

    }

    private static void scanDir(File rootDir, String word) {
        //列出当前rootDir中的内容,没有内容,直接递归结束
        File[] files = rootDir.listFiles();//返回rootDir对象(d盘)下的所有文件
        if(files == null){
            //当前rootDir是一个控的目录,这里啥都没有。
            //就没有必要在递归了,直接返回就行。
            return;
        }
        //目录中有内容,就遍历目录中的每个元素
        for(File f:files){//通过这个for循环遍历这个文件数组
            //设置一个日志
            System.out.println("当前搜索到:"+f.getAbsoluteFile());//打印文件的绝对路径
            if(f.isFile()){
                //判断是普通文件
                //打开文件,读取内容,比较看是否包含上述关键词
                String content = readFile(f);
                //判断看读到的文件内容中是否包含关键字
                if(content.contains(word)){
                    //文件中有要查中的关键字,则输出这个文件的绝对路径
                    System.out.println(f.getAbsoluteFile()+"包含要查找的关键字");
                }
            }else if(f.isDirectory()){
                //判断是目录
                scanDir(f,word);//此处的递归就是以但钱f这个目录作为起点,搜索t目录里面的内容。
            }else{
                //不是普通文件,也不是目录文件直接跳过,以Linux为例,文件种类有很多,普通文件,目录文件,管道文件...
                continue;
            }

        }
    }
    //读取文件的整个内容,返回出来
    private static String readFile(File f) {
        //使用字符流来读取,由于咱们匹配的是字符串,此处只能按照字符流处理,才是有意义的。
        StringBuilder stringBuilder = new StringBuilder();
        try(Reader reader = new FileReader(f)){
            //一次读一个字符,把读到的结果给拼装到StringBuffer中,同意转成String
            while(true){
                int c = reader.read();
                if(c == -1){
                    break;
                }
                //再进行字符串拼接的时候,需要将刚刚读取到的c数据强转成char类型,c的类型本身是一个int,为了防止超出char的范围
                stringBuilder.append((char)c);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return stringBuilder.toString();
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/585870.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Presto从入门到精通以及案例实操系列

1、简介 1.1、Presto的由来 Presto最初由Facebook公司开发,旨在解决Facebook内部大规模数据处理和数据分析的问题。在传统的Hadoop生态圈中,MapReduce作为数据处理框架,虽然能够处理海量数据,但是其查询性能却比较低下&#xff…

《面试1v1》CountDownLatch和CyclicBarrier

我是 javapub,一名 Markdown 程序员从👨‍💻,八股文种子选手。 面试官: 你用过 CountDownLatch 和 CyclicBarrier 吗? 候选人: 当然可以。CountDownLatch 和 CyclicBarrier 都是 Java 中用于多…

通过 docker-compose 快速部署 MySQL保姆级教程

文章目录 一、概述二、前期准备1)部署 docker2)部署 docker-compose 三、创建网络四、MySQL 编排部署1)构建镜像 Dockerfile2)配置文件2)编排 docker-compose.yaml3)开始部署 五、简单测试验证六、常用的 M…

在线排查内存泄漏的步骤

一、在线排查内存泄漏的步骤 想到内存泄漏问题的排查,很多开发会想到使用 Valgrind。使用 Valgrind 有几个局限: 需要安装 Valgrind 需要启停服务进程 影响服务进程性能 依赖于测试用例覆盖到 BUG 分支 由于这些原因,线上内存泄露问题并…

位图、布隆过滤器、海量数据处理

提示: 本文介绍了,位图、布隆过滤器、以及海量数据处理问题。 本节有很多关于大数处理的案例(已解答)。 ——细雨斜风作晓寒,淡烟疏柳媚晴滩。(苏轼) 文章目录 一、位图1.1 位图概念1.2 位图实…

深度学习12—VGG19实现

目录 VGG19实现 1.为数据打标签的generate_txt.py 2.对图像进行预处理的data_process.py 3.VGG19的网络构建代码net_VGG19.py 4.训练得到pth模型参数文件的get_pth_file.py 5.预测代码predict.py 6.预测VGG16与VGG19结果对比 VGG19实现 1.为数据打标签的generate_txt.p…

【git教程】

这里写目录标题 git是什么集中式版本控制系统和分布式版本控制系统git的优势git能做什么(常用)基础教程流程图介绍小节 常用Git命令速查表详解1、HEAD2、add3、commit4、branch5、merge6、rebasemerge和rebase区别7、reset8、revertrevert与reset的区别 git是什么 git是目前世…

【Java算法题】剑指offer_数据结构之02树

前言 刷题链接: https://www.nowcoder.com/exam/oj/ta?page2&tpId13&type265 2. 树 JZ55 二叉树的深度 思路:dep max_deepth(left,right)1,二叉树的深度为根节点到叶子节点,使用递归访问根节点的左孩子和右孩子&…

想要让数据更生动?试试这5种图表工具

在当今大数据时代,数据的利用和分析在各个领域的工作中起着重要的作用。因此,数据可视化图形工具已经成为数据分析的好帮手。事实上,数据可视化的本质是视觉对话。它通过图形手段清晰直观地表达信息,从数据中获得价值。然而&#…

Netty实战(九)

单元测试 一、什么是单元测试二、EmbeddedChannel 概述三、 使用 EmbeddedChannel 测试 ChannelHandler3.1 测试入站消息3.2 测试出站消息 一、什么是单元测试 单元测试的基本思想是:以尽可能小的区块测试代码,并且尽可能地和其他的代码模块以及运行时的…

Java: IO流

1.定义 IO流:存储和读取数据的解决方案 用于读写文件中的数据(可以读写文件,或网络中的数据...) 2.IO流的分类 1.按着流的方向 1.输入流:读取 2.输出流:写出 2.按照操作文件类型 1.字节流:所有类型文件 体系&…

Redis:缓存击穿、缓存穿透与缓存雪崩的区别、解决方案

0、前言 近期学习redis相关原理,记录一下开发过程中Redis的一些常见问题及应对方法。 1、缓存穿透 一句话总结:先查redis发现没数据,再去数据库查发现还是没数据。 这种情况下缓存永远不会生效,数据库将承担巨大压力。 我们知道&…

前端食堂技术周刊第 84 期:第 96 届 TC39 会议、Deno 五周年、JavaScript 安全最佳实践、2023 Node.js 性能现状

By Midjournery 美味值:🌟🌟🌟🌟🌟 口味:葡萄冰萃美式 食堂技术周刊仓库地址:https://github.com/Geekhyt/weekly 本期摘要 第 96 届 TC39 会议Deno 五周年JavaScript 安全最佳…

FreeRTOS:信号量

目录 一、信号量是什么二、二值信号量2.1二值信号量简介2.2创建二值信号量2.2.1函数 vSemaphoreCreateBinary()2.2.2函数xSemaphoreCreateBinary()2.2.3 函数 xSemephroeCreateBinaryStatic()2.2.4二值信号量创建过程分析 2.3释放信号量2.3.1函数 xSemaphoreGive ()2.3.2函数 x…

【MySQL学习6:多行输入函数——聚合函数及SQL书写和执行规则】

之前做的笔记都在有道云,之后会一点点将以前的笔记分享出来~ (配图在笔记中查看) MySQL学习6:多行输入函数——聚合函数及SQL书写和执行规则 SQL书写顺序:SQL99执行顺序:一、常见的聚合函数1. 常见的聚合函…

算法当中的时间、空间复杂度?

1.究竟什么是时间复杂度 时间复杂度是一个函数,它定性描述该算法的运行时间 时间复杂度就是用来方便开发者估算出程序运行的答题时间。 通常会估算算法的操作单元数量来代表程序消耗的时间,这里默认CPU的每个单元运行消耗的时间都是相同的。 假设算法的…

微服务架构之服务监控与追踪

与单体应用相比,在微服务架构下,一次用户调用会因为服务化拆分后,变成多个不同服务之间的相互调用,每个服务可能是由不同的团队开发,使用了不同的编程语言,还有可能部署在不同的机器上,分布在不…

【MySQL】MySQL间隙锁--幻读解决原理

文章目录 一、间隙锁概念二、测试间隙锁范围加锁三、测试等值间隙锁 一、间隙锁概念 当我们用范围条件而不是相等条件检索数据, 并请求共享或排他锁时,InnoDB 会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录…

八、视图集ModelViewSet(重点)

上一章: 七、Django DRF框架GenericAPIView--搜索&排序&分页&返回值_做测试的喵酱的博客-CSDN博客 下一章: 九、DRF生成API文档_做测试的喵酱的博客-CSDN博客 一、视图集ModelViewSet与ReadOnlyViesSet ModelViewSet视图集 与 ReadOnly…

第13届蓝桥杯Scratch国赛真题集锦

编程题 第 1 题 问答题 LED屏幕 题目说明 编程实现 LED屏幕 具体要求: 1).点击绿旗,在舞台中心区域出现由10 x 10方格组成的LED屏幕; 2).按下空格键,LED屏幕最外环方格全部点亮 (方格变为黄色) 3).LED屏幕每秒向内点亮一层,其它LED灯熄灭; 4).直到LED灯在最中心点亮2秒…