JavaEE初阶,文件IO(2)

文件内容的操作(读写文件)

Java中,针对文件内容的操作,主要是通过一组"流对象"来实现的。

场景: 计算机中的" 流 "

可以理解,从文件中读取100byte的数据,有无数种读数据的方法(如1次把100byte都读完或者分2次一次读50byte等等)
因此,计算机针对读写文件,使用流(Stream),流是操作系统层面的术语,和语言无关。而Java中提供了一组类来表示"流"。
针对上述Java提供的多个流,分成两大个类别。

  • 字节流
    读写文件,以字节为单位;针对二进制文件使用。
  • InputStream 输入
  • OutputStream 输出
  • 字符流
    读写文件,以字符为单位;针对文本文件使用。
  • Reader 输入
  • Write 输出

Java中其他的流对象都直接/间接继承自这几个类。

  • 从硬盘===>CPU ,叫输入
  • 从CPU===>硬盘 , 叫输出
    拓展: 一个字符可能对应多少个字节?

这是不确定的,取决于编码方式的。如GBK编码1个汉字占2个字节,utf-8编码1个汉字占3个字节,unicode编码1个汉字占4个字节。

字节流:InputStream

InputStream流对象体系,不仅仅给文件提供操作,网络编程也离不开流。

文件资源不等同于内存资源,虽然GC能够自动管理内存,但是不能自动管理文件,需要手动释放,若不释放就会导致 "文件资源泄露"

简单理解,每次程序打开一个文件,就会在文件描述符表中申请一个表项,如果光打开,不关闭,就会使这里的文件描述符表的表项耗尽,后续再次打开,就会打开失败!!!

因此,为防止各种异常/return带来不能及时关闭文件资源于是采用try finally来解决

java 复制代码
public class Demon7 {
    public static void main(String[] args) throws IOException {
        InputStream inputStream=null;
        try{
            //进行创建对象操作,一旦成功,就相当于"打开文件"
            inputStream=new FileInputStream("./test.txt");
        }finally {
            inputStream.close();//关闭资源
        }
    }
}

由于上述采用try finally 不够简洁,于是再次改进采用Java1.5引入的语法try with resources

try()里面可以写多个对象

读取文件里面的内容

java 复制代码
public class Demon7 {
    public static void main(String[] args) throws IOException {
        try (InputStream inputStream=new FileInputStream("./test.txt")){
            //读文件操作
            while (true){
                int data=inputStream.read();
                if (data==-1){
                    //返回-1 文件读完
                    break;
                }
                System.out.println(data);
            }
        }
    }
}

此方法是按照字节读取。

通过查ASCII表就能提取到test.txt里面写的内容。

一次读取若干字节通过数组进行传参并存储

java 复制代码
public class Demo8 {
    public static void main(String[] args) throws IOException {
        try(InputStream inputStream=new FileInputStream("./test.txt")){
            while (true){
                //一次读多个字节,数组的长度,自行定义
                byte[] data=new byte[3];
                //读操作,就会尽可能的把字节数组给填满
                //若填不满,则能填几个算几个
                //n代表实际读了几个字节
                int n=inputStream.read(data);
                System.out.println("n= "+n);
                if (n==-1){
                    //文件读完
                    break;
                }
                for (int i = 0; i < n; i++) {
                    System.out.printf("0x%x\n",data[i]);//0x%x采用16进制进行输出
                }
                System.out.println("==========");
            }
        }
    }
}

字节流:OutputStream

