此篇文章与大家分享文件操作及IO的内容
如果有不足的或者错误的请您指出!
目录
- *1.==解释IO==*
- *2.==关于文件的基本知识==*
- 2.1路径
- 2.1.1绝对路径
- 2.1.2相对路径
- 2.2文件分类
- *3.==通过Java代码操作文件==*
- 3.1针对文件系统进行操作
- 3.1.1File类的属性
- 3.1.2构造方法
- 3.1.3方法
- 3.1.4代码演示
- 3.2针对文件内容进行操作
- 3.2.1字节流
- read方法
- close关闭文件
- write方法
- 3.2.2字符流
- read方法
- write方法
- 3.2.3输入流对象搭配Scanner
1.解释IO
IO即为input和output,表示输入和输出
我们重点在于理解, 如何判断当前的操作是输入还是输出??
仅需通过一张图就能理解
我们以cpu为视角,上面的设备往下面设备的操作就是输出
下面设备往上面设备的操作就是输入
2.关于文件的基本知识
文件实际上本身也是个广义的概念,其实在操作系统中把很多资源(软件资源/硬件资源)都抽象为文件
但是我们本章节提到的实际上是狭义的文件,也就只是我们平时保存在硬盘上的那些文件
2.1路径
文件非常多,目录之间还能进行嵌套,我们就需要有一种方式,通过这个方式就能具体单位到某个文件上,这种方式就是路径
路劲就是从根节点开始,一层一层往下走,最终到达目录文件后,中间这些目录,集合起来就构成了"路径"
就拿我电脑上的QQ应用程序举例子
此时QQ.exe这个文件的路径就是
d:\QQ\bin\QQ.exe
在目录和目录之间用’/'或者 \进行分割(但是在日常开发,推荐使用/)
路径又分为相对路径和绝对路径
2.1.1绝对路径
如:d:/test.txt或d:\QQ\bin\QQ.exe
以盘符开头的就是绝对路径,起点都是"此电脑",但是此电脑可以省略
2.1.2相对路径
相对路径的起点可以是"任意路径",通常是工作目录
就比如如果我们的工作目录是 d:/QQ/Bin
那么此时 qq.exe 的相对路径就是 ./qq.exe
在相对路径里面,"."表示当前所在的位置
如果我们此时的工作目录是 d:/qq/Bin/plugins
那么想找到qq.exe就要先回到 Bin目录
而在相对路径里面,通过"…"就能返回上一级目录
此时的相对路径是 …/qq.exe
2.2文件分类
我们知道,文件是以二进制的形式保存在硬盘上的,文件的分类实际上有很多种,但是我们此时谈到的是和编写代码相关的,分为两种:文本文件和二进制文件
文本文件本质上就是字符串,每一部分都是"合法的字符",即文本文件里面的二进制数据能够构成合法的字符
而二进制文件里面的二进制数据不能构成合法的字符,或者说构成的字符合起来是无意义的(乱码)
分辨文本文件和二进制文件,最简单粗暴的方法就是.用记事本打开看看,如果是乱码,那就是二进制文件,反之就是文本文件
日常中,一些常见的比如docx,pptx,mp3,pdf都属于二进制文件(给程序看的)
而md,html,java,c则是文本文件(给人看的)
3.通过Java代码操作文件
3.1针对文件系统进行操作
在这个小节里,我们主要通过Java进行删除文件,创建文件,重命名文件等操作,即针对文件系统进行操作
在java中,用 File类来表示一个文件,进一步我们就可以通过File类里面的一些方法,对文件进行操作
3.1.1File类的属性
java为了能够跨平台,专门提供了变量,如果是wndows版本的jdk,上述变量值就是 ,如果在Linux / mac ,那么上述变量的值就是 /
3.1.2构造方法
我们可以直接指定一个目录,创建一个File实例
也可以指定父目录 + 孩子文件路径 来创建一个File实例(父目录可以是 File / 直接指定路径)
3.1.3方法
修饰符及返回值类型 | 方法签名 | 说明 |
---|---|---|
String | getParent() | 返回 File 对象的父目录文件路径 |
String | getName() | 返回 File 对象的纯文件名称 |
String | getPath() | 返回 File 对象的文件路径 |
String | getAbsolutePath() | 返回 File 对象的绝对路径 |
String | getCanonicalPath() | 返回 File 对象的修饰过的绝对路径 |
boolean | exists() | 判断 File 对象描述的文件是否真实存在 |
boolean | isDirectory() | 判断 File 对象代表的文件是否是一个目录 |
boolean | isFile() | 判断 File 对象代表的文件是否是一个普通文件 |
boolean | createNewFile() | 根据 File 对象,自动创建一个空文件。成功创建后返回 true |
boolean | delete() | 根据 File 对象,删除该文件。成功删除后返回 true |
void | deleteOnExit() | 根据 File 对象,标注文件将被删除,删除动作会到 JVM 运行结束时才会进行 |
String[] | list() | 返回 File 对象代表的目录下的所有文件名 |
File[] | listFiles() | 返回 File 对象代表的目录下的所有文件,以 File 对象表示 |
boolean | mkdir() | 创建 File 对象代表的目录 |
boolean | mkdirs() | 创建 File 对象代表的目录,如果必要,会创建中间目录 |
boolean | renameTo(File dest) | 进行文件改名,也可以视为我们平时的剪切、粘贴操作 |
boolean | canRead() | 判断用户是否对文件有可读权限 |
boolean | canWrite() | 判断用户是否对文件有可写权限 |
3.1.4代码演示
此时绝对路径和相对路径似乎没啥区别,我们再来看看下面代码
我们在当前工作目录的目录底下创建一个test.txt
使用相对路径创建一个File对象,再来看看区别
当我们构造函数写的是相对路径的时候,此时相对路径的基准,实际上和运行程序的方式有关,如果是idea直接
点击run运行,那么此时基准目录就是你项目所在的目录
此时的绝对路径实际上就是把当前的工作路径和相对路径进行简单的拼接
而相对路径就是把上面拼接的方法简化后的结果,把一些能省略的都省略的
如果我们将文件删掉,再来看看下面代码
那么我们就可以用代码来创建文件
进行createNewFile操作的时候,是有可能抛出异常的,即文件有可能是创建失败的
那么文件什么时候创建失败呢??
(1)给出的路径是非法的
(2)权限不足
同样我们还可以删除文件
删除文件还有另一个方法
如果我们使用的是deleteOnExit(),那么就是在进程结束的时候进行删除操作
进程结束后:
我们还可以创建一个文件夹
但是此时只是一个一级目录,我们可以同时创建多级目录
还可以对文件名进行更改
运行后
此时renameTo操作还能进行 移动文件操作
运行后
3.2针对文件内容进行操作
读文件,写文件,打开文件.关闭文件等操作就是针对文件内容进行操作
在java中,是通过"流"这样一组类,进行上述的文件内容操作
流就好比水流,我们在接水的时候,可以有很多方式,一次接一杯,一瓶,一桶…
对文件内容操作也是类似
我们针对文件内容的操作分成两种方式
(1)字节流,即以字节为单位读写数据,其中两个比较重要的类是InputStream 和 OutputStream
(2)字符流,即以单个字符为单位读写数据,其中两个比较重要的类是Reader 和 Writer
但是尽管是不同的流,不同的类,核心的操作就是4个:
(1)通过构造方法,打开文件
(2)通过read方法读文件内容
(3)通过write方法写文件内容
(4)通过close方法关闭文件
3.2.1字节流
实际上我们发现,InputStream是一个抽象类,不能实例化
这样设置的原因是,InputStream 对应不只是操作硬盘,也可以对应控制台,网卡…等其他硬件
而标准库已经提供了具体的子类了,直接拿来就能够用
public static void main(String[] args) throws FileNotFoundException {InputStream inputStream = new FileInputStream("./test.txt");}
read方法
read有三种方法,
第一种是一个字节一个字节从文件中读取,此时读到的内容就是通过返回值来表示了
但是我们看到,read方法的返回值是int类型?? 不应该是字节吗???
实际上,在计算机中,对于"字节"的数据来说,应该是"无符号的",因为是不需要它来进行任何算术运算的
而在java中,byte类型是有符号的,虽然大小1个字节,但是存储范围在-128 ~ 127,而字节的存储范围在 0 ~ 255,因此用int 来接收返回值,就能够 保证读到的数据都是正数
此外
在文档中提到,当读到文件末尾的时候,返回的数据是-1,但是如果是byte类型,-1会被当成有效数据,达不到文件末尾标志的作用,而用int,由于读取到的全是正数,因此如果读取到-1就说明到达文件末尾了
此时的读取结果就对应着hello 的 ascii码值,而最后的 13 和 11 ,实际上就是 回车键(\r)和换行键(\n)
第二种读的方式是:
此时返回结果n代表读取的字节数量,当读取到文件末尾的时候,还是返回-1
那么上述两种操作那个效率更高呢??当然是第二个,因为我们每次read的时候都是要通过系统API来访问硬盘的,而访问硬盘是低效操作
第三种读的方法是:
实际上和第二种差不多,但是此时是将内容从数组的off下标开始填充,填充len这么多
close关闭文件
这是个非常关键的操作
如果在一个进程中你打开一个文件而没有关闭,大部分情况你可能没有发现,但是一旦真的遇到了问题,那就是大事
实际上我们打开文件的时候,会在操作系统内核的pcb结构体里的"文件描述操作符"添加一个元素,这个元素就代表当前打开的文件的相关信息
而"文件描述操作符"就相当于一个顺序表,但是这个顺序表的长度是存在上限的,这个东西不能自动扩容!!!
一旦反复打开文件而不关闭,一旦导致文件描述符被占满,此时再次打开文件,就会打开失败,那么其他的操作,如网络通信相关的操作也会受到影响
这个问题是非常关键的,尤其对于服务器来说,当前我们写的代码,就属于进程一下就结束的,此时pcb对应也会销毁,就没啥机会能够把文件描述操作符占满
但是对于服务器要7 * 24小时持续运行,就很容易出现问题
这个问题我们称之为"文件资源泄露"
这个问题就类似与"内存泄露",我们称之为"文件资源泄露"
我们关闭文件是通过close方法,但是如果代码前面出现异常直接return了,那么就很可能导致我们没有及时关闭文件.close执行不到了
一种方法就是通过try finally方法
另外一种更加推荐的方法是:
此时流对象的创建是写在try()里之后,此时代码执行完try() {} 之后,就会自动调用inputStream.close了
但是不是所有的类创建实例都能放到try里面.之后实现了CLoseable接口的类才能放到try里面
write方法
与read一样,write也有三种方法
(1)一次写一个字节
实际上按照写操作打开文件的时候,会把文件里面原来的内容清空掉(不是write清空的,是打开操作清空的)
如果我们不想要原来的文件被清空,可以在打开操作添加一个参数
另外两种写法和读取的方式一样,此处就不一一列举
3.2.2字符流
read方法
此时的read虽然有4种方法,但是实际上和 字节流 的read方法类似,只是 此处的2CharBuffer是jvm对 char[] 的封装
同样中文也可以读取
但是如果我们使用字节流来读取中文
会发现,一个汉字是3个字节,但是在java里面明明一个char类型是2个字节??
这里实际上是java对上述的数据进行了编码转换
虽然在原始数据里面,是3个字节一个字符,但是read操作在读取的时候,是能够通过特殊的方式来识别是utf8格式的
此时读的是3个字节,返回成一个char类型的时候,把utf8转成了unicode格式,在uniCode格式,一个汉字就是2个字节
write方法
注意,此时按照这种写的方式也是会清空原来的内容的
因此如果我们要追加还是一样加上参数即可
注意:以写的方式打开文件,如果文件不存在,会自动创建一个文件
3.2.3输入流对象搭配Scanner
实际上我们之前用过的System.in里面的in就是一个输入流类的实例
那么我们就可以讲原来的System.in 换成 InputStream对象