文件输入与输出io

举报
游离 发表于 2022/11/30 18:58:32 2022/11/30
【摘要】 文件@[toc]狭义的文件: 存储在硬盘上的数据 , “以文件为单位”, 进行组织常见的文件类型: 文本文件,图片 视频文件 音频文件 可执行程序文件夹也叫做"目录"它也还是一种特殊的文件 硬盘与内存的区别硬盘的存储空间比较大,内存的存储空间比较小硬盘的访问速度慢,内存的访问速度快硬盘的成本比较低 , 内存的成本比较高硬盘的数据断电不会消失, 内存的数据断电后会消失(硬盘的持久性存储)广...

文件

@[toc]
狭义的文件: 存储在硬盘上的数据 , “以文件为单位”, 进行组织

常见的文件类型: 文本文件,图片 视频文件 音频文件 可执行程序

文件夹也叫做"目录"它也还是一种特殊的文件

硬盘与内存的区别

  1. 硬盘的存储空间比较大,内存的存储空间比较小
  2. 硬盘的访问速度慢,内存的访问速度快
  3. 硬盘的成本比较低 , 内存的成本比较高
  4. 硬盘的数据断电不会消失, 内存的数据断电后会消失(硬盘的持久性存储)

广义的文件

操作系统是负责管理软硬件的系统, Linux往往会把资源都抽象成"文件"

这就是"一切皆文件"

硬件也可以看做是文件

比如,一个网卡,可以把这个网卡抽象成一个文件, 创建出一个特殊的文件, 表示网卡

从网卡中接收数据就是读这个文件.

向网卡中发送数据就是写这个文件

目录结构是一个N叉树

绝对路径与相对路径

绝对路径是一个很具体的独一无二的路径

举一个例子:

image-20220930100114634

C:\Program Files (x86)\MySQL\MySQL Installer for Windows 这就是一个绝对路径

其中D: 是盘符 , \ 分割是部分都是目录

相对路径

相对路径首先要有一个"基准路径"也叫做"工作路径"

相对路径是以基准路径为起点,按照指示, 逐步找到目标的路径表示方式

基准路径不一样, 相对路径就不会完全相同

如果基准路径是C: .\Program Files (x86)\MySQL\MySQL Installer for Windows就是相对路径

如果基准路径是C:\Program Files (x86), 那么 .\MySQL\MySQL Installer for Windows就是相对路径

谈到相对路径, 一定要明确基准路径是什么

文件操作

文件操作是属于操作系统层面,提供的一些API, 不同的操作系统中的文件API是不一样的

Java是一个能跨平台的语言,为了统一代码, 在JVM中 把不同系统的操作文件的API都进行了封装, 这样子Java就能使用Java中的库来操作文件了

在Java中操作文件的类是File类

File类是在java.io这个包里面的

所谓的io就是input和output的缩写

输入输出的对象是CPU或者内存

File类的构造方法

image-20220930101941080

主要是第二个方法,就是直接传入一个路径字符串,这个路径可以是绝对路径也可以是相对 路径

File类中的方法

接下来举几个File类中方法的例子

package io;

import java.io.File;
import java.io.IOException;

public class Demo1 {
    public static void main(String[] args) throws IOException {
         File f = new File("D:/test.txt");//传入的是绝对路径
        System.out.println(f.getParent());//获取盘符
        System.out.println(f.getName());//获取文件名
        System.out.println(f.getPath());//获取传入的文件路径
        System.out.println(f.getAbsolutePath());//获取文件绝对路径
        System.out.println(f.getCanonicalFile());//获取文件的相对路径
        
    }
}

image-20220930103657167

当传的是绝对路径的时候,后面三个路径好像没有什么区别

当传入的是相对路径就会不一样了

package io;

import java.io.File;
import java.io.IOException;

