文件操作和InputStream,OutputStream的用法

“他越拧巴,我越喜欢!”

文件: 

此处谈到的文件,本身有很多的含义。

狭义上的文件,特指 硬盘上的文件(以及保存文件的目录)。

广义上的文件,计算机上的很多硬件设备,软件资源,在操作系统中都会被视为“文件”。

硬盘和内存对比:

  1. 硬盘的存储空间大,内存小
  2. 硬盘的访问速度慢,内存快 (硬盘访问速度慢,和它硬件的物理结构有关)
  3. 硬盘的成本低,内存高
  4. 硬盘能持久化存储,内存断电后数据会丢失

树型结构组织 和 目录:

随着文件越来越多,对文件的系统管理也被提上了⽇程,如何进行文件的组织呢,⼀种合乎⾃
然的想法出现了,就是按照层级结构进⾏组织 ⸺ 也就是我们数据结构中学习过的树形结构。这样,⼀种专门用来存放管理信息的特殊文件诞⽣了,也就是我们平时所谓⽂件夹(folder)或者⽬录
(directory)的概念。

目录: 用于组织文件的容器,可以包含文件和其他目录(子目录)。

基于上述的结构,就可以找到,某个文件在硬盘上的具体位置。

这一串目录结构,就描述了文件所在的位置。文件中,就约定使用分隔符,分隔目录。

\(反斜杠)      /(斜杠) 绝大部分系统,都是使用 / 作为目录的分隔符的,只有windows是既能够使用 / 也能够使用 \.

路径:

可以认为是文件的一种身份标识,通过标识,区分出唯一的一个文件.

1.绝对路径:

从盘符开始,一直到文件名结束.

D:\JavaEE(primer)\java-ee\Io\bug1.txt

2.相对路径:

说到相对路径,必然有一个"参考系",就是有一个"基准路径"或"工作路径".

如果说基准路径不同的话,对应的相对路径也是不同的.

拿上面的D:\JavaEE(primer)\java-ee\Io\bug1.txt举例:

如果约定以D:\JavaEE(primer)为基准目录  ->  相对路径为: .\java-ee\Io\bug1.txt

如果约定以D:\JavaEE(primer)\java-ee为基准目录 ->  相对路径为: .\Io\bug1.txt

文件的类型:

文本文件:当前文件里存储的所有内容,都是"文本"(合法的字符).

字符的编码方式(字符集):UTF-8 GBK字符编码

二进制文件:对应的,如果文件内容,在字符集对应的表格中,不可查,此时就是 二进制文件.

(简单粗暴的方式,直接使用记事本打开文件,如果打开之后不是乱码,就是文本文件,否则就是二进制文件).

文件的操作:

1.文件系统的操作(创建文件,删除文件,创建目录,重命名文件,判定文件是否存在.......)

Java中提供了 FILE 类,进行文件系统操作,这个对象,会使用"路径"初始化.从而标识一个具体的文件(这个文件可以存在也可以不存在).

构造方法
  • File(String pathname): 根据路径名字符串创建一个新File实例。
  • File(String parent, String child): 根据父路径名和子路径名创建新的File实例。
  • File(File parent, String child): 根据父File对象和子路径名创建新的File实例。
常用方法
  1. 文件和目录的创建、删除

    • boolean createNewFile(): 创建一个新文件,如果文件已经存在则返回false。
    • boolean mkdir(): 创建一个新目录,只有在其父目录存在的情况下才会创建成功。
    • boolean mkdirs(): 创建一个新目录,包括任何必需但不存在的父目录。
    • boolean delete(): 删除文件或目录,只有在目录为空时可以删除。
    • void deleteOnExit():根据File对象,标注我呢间将被删除,删除的动作会到JVM运行结束时才会进行。
  2. 文件和目录的查询

    • boolean exists(): 测试文件或目录是否存在。
    • boolean isDirectory(): 测试此抽象路径名表示的文件是否是一个目录。
    • boolean isFile(): 测试此抽象路径名表示的文件是否是一个标准文件。
    • boolean isHidden(): 测试此抽象路径名表示的文件是否是一个隐藏文件。
    • String getName(): 获取文件或目录的名称。
    • String getPath(): 获取文件或目录的路径。
    • String getAbsolutePath(): 获取文件或目录的绝对路径。
    • String getcanonicalPath(): 返回File对象的修饰过的绝对路径
    • long length(): 获取文件的字节长度。
  3. 文件和目录操作

    • boolean renameTo(File dest): 将该文件或目录重命名为指定的目标File。
    • String[] list(): 获取目录中的文件和子目录名称的字符串数组。
    • File[] listFiles(): 获取目录中的文件和子目录的File数组。