java 复制代码
public class Demo12 {
    public static void main(String[] args) {
        try(OutputStream outputStream=new FileOutputStream("./output.txt")) {
            //向output.txt文件写97在ascii表中就是a,若指定文件不存在则进行创建
            //一个字节一个字节读
//            outputStream.write(97);
//            outputStream.write(98);
//            outputStream.write(99);

            //读若干字节
            byte[] data={99,98,97};
            outputStream.write(data);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

对于OutputStream,默认情况下会尝试创建不存在的写入文件,而且每次写入会清除上次写入的文件内容。

解决每次写入会清除上次写入在文件里的内容,因此采用追加(append)的方式

字符流 :Reader

java 复制代码
public class Demo13 {
    public static void main(String[] args) {
        try(Reader reader=new FileReader("./output.txt")) {
            while (true){
            //一次读一个字符
                int c=reader.read();
                if (c==-1){
                    return;
                }
                System.out.println((char) c);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
java 复制代码
public class Demo14 {
    public static void main(String[] args) {
        try(Reader reader=new FileReader("./output.txt")) {
            while (true){
                //基于数组,读字符数组
                char[] buf=new char[1024];
                int n=reader.read(buf);
                if (n==-1){
                    break;
                }
                for (int i = 0; i < n; i++) {
                    System.out.println(buf[i]);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

字符流 :Writer

java 复制代码
public class Demo15 {
    public static void main(String[] args) {
        try(Writer writer=new FileWriter("./output.txt",true)) {
            writer.write("hello world!");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

知识拓展: 通常就是一段内存空间提高程序效率。由于直接读写硬盘,比较低效;因此进行IO的时候,就希望能够使用缓冲区,把要写的数据,先放到缓冲区里临时存储,在一起写;读也不是一次一次的读,一次读一批数据到缓冲区,在慢慢进行解析。
当前IO流对象,read和write就属于直接读写文件
要想提高效率

  • 写代码的时候,手动创建缓冲区(byte[ ]),手动减少read write的次数。
  • 使用标准库提供的"缓冲区流"BufferedStream,把InputStream之类的对象套上一层。
代码案例练习

示例1
扫描指定⽬录,并找到名称中包含指定字符的所有普通文件(不包含⽬录),并且后续询问用户是否要删除该文件。

java 复制代码
public class Demo9 {
    public static void main(String[] args) {
        Scanner scanner=new Scanner(System.in);
        System.out.println("请输入要搜索的目录:");
        String rootDir=scanner.next();
        File rootFile=new File(rootDir);
        if(!rootFile.isDirectory()){
            System.out.println("您输入的不是目录!");
            return;
        }
        System.out.println("请输入您要删除的关键字:");
        String keyword=scanner.next();
        scanDir(rootFile,keyword);
    }

    private static void scanDir(File rootFile, String keyword) {
        //1.列出当前目录中所包含的内容
        File[] files=rootFile.listFiles();
        if(files==null){
            System.out.println("当前目录为空!");
            return;
        }
        //2.遍历当前目录的内容
        for (File file:files) {
            //System.out.println("遍历目录&文件:"+file.getAbsolutePath());
            //3.判断当前文件是目录还是普通文件
            if (file.isFile()){
                //4.如果是普通文件,则判断文件名是否包含关键字
                dealFile(file,keyword);
            }else {
                //5.如果是目录,则递归调用本方法
                scanDir(file,keyword);
            }
        }
    }

    private static void dealFile(File file, String keyword) {
        if (file.getName().contains(keyword)){
            System.out.println("发现文件"+file.getAbsolutePath()+",包含关键字!是否删除?(y/n):");
            Scanner scanner=new Scanner(System.in);
            String input=scanner.next();
            if (input.equalsIgnoreCase("y")){
                file.delete();
                System.out.println("文件已删除!");
            }
        }
    }
}

示例2
进⾏普通⽂件的复制。

java 复制代码
public class Demo11 {
    public static void main(String[] args) {
        Scanner scanner=new Scanner(System.in);
        System.out.println("请输入源文件路径:");
        String scrPath=scanner.next();
        File scrFile=new File(scrPath);
        if (!scrFile.isFile()){
            System.out.println("源文件不存在或不是文件");
            return;
        }
        System.out.println("请输入目标文件路径:");
        String destPath=scanner.next();
        File destFile=new File(destPath);
        //要求destFile不一定存在,但是destFile所在的目录必须存在
        if (!destFile.getParentFile().isDirectory()){
            System.out.println("目标文件所在目录不存在");
            return;
        }

        //进行文件复制操作
        //此处不应该使用追加写,需要确保目标文件和源文件一模一样
        try(InputStream inputStream=new FileInputStream(scrFile);
        OutputStream outputStream=new FileOutputStream(destFile)) {
            while (true){
                byte[] buf=new byte[1024];
                int n=inputStream.read(buf);
                if (n==-1){
                    break;
                }
                //此处的write不应该写整个buf数组的
                //buf数组不一定被填满,要按照实际的n这个长度来写入
                outputStream.write(buf,0,n);
            }


        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

示例3
扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)

java 复制代码
public class Demo16 {
    public static void main(String[] args) {
        Scanner scanner=new Scanner(System.in);
        System.out.println("请输入要搜索的路径:");
        String rootPath=scanner.next();
        File rootFile=new File(rootPath);
        if(!rootFile.isDirectory()){
            System.out.println("输入的路径不是目录!");
            return;
        }
        System.out.println("请输入要搜索的关键字:");
        String keyword=scanner.next();

        scanDir(rootFile,keyword);
    }

    private static void scanDir(File rootFile, String keyword) {
        //列出目录下的所有内容
        File[] files=rootFile.listFiles();
        if (files==null){
            System.out.println("当前目录为空!");
            return;
        }
        for (File file:files) {
            if (file.isFile()){
                //是普通文件
                dealFile(file,keyword);
            }else {
                //是目录递归调用
                scanDir(file,keyword);
            }
        }
    }

    private static void dealFile(File file, String keyword) {
        if (file.getName().equalsIgnoreCase(keyword)){
            //1.包含关键字打印文件名
            System.out.println("发现文件"+file.getAbsolutePath()+" 包含关键字");
            return;
        }
        //2.判断文件内容是否包含关键字
        StringBuffer stringBuffer=new StringBuffer();
        try(Reader reader=new FileReader(file)) {
            while (true){
                char[] buf=new char[1024];
                int n= reader.read(buf);
                if (n==-1){
                   break;
                }
                stringBuffer.append(buf,0,n);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //判断stringBuffer是否包含关键字
        if (stringBuffer.indexOf(keyword)>=0){
            //包含关键字
            System.out.println("文件内容包含关键字:"+file.getAbsolutePath());
        }
        return;
    }
}

拓展 文件的元数据(Meta Data) 指文件的大小,文件的类型,文件的创建者,文件的最后修改时间等属性。

相关推荐
学习编程的Kitty3 小时前
JavaEE初阶——多线程(4)线程安全
java·开发语言·jvm
sheji34163 小时前
【开题答辩全过程】以 多媒体素材管理系统为例,包含答辩的问题和答案
java·eclipse
成钰3 小时前
设计模式之抽象工厂模式:最复杂的工厂模式变种
java·设计模式·抽象工厂模式
Elieal3 小时前
深入 Maven:从仓库配置到私服架构的进阶实践
java·架构·maven
学到头秃的suhian3 小时前
垃圾收集器
java·jvm
javpy3 小时前
为什么Service层和Mapper层需要实现interface接口
java·springboot
Han.miracle3 小时前
Java线程的学习—多线程(一)
java·开发语言·学习
人间打气筒(Ada)3 小时前
yum安装k8s集群----基于centos7.9
java·容器·kubernetes
是店小二呀3 小时前
Trilium非线性笔记测评:本地知识库+远程协作,构建你的第二大脑!
笔记