public class Demo1 {
    public static void main(String[] args) throws IOException {
         File f = new File("./test.txt");//传入的是相对路径
        System.out.println(f.getParent());//获取盘符
        System.out.println(f.getName());//获取文件名
        System.out.println(f.getPath());//获取文件路径(传入的路径)
        System.out.println(f.getAbsolutePath());//获取文件绝对路径
        System.out.println(f.getCanonicalFile());//获取文件的相对路径

    }
}

image-20220930103908546

使用IDEA运行这段程序,基准目录是项目所在的路径

注意: File里传入的文件路径,文件不一定是真实存在的

所以要创建出文件

package io;

import java.io.File;
import java.io.IOException;

public class Demo2 {
    public static void main(String[] args) throws IOException {
        File f = new File("./test.txt");
        //判断文件是否存在
        System.out.println(f.exists());
        //判断文件是否是普通文件
        System.out.println(f.isFile());
        //判断文件是否是目录
        System.out.println(f.isDirectory());

        f.createNewFile();//将文件创建出来
        System.out.println(f.exists());
        System.out.println(f.isFile());
        System.out.println(f.isDirectory());
    }

}

image-20220930104837712

删除文件

image-20220930105231185

创建目录(单层 / 多层)

image-20220930105627498

image-20220930105837887

打印同级目录

import java.io.File;
import java.util.Arrays;

public class Demo6 {
    public static void main(String[] args) {
        File f = new File("./test.dir");
        String[]  res = f .list();
        System.out.println(Arrays.toString(res));
    }
}

image-20220930111029612

上述的操作都是在操作"文件系统",而不是真正对一个文件进行操作

针对文件的内容进行操作才是关键, 也就是读文件和写文件

文件内容的读写

Java中 将读写文件的操作比喻成"流", stream

水的特点是,流动起来,绵延不断

对应到文件的读写操作上, 要是想要写100个字节到文件中, 可以一次写1个字节,分100次写,也可以一次写10个字节,分10次写,还可以一次写20个字节,分5次写,所以这是很灵活的

Java标准库中,就在"流"的概念上,提供了一组类,来完成读写文件的操作

流的分类

image-20220930113423339

字节流

读文件

InputStream有三个构造方法

image-20220930163741455

一次读一个字节,读出的结果是read的返回值

image-20220930163747658

image-20220930163755355

这两个方法, 会把读的内容放到字节数组b中

注意: 这里传入的数组b是存放输出结果的内容,此时 b叫做输出型参数,这在java中不是很常见

package io;

import java.io.*;

public class Demo7 {
    public static void main(String[] args) throws IOException {
        //打开文件,要想进行文件的读写,首先要打开文件
        //InputStream是抽象类,不能被直接实例化
        InputStream inputStream = new FileInputStream("./test.dir./heihei.txt");
        while(true){
            //read()读取结束的时候,会返回一个-1
            int ret = inputStream.read();//一个一个读
            if(ret == -1){
                break;
            }
            System.out.println(ret);
        }
        inputStream.close();//关闭文件,一定要记得关闭
    }
}

heihei.txt 的内容是Are you ok, 所以最后程序输出的结果是ASCII码:(字节流)

image-20221001132119930

写文件

image-20221001132641892

write有三个构造方法,write(int b)是写入一个字节

write(byte[] b)是把字节数组中的所有内容写到文件中

write(byte[] b, int off ,int len) 是把b字节数组从线标off位置开始写,写进去len个字节

package io;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

public class Demo8 {
    //字节流  写文件
    public static void main(String[] args) throws IOException {
        //写的时候,只要打开文件,原来的内容 就没有了
        OutputStream outputStream = new FileOutputStream("./test.dir/heihei.txt");
        outputStream.write(97);
        outputStream.write(99);
        outputStream.write(100);
        outputStream.close();//关闭文件
    }
}

最后heihei.txt 的内容是 acd

字符流

读文件

package io;

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

