【IO】文件操作

🥰🥰🥰来都来了,不妨点个关注叭!
👉博客主页:欢迎各位大佬!👈

文章目录

  • [1. 文件](#1. 文件)
    • [1.1 认识文件](#1.1 认识文件)
    • [1.2 分清操作的是内存还是硬盘](#1.2 分清操作的是内存还是硬盘)
    • [1.3 路径](#1.3 路径)
      • [1.3.1 目录结构](#1.3.1 目录结构)
      • [1.3.2 相对和绝对路径](#1.3.2 相对和绝对路径)
    • [1.4 文本文件和二进制文件](#1.4 文本文件和二进制文件)
      • [1.4.1 含义](#1.4.1 含义)
      • [1.4.2 区分](#1.4.2 区分)
  • [2. 文件系统操作](#2. 文件系统操作)
    • [2.1 构造 File 对象](#2.1 构造 File 对象)
    • [2.2 File 提供的方法](#2.2 File 提供的方法)
  • [3. 文件内容操作](#3. 文件内容操作)
    • [3.1 分类](#3.1 分类)
    • [3.2 字节流 ------ InputStream和OutputSream的使用方法](#3.2 字节流 —— InputStream和OutputSream的使用方法)
    • [3.3 try with resources 用法](#3.3 try with resources 用法)
    • [3.4 字符流 ------ Reader和Writer的使用方法](#3.4 字符流 —— Reader和Writer的使用方法)
    • [3.5 如何实现读一行](#3.5 如何实现读一行)

1. 文件

1.1 认识文件

平时谈到的"文件",指的都是硬盘上的文件

知识回顾

硬盘(外存) 与 内存相比,从如下4个方面对比:

1)速度:内存比硬盘快很多

2)空间:内存空间比硬盘小

3)成本:内存比硬盘贵(随着时代的发展,目前内存也不算太贵啦)

4)持久化:内存掉电后数据丢失,硬盘掉电后数据仍在

1.2 分清操作的是内存还是硬盘

回顾我们之前写的代码,绝大部分都是围绕内存展开的,比如定义一个变量,其实就是在内存上申请空间,MySQL主要是操作硬盘

IO】指的是输入和输出,I 是 input,O 是 output

文件IO 这里也是操作硬盘~ 有两种操作,如下:

1)文件系统操作:创建文件,删除文件,重命名文件,创建目录等等

2)文件内容操作:针对文件的内容进行读和写

1.3 路径

路径即文件系统上一个文件/目录,具体的位置

目录 ==> 文件夹 (目录,即文件夹)

目录 ------ 计算机专业人士,专业术语

文件夹 ------ 通俗的说法(我们经常说的~)

1.3.1 目录结构

计算机的目录是层级结构文件系统是以树型结构来组织文件和目录的 ,是N叉树 ,如下图所示:

从上图可以看到,文件路径 ,就是从树根节点出发,沿着树杈,一路往下走,到达目标文件,此时这个中间经过的内容

1.3.2 相对和绝对路径

Windows 都是从"此电脑" 开始的,表示路径时,可以把 "此电脑" 省略,直接从盘符开始表示

实际表示路径如下:

D:\QQ\Misc\QQApp

实际表示路径,是通过一个字符串表示,每个目录之间使用 \ (反斜杠) 或者 / (斜杆)

(注意:\ 反斜杠只是在Windows中适用,代码中需要写成 \ 需要转义,所有在代码中最好用 / 斜杠表示)

绝对路径 】从盘符开始,一层一层往下找,这个过程中得到的路径为绝对路径

相对路径 】从给定的某个目录出发,一层一层往下找,找得这个过程得到的路径为相对路径
注意!!! 相对路径一定要明确工作目录,即基准目录什么!!!

在具体一点来说儿~ 工作目录就是项目所在的目录,写相对路径就是以工作目录/基准目录为基准来展开的!!!

举个栗子,比如我要找一碗热干面,

我可以从宇宙出发,进行寻找,一直找宇宙 ------> 地球 ------> 中国 ------> 湖北 ------> 武汉... 找到热干面,这是绝对路径

我也可以直接从中国出发,一直找中国 ------> 湖北 ------> 武汉...找到热干面,这是相对路径

路径举例

要找到USBCoInstaller.dill,绝对路径如下:
1)假设工作目录如下:

此时找到USBCoInstaller.dill 相对路径的表示:./32/USBCoInstaller.dill

2)假设工作目录如下:

此时找到USBCoInstaller.dill 相对路径的表示:./adbdrv/32/USBCoInstaller.dill

3)假设工作目录如下:

此时找到USBCoInstaller.dill 相对路径的表示:./LEmu/adbdrv/32/USBCoInstaller.dill

补充说明

文件系统上,任何一个文件对应的路径是唯一的!!! 不会存在两个路径相同,但是文件不一样的情况

1)在Windows 系统上可以认为,路径和文件是一一对应的,路径就相当于一个文件的"身份标识",具有唯一

2)在Linux 系统上,可能存在一个文件,有两个不同的路径能找到该文件

总结

关于路径,是非常关键的~后面很多地方会涉及到路径,尤其是相对路径,相对路径一定要明确工作目录(基准目录)是什么,两者相比之下,绝对路径就可以理解成以"此电脑"为工作路径

1.4 文本文件和二进制文件

1.4.1 含义

文本文件

存储的都是文本文本文件内容都是由ASCII字符构成的 ,以下是ASCII字符表部分展示:
对于ASCII而言,表示范围0-127,随着时代发展,又有了其它的编码方式,如 utf8 之类的,就可以针对其它语言的文字符号进行编码,utf8 可以想象成一个更大的表,但是终究是有限的!
文本文件存储的数据就是遵守ASCII或者其它字符集编码,所得到的文件,本质上存的是字符,不仅仅是所说的char!!!

二进制文件

存储的是二进制数据 ,没有任何字符集的限制,存什么都是可以的,不受限制!!!

1.4.2 区分

最简单粗暴的方式:直接使用记事本打开某个文件,通过其内容判断
如果能看懂这个文件,则是文本文件,
如果是一大堆乱码,看不懂的,则是二进制文件

(为什么会出现乱码,因为二进制都是一个个字节,记事本尝试把当前若干个字节的数据我那个 utf8 里面套,套出来是什么就是什么,套不出来的就是方块)

常见的文本文件

1).txt

2).java

3).c

常见的二进制文件

1).exe

2).jpg

3).mp3

4).docx

