1 文件、路径的含义以及路径的种类

含义:1)狭义:保存在硬盘上的文件

           2)广义:操作系统进行资源管理的一种机制。很多的软件/硬件资源,抽象成“文件”来表示

说到文件,有人可能会想到文件夹,那文件夹是不是文件呢?
通俗来讲,答案是!专业术语来说,是目录(directory)

在计算机上,不允许直接操作硬盘,但可以通过文件的方式间接操作。

计算机上能够保存的文件很多,那是如何区分/识别文件的呢?
路径就是答案

计算机中,目录套目录,构成了树型结构(N叉树)。操作系统就能够通过这种结构组织目录和文件。从树根开始到最终的文件,中间都需要经过哪些目录,把这些目录记录下来,就构成了“路径”

一般使用 / (斜杠)来分割路径中的多级目录。在主流操作系统中,都是使用 / 分割的。但Windows是例外,/ 和 \ (反斜杠)它都支持。但是推荐写 / 这个。因为写 \ 这个在字符串中得转义

路径又分为绝对路径和相对路径。

绝对路径:从盘符(根节点)开始,逐级表示出来。比如:C:\Users\1\test.txt,C 就是根节点

相对路径:对于相对路径,它需要明确一个“基准路径”,有两个重要的符号:“.”(.表示当前所在的目录位置)和“..”(表示当前路径的上一层路径)。就以上面的绝对路径为例。

“.”:假设基准:C:\Users\1  相对路径:./test.txt ,假设基准:C:\Users  相对路径:./1/test.txt,

“..”:假设基准:C:\Users\1\Desktop  相对路径:../test.txt,

        假设基准:C:\Users\1\Desktop\myData 相对路径:../../test.txt。

那要是在代码中写一个相对路径,它的基准路径是什么呢?

不确定,取决于你程序运行的方式!

1)在IDEA中直接运行,基准路径就是项目的目录

2)打一个jar包,单独运行jar包。当前在哪个目录下执行运行命令(java -jar jar包名),基准目录就是哪个目录

3)如果是打成一个war包,放到tomcat中运行,此时基准目录就是tomcat的bin目录

2 文件的种类

从开发的角度,把文件分成两类:1)文本文件    2)二进制文件

但是,这里有个大前提:所有的文件都是二进制的,只不过有一些文件特殊(文本文件),二进制数据刚好能构成字符。

那如何判断文件是二进制的还是文本的呢?

如果二进制的数据恰好能够在码表上找到并且翻译过来是有意义的字符,那就是文本文件

实际开发中的判断方法,简单粗暴,那就是:直接使用记事本打开。打开之后,不乱吗,能看懂,那就是文本。乱吗,看不懂,那就是二进制。图片、视频、音频、可执行程序都是典型的二进制文件。txt纯文本,.java,.c都是典型的文本文件。值得一提的是,word,docx(富文本,不仅是文本内容,还可以有图片、多媒体信息)这样的文件是二进制文件。

3 文件操作

Java标准库提供了一系列的类来操作文件

文件操作分为两种:

1)文件系统操作

创建文件、删除文件、重命名文件、创建目录……

2)文件内容操作

针对一个文件里面的内容进行读和写

3.1 File类

File类用于操作文件系统

3.1.1 基础方法

构造方法:

File(File parent,String child) :根据父目录+孩子文件路径,创建一个新的File实例

File(String pathname):根据文件路径创建,绝对还是相对路径都可

File(File parent,String child):根据父目录+孩子文件路径,创建一个新的File实例,父目录用路径表示

方法:

代码示例:

绝对路径:

运行结果:

构造方法传入的是绝对路径,getPath就是绝对路径

相对路径:

运行结果:

getCanonicalPath()得到的是绝对路径的简化版本,值得注意的是,以上有无文件都是可以运行的

其他方法:

没有文件:

运行结果:

有文件(手动创建)的结果:

自动创建的代码:

出现异常的情况:1)硬盘满了  2)没有权限  3)硬盘坏了

3.1.2 delete()和deleteOnExit()

delete():删除文件,返回类型是boolean,如果删除的文件还在,执行这个方法,返回的结果是true,反之,是false

deleteOnExit():进程退出时删除文件

3.1.3  list() 和 listFiles()

这两个方法都是针对目录的,对文件来说是没有意义的。区别在于一个返回值类型是String(文件名),另一个返回值类型是File

list() 代码示例(这个是项目的):

运行结果:

把路径换成C盘:

运行结果:

综上我们可以看出(大家可以把你的代码的运行结果和对应路径下的目录对比一下),list只是列出当前目录里面的子元素,无法列出子目录里面的内容。

listFiles():得到的是File对象,包含更多的操作,更推荐大家使用

运行结果:

3.1.4 mkdir() 和 midirs()