public class Demo9 {
    public static void main(String[] args) throws IOException {
        Reader reader = new FileReader("./test.dir/heihei.txt");
        while (true){
            //read()返回的是int类型,但是Reader是字符流,所以要强转
            int ret = reader.read();
            if(ret == -1){
                break;
            }
            char ch = (char) ret;
            System.out.println(ch);
        }
        reader.close();//关闭文件
    }
}

写文件

image-20221001135050673

写文件的时候可以从传入String字符串

package io;


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

public class Demo10 {
    public static void main(String[] args) throws IOException {
        Writer writer = new FileWriter("./test.dir/heihei.txt");
        writer.write("hello world");    
        writer.close();
    }
}

其实读文件还有别的方法–使用scanner

package io;

import javax.swing.plaf.SeparatorUI;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;

public class Demo11 {
    public static void main(String[] args) throws IOException {
        //使用scanner来读取文件
        InputStream inputStream = new FileInputStream("./test.dir/heihei.txt");
        Scanner scanner = new Scanner(inputStream);//把文件传给scanner
        while (scanner.hasNext()){
            System.out.println(scanner.next());
        }
        inputStream.close();
    }
}

打开文件之后,一定要记得关闭文件,否则会造成很严重的问题

具体原理: 每个进程对应着一个PCB(双向链表),也可能十多个,PCB里面有一个字段–文件描述符表, 同一个进程中的多个PCB共用一份 文件描述符表 , 每次打开一个文件, 就会在表中创建一个项(相当于数组,最大长度是有上限的),关闭文件就会释放掉这个项, 如果持续打开文件,但是从来不关闭文件,就会导致表项被耗尽,之后就不能再打开别的文件了,这就是文件资源泄漏,十分严重,因为这个问题不是一下子就暴露的,而是需要时间才会出现bug

image-20221001143012752

在这个代码中,不一定能保证 一定能执行到关闭文件,万一前面抛出异常,关闭文件就执行不到了

解决方法

使用try finally, 但是这样写比较丑

public static void main(String[] args) throws IOException {
        InputStream inputStream = null;
        try{
            inputStream = new FileInputStream("./test.dir/heihei.txt");
            Scanner scanner = new Scanner(inputStream);
            while (scanner.hasNext()) {
                System.out.println(scanner.next());
            }
        }finally{
            inputStream.close();
        }
    }

