文件IO操作

目录

文件

文件的类型

文件操作

 Java标准库的流对象

文件

文件,File这个概念,在计算机中也是一词多义。

狭义的文件:指的是你的硬盘上的文件和目录。

广义的文件:泛指计算机中的很多的软硬件资源。操作系统中,把很多的硬件设备和软件资源抽象成了文件,按照文件的方式来统一管理。在网络编程中,操作系统也是把网卡当成了一个文件。

本章节只讨论狭义上的文件操作。

每个文件在硬盘上都有一个具体的路径,比如:

这是一张图片,在硬盘上的路径叫做d:/cat.jpg(在windows上也可以写作为d:\cat.jpg)表示一个文件的具体位置路径,就可以使用/来分割不同的目录级别,例如:d:/tmp/111/aaa   最前面的d,代表盘符,c、d、e这样的盘符是通过硬盘分区来的,每个盘符可以是一个单独的硬盘,也可以是若干个盘符对应一个硬盘。路径有两种表示风格:

1.绝对路径:以c,d盘符开头的路径。

2.相对路径:以当前所在的目录为基准,以 . 或者 .. 开头,找到指定的路径。

当前所在的目录,称为工作目录。每个程序运行的时候,都有一个工作目录。在控制台里通过命令操作的时候,是特别明显的;后来进化到图形界面了,工作目录就不那么直观了。打开cmd控制台后,显示出的就是工作目录:

工作目录是可以更改的,输入D: 之后,工作目录就被更改了:

 通过控制台可以打开一些应用,比如要打开计算器:

此时计算器就被打开了。此时计算器这个应用的工作目录就是D: 。当然,直接在电脑中打开计算器,是感知不到计算器的工作目录的。下面再举个例子:

如图,假定当前的工作目录是d:/tmp,那么当定位到111这个目录,就可以表示成./111(./就表示当前的目录)。同样,定位到222这个目录就表示为./222。如果工作目录不同,那么定位到同一个文件,相对路径写法就是不同的。比如同样是定位到111这里:

如果工作目录是d:/          那么相对路径就是./tmp/111

如果工作目录是d:/tmp    那么相对路径就是./111 

如果工作目录是d:/tmp/222 那么相对路径就是../111(..表示当前目录的上级目录)

如果工作目录是d:/tmp/222/bbb,那么相对路径就是../../111

文件的类型

文件的类型有很多,比如word,exe,图片,视频,音频,源代码,动态库等等。这些不同的文件,整体可以归纳到两类中。

1.文本文件(存的是文本,字符串)

2.二进制文件(存的是二进制数据,不一定是字符串了)

随便给你一个文件,怎么区分是文本还是二进制呢?直接使用记事本打开,如果乱码了,说明就是二进制,如果没乱码,说明就是文本。

Java对于文件的操作

1.针对文件系统操作(文件的创建,删除,重命名...)

2.针对文件内容操作(文件的读和写)

Java标准库中,提供了File这个类:

在new File对象的时候,构造方法参数中,可以指定一个路径,此时File对象就代表这个路径对应的文件了。下面是File提供的一些方法:

文件操作

 接下来,在java中进行文件操作。首先创建一个 File对象:

 此时是不要求在d:/这里这里真的有个test.txt。

 使用方法分别得到路径的名字,父目录名字,文件路径,文件绝对路径,修饰过的绝对路径,输出结果如下:

略作修改,将文件路径改为相对路径:

 此时得到的结果如图:

 可以发现,此时这个路径就是在创建的Java程序下。但是,此时的test.txt文件还没有被真正创建出来。怎么真正创建出来呢?如图:

 

 程序运行起来之后,就会发现文件被创建出来了:

 通过file的方法显示:

 可以发现,创建出的的确是一个文件且真实存在,且并非目录。如果想删除此文件,使用delete方法即可,如图:

 可以发现文件的确被删除了。deleteOnExit代表程序退出的时候,自动删除。当程序中需要使用到一些临时文件的时候,需要用到。临时文件相当于保存了当前实时编辑的内容,防止编辑了很多东西还没来得及保存后就断电了。

下面再演示一下目录的创建:

 运行程序之后,就会在文件栏看到显示:

 Java标准库的流对象

从类型上,流对象分为两个大类:

1.字节流

字节流是操作二进制数据的,例如InputStream、FileInputStream、OutputStream、FileOutputStream

2.字符流

字符流是操作文本数据的,例如Reader、FileReader、Writer、FileWriter。

这些类的使用方式是非常固定的,核心就是四个操作:

1)打开文件(构造对象)

2)关闭文件(close)

3)读文件(read)=>针对InputStream/Reader

4)写文件(write)=>针对OutputStream/Writer

InputStream/OutputStream/Reader/Writer都是抽象类,是不能直接new对象的。下面使用一下字节流来读取一下文件:

 inputStream提供了三种read方法,

1.read无参数版本:一次只读一个字节

2.read一个参数版本:把读到的内容填充到参数的这个字节数组中,返回值是实际读取的字节数。

3.read三个参数版本:和2类似,只不过是往数组的一部分区间里尽可能的填充。

read每次是读取一个字节,按理说,返回一个byte就行了,但是read的代码实现中,返回值类型是int:

为什么呢? 除了表示byte里的0到255这样的情况之外,因为要表示一个特殊的情况:-1。这个情况表示读取的文件结束了(读到文件末尾了)。现在在指定的路径中,记事本的内容为hello字符串,那么输出结果为:

 读到的这些数字就是hello的ascii码。将记事本的内容改成中文“你好”之后,得到结果如下:

 这里的几个数字就表示“你好”两个字的编码方式。可以用16进制来打印,对照码表观察:

这里是utf8的码表,如果文件是gbk编码的,就需要和gbk码表去对比。

那么,如何一次读若干个字节呢?

 得到的结果和一次读一个字节得到的结果是相同的。此处用了read的第二个版本,需要调用者提前准备好一个数组,传参操作相当于是把刚才准备好的数组交给read方法,让read方法内部针对这个数组进行填写。此处的参数相当于”输出型参数“。Java中一般习惯性的做法,是把输入的信息作为参数,输出的信息作为返回值,但是也有少数情况,是使用参数来返回内容的,这一点在C++是很常见的。继续读取一个比较大的文件,观察len的长度:

由于这次的文件大小比较大,因此就需要循环多次来进行read操作。由于数组长度是1024,前面每次read到的长度都是1024。第二轮的数据会覆盖掉第一轮的数据。当读到最后,发现数据不足1024了,实际能读到多少字节就读到多少字节。这里剩下512字节(不同的文件剩下的不一样),把这512字节读完之后文件读到了末尾。再进行下一轮循环,read无任何内容可读,返回-1结束循环。

上面使用了InputStream来读文件,还可以使用OutputStream来写文件,如图:

 在电脑中找到此文件打开:

 对于OutputStream来说,默认情况下,打开一个文件,会先清空文件原有的内容。(这样的话,之前的”你好”就没了。如果不想清空,流对象还提供了一个“追加写“对象,通过这个就可以实现不清空文件,把新内容追加写到后面。

读写完文件后,最后还需要有一个close操作,含义是关闭文件。

为什么需要及时关闭呢?因为在表示进程的PCB中有一个重要的属性:文件描述符表。文件描述符表相当于一个数组,每个元素都是内核里的一个file_struct对象,出现这个对象就代表打开了一个文件。每次打开文件操作,就会在文件描述符表中,申请一个位置,把这个信息放进去。每次关闭文件,也就会把这个文件描述符表对应的表项给释放掉。那么问题来了,如果这个close操作没写,会怎么样呢?如果没有close,那么对应的表项没有及时释放,意味着文件描述符表很可能很快就被占满了。如果占满了之后再次打开文件,就会打开失败。所以close操作一般是要执行的。所以,为了避免输入输出结束后忘记关闭对象,最推荐的写法如图:

这个写法虽然没有显式的写close,实际上是会执行的。只要try语句块执行完毕,就可以自动执行到close。这种语法,在Java中被称为try with resources。这里不是随便拿个对象放到try()里就能自动释放的,得满足一定的要求:

必须是实现了这个Closeable接口的类才可以放到try的()被自动关闭。这个接口提供的方法就是close方法。

字符流

字符流和字节流使用方法差不多,提供Writer,Reader两种对象,用法和上面的字节流类似:

读文件操作:

 写文件操作:

 对于close操作,是会触发刷新缓冲区的操作的(刷新操作,就是把缓冲区里的内容写到硬盘里)。除了close之外,还可以通过flush方法起到刷新缓冲区的方法。

下面,写一个程序,来扫描指定目录,并找到名称中包含知指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件:

public class Teat3 {
    public static void main(String[] args) {
        //让用户输入一个指定的目录
        Scanner scanner=new Scanner(System.in);
        System.out.println("请输入要搜索的路径:");
        String basePath=scanner.next();
        //针对用户输入进行简单的判定
        File file=new File(basePath);
        if (!file.isDirectory()){
            System.out.println("输入有误");
            return;
        }
        //再让用户输入一个要删除的文件名
        System.out.println("请输入要删除的文件名:");
        //注意此处要用next,而不要使用nextLine
        String nameToDelete=scanner.next();

        //针对指定的路径进行扫描,递归操作
        //先从根目录触发
        //先判定一下,当前的这个目录里,看看是否包含要删除的文件,如果是,就删除;否则就跳过下一个
        scanDir();
    }
    public static void scanDir(File root,String nameToDelete){
        //1.先列出当前路径下包含的内容
        File[] files=root.listFiles();
        if (files==null){
            //当前目录是一个空目录
            //结束继续递归
            return;
        }
        //2.遍历当前的列出结果
        for (File f:files){
            if (f.isDirectory()){
                //如果是目录,就进一步递归
                scanDir(f,nameToDelete);
            }else {
                //如果不是目录,判断
                if (f.getName().contains(nameToDelete)) {
                    f.delete();
                }
            }
        }
    }
}

 其中,

这里是使用了File对象表示列出的结果,结果有多个 ,所以使用数组表示。这其中涉及到递归遍历目录的操作需要了解和掌握。

进行普通文件的复制:

 以上是IO操作以及文件的内容,如有错误,欢迎指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晚报大街-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值