创建目录,mk 是 “make”,dir 是 “directory”,mkdir一次创建一层目录,返回值类型是boolean,如果目录未创建,返回值是true,如果已经有了,返回值是false。mkdirs一次创建多层目录,返回值类型是boolean,如果目录未创建,返回值是true,如果已经有了,返回值是false。

mkdir 代码示例:

运行结果:

mkdirs 代码示例:

运行结果:

3.1.5 renameTo(File dest)

重命名,返回值类型是boolean,也起到移动(比如 剪切)的效果。从操作系统的角度看,重命名和移动的操作本质是一样的。这个移动操作速度极快(O(1)),但是,如果移动操作跨硬盘了,此时就相当于复制+删除 操作,速度就慢了,这里说的硬盘不是C盘/D盘。复制文件,O(N),因为文件/目录里的所有数据,遍历,在写入文件/目录

代码示例:

移动:

3.2 流对象

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

计算机中的“流”和生活中的水流是非常相似的。流是操作系统层面的术语,和语言无关,不是Java所特有的。

Java中提供了一组类(有几十个,但是我们先来了解8个即可)来表示流。

针对这几十个流,分成了两大类别:

1)字节流

   读写文件,以字节为单位,是针对二进制文件使用的

   代表类:InputStream  输入 (从文件读数据)   OutputStream 输出 (从文件写数据)

2)字符流

  读写文件,以字符为单位,是针对文本文件使用的

  代表类:Reader 输入  (从文件读数据)    Writer 输出 (从文件写数据)

值得注意的是,这4个类都是抽象类,Java中其他的流对象都是直接/间接继承这几个类

3.2.1 InputStream

作者有话说:只要把这个弄明白了,下面的几个类似。

因为这个类是抽象类,所以不能实例化,我们就new它的一个子类:

需要填写参数:文件的路径(绝对/相对)或 File对象

这里的创建对象成功(没有文件也可),就相当于打开文件,同时,也别忘了关闭文件,释放资源。

close很重要,无论如何都得执行,所以可以使用Finally

写到这,又引入了新的问题,那就是这个代码有点丑啊!!!

那该如何解决呢? 使用 try with resources

只要出了try的代码块,就会自动调用close,这个代码就相当于上述的代码。这个语法也支持多个文件,中间用“;”隔开。既然这个finally都可以用try with resources 了,那ReentrantLock 是不是也可以用类似的方式呢?

答案是 不可以,使用这个,要求这个类需要实现closable接口,也就是一定有close方法。

写到这,打开文件和关闭文件已经完事了,接下来,就可以读文件了: 

read():一次读取一个字节,返回值是int,而不是byte(原因:范围的问题,int可以用特殊值来表示特殊含义 比如用-1表示读取完毕)

代码示例:

test.txt里面写的是hello

运行结果:

我把这个运行结果和ascii表(hello 纯英文)对了一下,是对的(看10进制)。大家也可以写一下代码,然后看一看。

read(byte[] b):一次读取若干个字节,读到的数据放到参数b中

代码示例:

test里面是你好

运行结果:

其中 

这个代码是使用 参数作为方法的返回值,所以叫“输出型参数”。

因为 1024长度太大了,填不满,换成3,看看效果

运行结果:

read(byte[] b,int off,int len):off 为 offset 偏移量(数组下标) ,它的使用场景:有非常大的数组,每次读操作都把数据放到数组的某个部分。

3.2.2 OutputStream

write(int b):一次写一个字节

write(byt[] b):一次写若干个字节

write(byt[] b, int off, int len):一次写若干个字节,数组的一部分

对于OutputStream来说,默认会尝试创建不存在的文件,而且会清除上次的内容的(打开文件的一瞬间,上次的内容就清空了)。

write(int b) 代码示例:

结果:

结果:

上次写的 abc 就清除了,那如何解决呢?

使用追加的方式

上次的output文件已经有了cb,这回还往文件里面写cb,来看看结果

这回就没有清除上次的内容了。

write(byt[] b) 代码示例:

运行结果:

追加啥的都可以

3.2.3 Reader

read():一次读一个字节

read(char[] cbuf):读字符数组,尽可能把字符数组给填满,返回实际读到的字符个数

read(CharBuffer target):相当于对char[]进行了封装

代码示例:

运行结果:

代码示例:

运行结果:

3.2.4 Writer

可以看到,既有char 又有String,这里以String为主。和OutputStream一样,也会清除上次内容,解决办法也一样。

运行结果:

4 小总结

当前IO流对象,read和write属于直接读写文件,效率比较低,要想提高效率:
1)写代码的时候,手动创建缓冲区(byte[]),手动减少 read write次数

2)使用标准库提供的“缓冲区流” BufferedStream,把InputStream之类的对象套上一层

OK啦,到此结束!!!

更多推荐