使用try with resources , 在try结束之后,就会自动调用对象的close(),而且还能传入多个对象,只要 用分号连接就行

 public static void main(String[] args) throws FileNotFoundException {
        try(InputStream inputStream = new FileInputStream("./test.dir/heihei.txt")){
            Scanner scanner = new Scanner(inputStream);
            while (scanner.hasNext()){
                System.out.println(scanner.next());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

以上就是所有的关于io的知识讲解,其实不多也不难,知识需要多多练习

接下来就要进行一些练习

练习一

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

思路:

1.用户输入扫描的目录和删除的关键字

2.遍历所有的目录

3.找到包含关键字的文件,选择是否删除

遍历的时候,要先判断当前目录是不是空的,确定非空之后,再进行遍历

使用 listFiles 可以把当前目录中的文件和子目录都列举出来,但是没办法列举出子目录的子目录, 所以解决办法就是遍历listFiles的结果, 针对每一个元素看看它是普通文件还是子目录,要是是普通文件就选择是否删除,要是是子目录,就进行递归,继续扫描子目录的子目录

image-20221001153724262

也就是说listFiles只能看一级的目录,可以使用递归来扫描下一级的内容

目录本质是就是"树"

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

public class Test1 {
    //1. 输入要查找的目录和要删除的关键字
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入你要查找的目录");
        File rootDir = new File(scanner.next());//rootDir是要扫描的目录
        if(!rootDir.isDirectory()){
            System.out.println("输入的不是目录");
            return;
        }
        //输入的是目录
        System.out.println("请输入你要删除的关键字: ");
        String toDelete = scanner.next();
        scanDir(rootDir,toDelete);
    }

    //2. 遍历所有的目录
    private static void scanDir(File rootDir, String toDelete) {
        System.out.println("当前访问"+ rootDir.getAbsolutePath());
        File[] files = rootDir.listFiles();
        if(files == null){
            //目录是空的
            return;
        }//判断空目录一定不要省去,因为这是递归的结束条件
        //目录不是空的,就要开始遍历
        for(File f : files){
            if(f.isFile()){
                checkDelete(f,toDelete);
            }else{
                //在这个非空目录中不是普通文件,只能是又一级目录
                scanDir(f,toDelete);//递归查看下一级目录
            }
        }
    }

    private static void checkDelete(File f, String toDelete) {
        if(f.getName().contains(toDelete)){//查看文件名中是否包含关键字
            System.out.println(toDelete + "已经在" + f.getAbsolutePath()+ "中找到,请问是否删除?(Y/N)");
            Scanner scanner = new Scanner(System.in);
            String ret = scanner.next();
            if(ret.equals("y") || ret.equals("Y")){
                f.delete();
            }
        }
    }
}

image-20221001154018768

练习二

实现文件的复制

实现复制比较简单,就是打开一个文件读,一个文件写

package io;

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

public class Test2 {
    public static void main(String[] args) {
        //1.用户输入要复制的文件名(源文件)和复制后的文件名(目标文件)
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入你要复制的文件名路径");
        File srcFile = new File(scanner.next());
        System.out.println("请输入复制后的文件名路径");
        File destFile = new File(scanner.next());
        //要确保源文件是一个普通文件,例如d:/tmp/1.txt
        if(!srcFile.isFile()){
            System.out.println("输入的源文件有误");
        }
        //要确保文件的上一级是一个目录,这样子才好创建,例如d:/tmp/12.txt
        if(!destFile.getParentFile().isDirectory()){
            System.out.println("输入的目标文件有误");
        }
        //打开源文件和目标文件
        try(InputStream inputStream = new FileInputStream(srcFile);
            OutputStream outputStream = new FileOutputStream(destFile)){
            //源文件一个一个字节地读,目标文件一个一个字节地写
            while (true){
              int ret =  inputStream.read();
              if(ret == -1){
                  break;
              }
              outputStream.write(ret);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

image-20221001160402058

练习三

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

这个算是练习一 的进阶版,主要是增加了在文件内容中查找的程序

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

public class Test3 {
    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入你要查找的目录");
        File file = new File(scanner.next());
        if(!file.isDirectory()){
            System.out.println("你输入的不是目录");
            return;
        }
        System.out.println("请输入你要查找的关键字");
        String toFind = scanner.next();
        scanDir(file,toFind);//遍历目录
    }
    private static void scanDir(File rootDir, String toFind) throws IOException {
        System.out.println("当前访问"+rootDir.getAbsolutePath());
        File[] files = rootDir.listFiles();
        if(files == null){
            return;
        }
        for(File f : files){
            if(f.isFile()){
                checkFile(f,toFind);
            }else{
                //说明还有下一级目录
                scanDir(f,toFind);
            }
        }
    }

    private static  void checkFile(File file, String toFind) throws IOException {
        //1. 检查文件名
        if(file.getName().contains(toFind)){
            System.out.println(file.getCanonicalFile()+"在文件名中"+"包含"+toFind);
        }
        //2. 检查文件内容
        try(InputStream inputStream = new FileInputStream(file)){  //打开文件
            StringBuffer stringBuffer = new StringBuffer();
            Scanner scanner = new Scanner(inputStream);//读取文件
            while (scanner.hasNextLine()) {
                stringBuffer.append(scanner.nextLine() );//添加所有的内容到stringBuffer
                }
                if(stringBuffer.indexOf(toFind)>-1){  //在stringBuffer里面找,要是找不到就返回-1
                    System.out.println(file.getCanonicalFile()+"在文件内容里中包含"+toFind);
            }
        }
    }
}

这里的练习三代码的运行效率是很低的

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。