5).excel

如图所示,.java文件被记事本打开是可以看懂的内容,比如这些是导入包的代码语句,.java是文本文件

如图所示,.docx 文件被记事本打开是一大堆乱码,完全看不懂的内容,.docx 是二进制文件

2. 文件系统操作

文件系统操作,我们需要了解是怎么操作的,文件是存储在硬盘上的,直接通过代码操作硬盘,不是很方便,就在内存中创建一个对应的对象,通过操作这个内存中的对象,就可以间接影响到硬盘的文件情况~
所以,Java 标准库给我们提供了 File 这个类,File 对象是硬盘上的一个文件"抽象"的表示,通过代码对 File 对象的操作,间接影响硬盘的文件内容

打个形象的比方,这里的 File 对象相当于遥控器,我们可以通过控制遥控器,即通过代码操作 File 对象,来间接控制电视频道,即间接控制硬盘的文件

2.1 构造 File 对象

File类构造方法如下:

其中,最常用的还是第2种,构造的过程中,可以使用绝对路径/相对路径 进行初始化
注意:这个路径指向的文件,可以是真实存在的,也可以是不存在的!!! 这里是不受限制的~

创建 File 对象,需要导入该包

c 复制代码
import java.io.File;

具体创建 File 对象过程如下:

c 复制代码
public class IODemo {
    public static void main(String[] args) throws IOException {
        File file = new File("d:/student.txt");  
        //这里可以是相对路径,也可以是绝对路径
    }
}

2.2 File 提供的方法

这些方法,我们一看名字,就知道怎么用,很容易理解,不需要去记,在实践中掌握就可以啦~ 忘记或者不会的,查查就可以!

其中需要注意的是:

【Q】如果输入的路径,文件不存在,系统会自动创建一个文件吗?

【A】并不会,只有手动调用 createNewFile() 方法,才可以创建文件(注意文件是文件,目录是目录,区分开来!!!)

下面,我们进行代码的演示,以熟悉上述操作:

1)前面五种操作,返回值均为 boolean 类型

c 复制代码
public class IODemo1 {
    public static void main(String[] args) throws IOException {

        File file = new File("d:/student.txt");
        System.out.println(file.getParent());
        System.out.println(file.getName());
        System.out.println(file.getPath());
        System.out.println(file.getAbsoluteFile());
        System.out.println(file.getCanonicalFile());
    }
}

打印结果如下:

2)接下来的五种操作

c 复制代码
public class IODemo2 {
    public static void main(String[] args) throws IOException {
        File file = new File("./hello world.txt");

        System.out.println(file.exists());
        System.out.println(file.isDirectory());
        System.out.println(file.isAbsolute());

        //创建文件
        file.createNewFile();

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

        //删除文件之后
        file.delete();
        System.out.println(file.exists());
    }
}

打印结果如下:

3)创建目录

c 复制代码
public class IODemo3 {
    public static void main(String[] args) {
        File file = new File("test-dir/aaa/bbb");
        //只能创建1个目录
        //file.mkdir();
        //创建多级目录
        file.mkdirs();
    }
}

