“他越拧巴,我越喜欢!”
文件:
此处谈到的文件,本身有很多的含义。
狭义上的文件,特指 硬盘上的文件(以及保存文件的目录)。
广义上的文件,计算机上的很多硬件设备,软件资源,在操作系统中都会被视为“文件”。
硬盘和内存对比:
- 硬盘的存储空间大,内存小
- 硬盘的访问速度慢,内存快 (硬盘访问速度慢,和它硬件的物理结构有关)
- 硬盘的成本低,内存高
- 硬盘能持久化存储,内存断电后数据会丢失
树型结构组织 和 目录:
随着文件越来越多,对文件的系统管理也被提上了⽇程,如何进行文件的组织呢,⼀种合乎⾃
然的想法出现了,就是按照层级结构进⾏组织 ⸺ 也就是我们数据结构中学习过的树形结构。这样,⼀种专门用来存放管理信息的特殊文件诞⽣了,也就是我们平时所谓⽂件夹(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实例。
常用方法
-
文件和目录的创建、删除
boolean createNewFile()
: 创建一个新文件,如果文件已经存在则返回false。boolean mkdir()
: 创建一个新目录,只有在其父目录存在的情况下才会创建成功。boolean mkdirs()
: 创建一个新目录,包括任何必需但不存在的父目录。boolean delete()
: 删除文件或目录,只有在目录为空时可以删除。void deleteOnExit():根据File对象,标注我呢间将被删除,删除的动作会到JVM运行结束时才会进行。
-
文件和目录的查询
boolean exists()
: 测试文件或目录是否存在。boolean isDirectory()
: 测试此抽象路径名表示的文件是否是一个目录。boolean isFile()
: 测试此抽象路径名表示的文件是否是一个标准文件。boolean isHidden()
: 测试此抽象路径名表示的文件是否是一个隐藏文件。String getName()
: 获取文件或目录的名称。String getPath()
: 获取文件或目录的路径。String getAbsolutePath()
: 获取文件或目录的绝对路径。- String getcanonicalPath(): 返回File对象的修饰过的绝对路径
long length()
: 获取文件的字节长度。
-
文件和目录操作
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
类的一些常用方法:
-
int read(): 读取一个字节的数据,返回一个整数(0-255),如果达到流的末尾,则返回 -1。
-
int read(byte[] b): 读取多个字节的数据,将读取的数据存储到字节数组
b
中,返回读取的字节数,如果达到流的末尾,则返回 -1。 -
int read(byte[] b, int off, int len): 从输入流中读取最多
len
个字节的数据,并将其存储在数组b
从偏移量off
开始的位置。返回实际读取的字节数,如果达到流的末尾,则返回 -1。 -
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:
相信对于大家来说很简单吧。那么以上呢,就是今天的内容了,我们下一期再见吧。
“无论前方的路有多艰难,只要我们保持对未来的信心,就一定能克服一切困难。让我们一起在追梦的旅程中不断前行!”