下面我们来看看部分方法的用法与效果:


观察 get 系列的特点和差异
public class Demo1 {
    public static void main(String[] args) throws IOException {
        //绝对路径
        File file =  new File("D:\\JavaEE(primer)\\java-ee\\Io\\bug1.txt");
        System.out.println(file.getParent());
        System.out.println(file.getName());
        System.out.println(file.getPath());
        System.out.println(file.getAbsolutePath());
        System.out.println(file.getCanonicalPath());
    }
}

注意:创建文件,很可能会抛出异常的

(1)硬盘的空间不够了。

(2)没有权限,确保你具有操作的权限,才能进行,对于文件的权限,典型的就是读和写。


普通文件的创建、删除
public class Demo2 {
    public static void main(String[] args) throws IOException {
        File file = new File("./hello.txt");
        boolean ok = file.createNewFile();
        System.out.println(ok);

        System.out.println(file.exists());
        System.out.println(file.isFile());
        System.out.println(file.isDirectory());
        
    }
}

 

public class Demo3 {
    public static void main(String[] args) {
        File file = new File("./hello.txt");
        boolean ok = file.delete();
        System.out.println(ok);
    }
}

下面我们来看一看 void deleteOnExit()与boolean delete()有什么不同:

 


列出file对象下的文件名
public class Demo5 {
    public static void main(String[] args) {
        File file = new File("./src");
        
        System.out.println(Arrays.toString(file.list()));
        System.out.println(Arrays.toString(file.listFiles()));

    }
}

如果直接使用list /listFiles 只能看到当前目录中的内容,如果想看到某个目录中所有的目录和文件,就需要递归来完成了!

public class Demo7 {
    public static void main(String[] args) {
        File file = new File("./");
        scan1(file);
    }

    private static void scan1(File file) {
        //先判断是不是目录
        if(!file.isDirectory()) {
            return ;
        }

        //列出当前目录中包含的内容
        File[] files = file.listFiles();
        //不存在路径/空目录
        if(files == null || files.length == 0) {
            return;
        }

        //打印当前目录
        System.out.println(file.getAbsolutePath());

        for (File f:files) {
            //如果是普通文件就直接打印文件路径
            if(f.isFile()) {
                System.out.println(f.getAbsolutePath());
                //是目录,就继续进行递归
            }else {
                scan1(f);
            }
        }
    }
}


观察目录的创建

 


观察文件名的重命名


 文件内容的读写-数据流:

读文件和写文件,都是操作系统提供了 api,java也进行了封装.

"文件流"/"IO流"

水流的特点:                                                                             IO流的特点:

我要通过水龙头,接100ml的水:                                        我要从文件中读取100字节的数据

1.直接一口气,把100ml水接完                                          1.直接一口气,把100字节读完

2.一次接50ml,分两次接完                                                2.一次读50字节,分两次

3.一次接10ml,分10次接完                                                3.一次读10字节,分10次

...............................................                                           ..........................................................

1.字节流(二进制) 读写的基本单位,就是字节 

InputStream        OutputStream

2.字符流(文本)读写的基本单位,就是字符

Reader                 Writer

上述的这四个类,都是“抽象类”,实际上真正干活的不是这四个。java中实现了提供了很多很多的类,实现了上述的这四个抽象类。

凡是类的名字一 Reader Writer结尾的,就是实现了reader 和 Writer的字符流对象,凡是类的名字以 InputStream 和 OutputStream结尾的,就是实现了 InputStream 和 OutputStream 的字符流对象。

以下是 InputStream 类的一些常用方法:

  1. int read(): 读取一个字节的数据,返回一个整数(0-255),如果达到流的末尾,则返回 -1。

  2. int read(byte[] b): 读取多个字节的数据,将读取的数据存储到字节数组 b 中,返回读取的字节数,如果达到流的末尾,则返回 -1。

  3. int read(byte[] b, int off, int len): 从输入流中读取最多 len 个字节的数据,并将其存储在数组 b 从偏移量 off 开始的位置。返回实际读取的字节数,如果达到流的末尾,则返回 -1。

  4. void close(): 关闭输入流并释放与该流相关联的所有资源。