打印结果如下:可以在左侧看到,文件夹test-dir,创建了多级目录

4)罗列目录下的内容

c 复制代码
public class IODemo4 {
    public static void main(String[] args) {
        File file = new File("test-dir");
        //罗列目录下的内容
        String[] result1 = file.list();
        System.out.println(Arrays.toString(result1));
        File[] result2 = file.listFiles();
        System.out.println(Arrays.toString(result2));
    }
}

打印结果如下:

5)修改文件名字

c 复制代码
public class IODemo5 {
    public static void main(String[] args) {
        File src = new File("./test-dir");
        File dest = new File("./test123");
        src.renameTo(dest);
    }
}

打印结果如下:

以上是 File 类方法的一些基本操作演示,通过这些案例,我们更够更加熟悉叭~

3. 文件内容操作

3.1 分类

文件内容操作,提供了很多类,一组类,进行操作,分为两类

1)针对文本文件 ,提供了一组类,统称为"字符流 "

典型代表:Reader,Writer 读写的基本单位是 字符

2)针对二进制文件 ,提供了一组类,统称为"字节流 "

典型代表,InputStream,OutputSream 读写的基本单位是 字节

这里出现了一个"新"字 ------> "流"(stream)

比如在生活中,水流~ 从水龙头接水,假如让你接 1000ml 的水,你的接法有很多种,

<1> 一次性接完 1000ml 的水

<2> 一次接 500ml,分两次接完

<3> 一次接 200ml,分五次接完

...

字节流

假如让你从文件读取 1000 个字节的数据,你的读法也可以有很多种

<1> 一次性读完 1000 字节的数据

<2> 一次读 500 字节的数据,分两次读完

<3> 一次接 200 字节的数据,分五次接完

每种流对象,分为两种:

输入的:Reader,InputStream

输出的:Writer,OutputStream
【注意】一定要认清输入输出的方向!!! 以下面示意图,更加明确输入和输出的方向:

3.2 字节流 ------ InputStream和OutputSream的使用方法

InputStream 用来进行 IO,IO 不仅仅可以读写硬盘的文件,还可以读写别的,如后面学习到网络编程,可以知道IO 还能用来读网卡

初步认识一下 InputStream,是抽象类,不能直接实例化!!!

使用方法:

c 复制代码
InputStream inputStream = new FileInputStream(指定当前要读的一个文件路径);

重点------关闭操作

同时一定要记得一个操作:关闭操作!!!

c 复制代码
inputStream.close();

这个操作非常重要!在C++中,主要是一个手动释放资源,包括内存,Java中有GC,内存一般不需要手动释放,但是在这里文件的资源,则需要手动释放!

(垃圾回收(GarbageCollection,GC),它的主要作用是回收程序中不再使用的内存)

【这里文件的资源指的是什么呢?】

主要指的是"文件描述符",这里回顾一下前面讲解进程时候,提到进程是使用 PCB 这样的结构来表示的,

其中PCB中的属性有: 1)pid 2)内存指针 3)进程调度信息4) 文件描述符表

文件描述符表记载了当前进程都打开了哪些文件,每次打开一个文件都在这个表里申请到一个位置,这个表可以当做一个数组,数组下标就是文件描述符,数组元素就是这个文件在内核中结构体的表示,但是这个表的长度是有限的,不能无休止的打开而不进行释放,一旦满了,继续打开,就会打开失败,造成文件资源泄露的问题,这个问题是十分严重的!!!

c 复制代码
public class IODemo {
    public static void main(String[] args) throws IOException {
        //这个过程就是相当于C中的fopen 文件的打开操作
        //让这个当前变量和硬盘的文件相关联起来
        InputStream inputStream = new FileInputStream("d:/student.txt");   
        //...
        //进行读的操作     
	    //关闭操作很重要!!
        inputStream.close();
		}
}

但是这样写 如果执行中间出现一些问题 比如return 或者 抛出异常,close就执行不到了,是十分危险的

既然这样,我们要确保close()方法执行到,可以进行如下修改:

c 复制代码
public class IODemo {
    public static void main(String[] args) throws IOException {
		  InputStream inputStream = null;
          try {
            inputStream = new FileInputStream("d:/student.txt");
            //...
           	//进行读的操作
        } finally {
            inputStream.close();
        	}
		}
}

但是上述代码写法是不优雅的~ 代码也要讲究美观性呀,下面介绍try with resources 用法,使代码"漂亮"起来,即更加简洁起来!

同理,OutputSream的用法如下:

c 复制代码
OutputStream outputStream = new FileOutputStream(指定当前要读的一个文件路径);

3.3 try with resources 用法

try with resources 】是一个异常处理机制,它的作用是解决使用 try-catch 语句时可能遇到的代码重复和繁琐问题,该机制可以确保即使在抛出异常的情况下,也能将资源正确的关闭

范围】try with resources 语句可以用于处理任何实现了java.lang.AutoCloseable接口的对象,这包括了所有IO 对象(如InputStream、OutputStream、Reader和Writer)、JDBC资源(如Connection、Statement)和Zip压缩文件(如ZipFile和ZipInputStream)

因为 InputStream 实现了一个特定的接口------> Closeable,充分利用该特点,带有资源的 try 操作,会在 try 代码块结束自动执行 close 关闭操作

inputStream 相当于遥控器,通过它调用 read() 对文件进行读取,有三种形式,如下:

1) read() 无参数,相当于一次读一个字节

2) read(byte[] b) 读多个字节,放在 byte[] 数组里面

3) read(byte[] b,int off,int len) 读多个字节,放在 byte[] 数组里面,限定起始位置和长度

补充】这里可以看到,都是 int 接收,为啥不使用 short ?

我们可以把 short 忘记啦,看起来是节省了两个字节,但是显得很小气!!! 虽然 short 也实现内存对齐,但是没有 int来得快,short 和 float 都用得很少!!!

表示整数,一般都是 int 和 long

表示浮点数,一般都是 double

代码举例如下:

c 复制代码
public class IODemo {
    public static void main(String[] args) throws IOException {
        try(InputStream inputStream = new FileInputStream("d:/student.txt")) {
            //读文件
            //read一次返回的是一个字节 但是此处的返回类型是int!!!
            while(true) {
                int b = inputStream.read();
                if(b == -1) {
                    //读到末尾了
                    break;
                }
                System.out.println(b);
            }
        }
    }
}

打印的结果如下:

解释说明

在此处读到的97、98、99,是因为使用的是字节流,每次读的不是字符,而是字节,读出的这些数据就是每个字符的ASCII码!!! a,b,c 对应的ASCII码正是97、98、99

如果student.txt 文件写的是汉字呢?结果将会是怎么样的呢~

重新运行程序,打印结果如下:

字符集!!! 2个汉字,6个字节(每个数字就是一个字节,上述程序运行结果有6个数字,即6个字节),每个汉字3个字节,很明显是utf8编码的~查询对应结果如下:

上述打印结果为10进制,是把3个字节放在一起,弄了一个大的十进制,和把3个字节分开打印是不一样的,把代码按照十六进制打印,更方便看清,与上图结果相对应

c 复制代码
  //转成16进制打印
  System.out.printf("%x\n",b);

utf8和unicode就可以视为两个不同的表,编号是不同的,即使是同一个符号(汉字)得到的数值是不一样的,uft8是基于unicode演化而来,写代码统一为utf8是最靠谱的做法!
这里是utf8查询转换链接

OutputStream 写入的举例代码,如下:

c 复制代码
public class IODemo7 {
    public static void main(String[] args) {
        try(OutputStream outputStream = new FileOutputStream("d:/student.txt")) {
            outputStream.write(100);
            outputStream.write(101);
            outputStream.write(102);
        } catch(IOException e) {
            e.printStackTrace();
        }
    }
}

运行程序后,打开student.txt,ASCII值为100对应小写字母d,101对应e,102对应f,成功写入!

有一种很神奇的感觉,通过代码就能实现对文件的内容写入~ 还是很方便的!

3.4 字符流 ------ Reader和Writer的使用方法

与字节流是类似的~

Reader 对应的 FileReader

c 复制代码
Reader reader = new FileReader(指定当前要读的一个文件路径);

Writer 对应的FileWriter

c 复制代码
Writer writer = new FileWriter(指定当前要读的一个文件路径);

read()方法来读 ------ 一次读一个char或者char[]

write()方法来写 ------ 一次写一个char 或者char[]或者String

举个栗子~

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

打印结果如下,是对应的,读取成功!

3.5 如何实现读一行

使用Scanner 套就可以啦,不多说直接上代码!

c 复制代码
public class IODemo8 {
    public static void main(String[] args) {
    
        File file = new File("d:/student.txt");

        try (Scanner sc = new Scanner(file)) {
                while(sc.hasNextLine()) {
                    String line = sc.nextLine();
                    System.out.println(line);
                }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

打印结果如下:

💛💛💛本期内容回顾💛💛💛

✨✨✨本期内容到此结束啦~

相关推荐
老猿讲编程18 分钟前
一个例子来说明Ada语言的实时性支持
开发语言·ada
Chrikk1 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*1 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue1 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man1 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
测开小菜鸟1 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity2 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天2 小时前
java的threadlocal为何内存泄漏
java
caridle3 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
萧鼎3 小时前
Python并发编程库:Asyncio的异步编程实战
开发语言·数据库·python·异步