public class Demo10 {
    public static void main(String[] args) throws IOException {
        //这里("./ret.txt")可以指定绝对路径,也可以是相对路径,还可以是FIle对象
        InputStream inputStream = new FileInputStream("./ret.txt");
        //此处隐含一个操作“打来文件”,file open,针对文件进行读写,务必需要先打开(操作系统,基本要求)
        
        //关闭
        inputStream.close();
    }
}

如果不使用close()关闭,会怎么样呢?

打开文件,其实就是在 该进程的 文件描述符表 中,创建一个新的表项。描述了该进程,都要操作哪些文件,文件描述符表,可以认为是一个数组。数组中的每个元素就是一个 struct file 对象,每个结构体就描述了对应操作的文件的信息,数组的下标,就称为“文件描述符”。

每次打开一个文件,就相当于在一个数组上占用了一个空间,而在系统内核中,文件描述符表数组是固定长度&&不可扩容的。

除非主动调用close,关闭文件,此时才会释放出空间。否则,如果代码里一直打开,没有去进行关闭,就会使这里的资源越来越少,数组的空间被占满之后,后续在进行打开文件就会失败!

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class Demo11 {
    public static void main(String[] args) {
        InputStream inputStream = null;

        try {
            inputStream = new FileInputStream("./ret.txt");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

这种写法,虽然能够确保严谨,但是比较麻烦,下面给出一种简单&&可靠的办法!

public class Demo12 {
    public static void main(String[] args) {
        try(InputStream inputStream = new FileInputStream("./ret.txt")) {

        }catch(IOException e) {
            e.printStackTrace();
        }
    }
}

 这样的写法之下,不必写finally也不必写close,这里的()中创建的资源(可以是多个,中间用;)try的()执行完毕,最终都会自动执行close()。

必须实现了Closeable接口的类,才可以放到try()里面!

我们先看看ret.txt这个这个文件里面有什么内容:

里面只有一个字符h.

下面进行读取ret.text这个文件里面的内容。 

public class Demo14 {
    public static void main(String[] args) {
        try(InputStream inputStream = new FileInputStream("./test.txt")) {
            while (true) {
                int b = inputStream.read();
                if (b == -1) {
                    // 读取完毕了
                    break;
                }

                // 表示字节, 更习惯使用 十六进制 打印显示.
                 System.out.printf("0x%x\n", b);
            }
        }catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 h的十六进制就是68,所以说正确的读了出来!

下面,我将test.txt这个文件里面的内容改成“难受”,然后进行读取操作。这次不是一次只读取一个字节,而是读取多个。

public class Demo13 {
    public static void main(String[] args) {
        try(InputStream inputStream = new FileInputStream("./ret.txt")) {

            while(true) {
                byte[] buffer = new byte[1024];
                //n表示实际上读取到了多少个字节
                int n = inputStream.read(buffer);
                if(n == -1) {
                    break;
                }

                for(int i = 0;i < n;i++) {
                    System.out.printf("0x%x\n",buffer[i]);
                }

            }
        }catch (IOException e) {
            e.printStackTrace();
        }
    }
}

其中的 inputStream.read(buffer);此处是把buffer形参当成了“输出型参数”,这个操作就会把硬盘中读到的对应的数据,填充到buffer内存的字节数组中。(这个过程就像你是食堂打饭时,把你手中的饭盒交给食堂阿姨,阿姨打满饭菜之后交给你)

 读取展示完之后,那么写操作也是类似的啦。

可以发现,我们之前的文件中是有其他内容的,在进行写入之后,之前的内容被覆盖了。如果不想被覆盖,可以在实例对象中写上 true 。 

有了上述的InputStream he OutputStream操作的展示之后,后面的Reader 和 Writer 展示,我就仅仅展示一些代码来看看咯。 

Reader:

Writer: 

相信对于大家来说很简单吧。那么以上呢,就是今天的内容了,我们下一期再见吧。

“无论前方的路有多艰难,只要我们保持对未来的信心,就一定能克服一切困难。让我们一起在追梦的旅程中不断前行!”

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

并不会

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

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

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

打赏作者

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

抵扣说明:

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

余额充值