目录
一、了解什么是文件
狭义的文件:
广义的文件:
二、文件的路径
①文件的绝对路径
②文件的相对路径
三、Java对于文件的操作
File类的构造方法
File类的普通方法
四、对于文件的内容操作
①FileInputStream(文件输入流)
常用构造方法:传递一个字符串
read方法
(1)无参数的方式:read()
(2)有参数的版本,参数为一个byte数组
②FileOutputStream(文件输出流)
五、close()方法
需要及时关闭"流"的原因:
六、更加优雅的close()方式
采用Scanner来读取磁盘的内容:
七、字符流
一、了解什么是文件
文件,File这个概念,在计算机当中,也是非常常见的一个词语。
狭义的文件:
指的的是计算机硬盘上面的文件和目录(文件夹)。
如图:
广义的文件:
泛指计算机当中的很多的软硬件资源。
操作系统把很多的硬件设备和软件资源抽象成了文件,例如网卡等待...
下面将要重点讨论的,就是狭义的文件
二、文件的路径
文件的路径,主要是有以下两种表示方式:
①文件的绝对路径
绝对路径,也比较好理解,可以视为文件相对于某个盘,所存储的位置,一般情况下面,以D:/***/***这样的形式表示。
例如在上图当中,jdk1.88这个文件夹,它的绝对路径就是:D:\jdk1.88
需要注意的是,虽然在windows当中,现在表示一个文件的目录都是采用“\"作为目录的分隔符的。
但是,实际上在代码的书写当中不可以采取反斜杠作为路径的分隔符,一般都是采取正斜杠的方式作为分隔符。 因为,反斜杠通常搭配一个普通的字符,都会把"\+某个字符"转义为其他的字符。
这样,容易引发歧义。因此在代码的书写当中,还是要采取正斜杠"/"的方式。
②文件的相对路径
相对路径,通常以当前所在目录作为基准,以"."或者".."开头,找到指定的路径。
举个例子:
当点击进入了:D:\jdk1.88\lib这个文件夹之后,当前的目录就是:D:\jdk1.88\lib
如果想在当前的D:\jdk1.88\lib 目录下面定位到第一个文件夹,也就是missioncontrol。就可以表示为:./missioncontrol
其中: “./” 就表示当前的目录。
同理可得:如果当前目录是D:\jdk1.88
那么,想定位到lib目录,就是:./lib
三、Java对于文件的操作
分为以下两类:
1、针对文件的系统操作(创建、删除、重命名等等);
2、针对文件内容的操作(针对某个文件的读写)。
下面,首先介绍对于文件的系统操作,需要使用的类为File这个类。
File类的构造方法
构造方法 | 说明 |
File(String pathname) | 根据文件的路径创建一个File实例,实例可以是绝对路径, 也可以是相对路径 |
File(File parent,String child) | 根据父目录+孩子文件路径,创建一个新的File实例 |
File(String parent,String child) | 根据父目录+孩子文件路径,创建一个新的File实例,与第2个不同的是, 父目录用路径表示 |
其中,第一行的构造方法是最常见的
代码实现:
File file=new File("D:/bookBook.txt");
这样,就成功创建出来一个file的实例了。
需要注意的事项有下面两点:
①file对象代表的文件,此时不一定在对应的位置当中存在。如果不存在,可以在后续调用file.mkdir()方法创建。
如果已经存在了再次调用mkdir()方法创建,那么不会重新创建一个文件,也不会覆盖掉原来已经存在的文件的内容。
②如果是单个斜杠,代表创建一个文件,例如(...txt/...doc)
如果两个及以上的斜杠,代表创建一个文件夹
File类的普通方法
修饰符及返回 值类型 | 方法签名 | 方法签名 |
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() | 判断用户是否对文件有可写权限 |
下面,将演示几个常见的普通方法代码实现:
①file.getName(String name)
假设此时文件"bookBook1.txt"已经存在了
如下面的代码:
//不一定在D盘当中一定存在这一个路径File file=new File("D:/bookBook.txt");System.out.println(file.getName());
就会输出bookBook.txt,为对应文件的名称。
如果执行下面的代码:
File file=new File("./bookBook.txt");System.out.println(file.getName());
也会输出bookBook.txt。
②file.getParent()
File file=new File("D:/bookBook/hello.txt");System.out.println(file.getParent());
此时,将输出hello.txt的最近一级的目录:即:D:\bookBook
③file.getPath(),getAbsolutePath()
此时,将分情况讨论:
如果file的构造方法当中指定的是一个绝对路径,也就是类似于D:/***/***这样的路径,那么二者输出的时候没有任何差别。
File file=new File("D:/bookBook/hello.txt");System.out.println(file.getAbsolutePath());System.out.println(file.getPath());
File file=new File("./hello.txt");System.out.println(file.getAbsolutePath());System.out.println(file.getPath());
如果file的构造方法当中指定的是一个相对路径,也就是类似于./***这样的路径,那么二者的差别在于:
file.getAbsolutePatrh()返回的是”绝对路径“,也就是从D盘开始的路径。
由于此时hello.txt位于idea当中,因此,返回的路径就是当前idea项目的路径+hello.txt
file.getPath()返回的是这一个文件的”工作路径"。
运行结果截图:
④file.isDirectory(String name)
输出是否为一个目录。
如果传递的参数为一个目录,也就是一个文件夹,那么返回true。
如果传递的参数为一个文件的地址,那么返回false。
//demo26为一个具体的项目文件夹File file=new File("D:/demo26");//test为一个txt文本文档File file1=new File("./test.txt");//输出trueSystem.out.println(file.isDirectory());//输出falseSystem.out.println(file1.isDirectory());
⑤file.createNewFile:在指定的目录创建一个文件,如果创建成功返回true,否则false
//test为一个txt文本文档File file1=new File("./test1.txt");//创建成功返回true,创建失败返回falseSystem.out.println(file1.createNewFile());
同理,file.delete()也是删除一个指定目录的文件。
四、对于文件的内容操作
在三当中,提到了对文件内容进行读取操作,那么,下面将介绍对于文件内容的读取操作的一些介绍。
我们常说的IO流,就是内存和硬盘资的数据交互的一种工具。
图解:输入流、输出流:
从磁盘读取数据到内存的流,就是输入流------->FileInputStream。
从内存读取数据到磁盘的流,就是输出流------->FileOutputStream。
因此,所谓的输入输出流,都是相对于应用程序来讲的。
①FileInputStream(文件输入流)
常用构造方法:传递一个字符串
此处的字符串,可以是相对目录,也可以是绝对目录。下面代码采用的是绝对目录的方式
//如果文件不存在,就会抛出异常FileInputStream inputStream=new FileInputStream("./test.txt");
需要注意的是,读取的字符串一定要是一个确确实实存在的文件,否则会抛出 FileNotFoundException
read方法
此方法在FileInputStream内部是存在多态的:
下面,将简单介绍一下read方法的三个多态形式:
(1)无参数的方式:read()
FileInputStream是按照一个一个字节的顺序读取的
相当于一次只读取一个字节的数据。
read()方法的返回值却是int类型的。
那么它的含义就是,每次只读取一个字节的数据,读取的是它的ASCII码值。
读取完一个数据之后,再次调用read()方法,会接着读取接下来的一个数据。
当把整个文件都读取完毕之后,如果继续调用read()方法,那么会直接返回-1.
代码实现
public static void main(String[] args) {try {FileInputStream inputStream= new FileInputStream("./test.txt");while (true){int b=inputStream.read();//当读取到的值为-1的时候,意味着已经读取完毕了,直接跳出循环if(b==-1){break;}//此处需要强制类型转换,才可以读取到对应的真实的字符System.out.print(""+(char)b);}//使用完毕之后,需要手动关闭这个流inputStream.close();} catch (IOException e) {e.printStackTrace();}
运行结果截图:
(2)有参数的版本,参数为一个byte数组
byte数组为read读取到的值所传递的数组。
当第一次调用inputStream.read(byteArray)的时候,会一次性把目标文件当中的所有内容一次保存到byteArray当中,并且返回实际读取的文件的字节数量。
读取完毕之后,第二次再次读取,会返回-1.
代码实现:
public static void main(String[] args) {try {//如果文件不存在,就会抛出异常FileInputStream inputStream=new FileInputStream("./test.txt");//read无参数版本:一次读取一个字节byte[] buffer=new byte[1024];while (true){//读取到某一个值,让字符数组对这一个buffer进行读取//尽可能让int len=inputStream.read(buffer);if(len==-1){break;}//遍历buffer数组for(int i=0;i<len;i++){System.out.println(buffer[i]);}}//使用完毕之后,需要手动关闭这个流inputStream.close();} catch (IOException e) {e.printStackTrace();}}
此时,运行结果为:
从上往下依次对应hello的ASCII值
灵魂拷问:为什么第(2)种方式的效率更高?
首先需要了解一个前提条件,就是,使用FileInputStream进行IO操作的时候,每IO一次的时间是相同的。
而第一种方式当中,每循一次,read一次,相当于只IO一次,读取了一次文件,就把所有的内容读取到内存当中了。
因此,第二种方式减少了IO次数,提高了执行的效率。
②FileOutputStream(文件输出流)
文件专门是把内存当中的数据读取到文件(磁盘)当中。
也是在构造方法当中指定了需要传输的对应的文件。
OutputStream outputStream=new FileOutputStream("./hello.txt");
需要注意的是,构造方法当中指定的字符串一定要合法,合法的体现性为字符串一定要以文件目录的形式开头,例如./某某文件这样的格式。或者D:/***/***这样的格式,需要指定文件的目录。
如果对应的目录下面不存在这个文件,那么会首先创建一个这样的文件出来;
write()方法
跟FileInputStream一样,也是存在多态的
这个write() 方法的操作和read()类似,只是一个输入,一个输出。
需要注意的是,如果程序启动之前,原来的文件当中仍然有内容,那么,第一次write()的时候,会把原来文件当中的内容全部清空。
五、close()方法
这个方法的含义就是关闭当前的"流”。
当进行完对应的读写操作之后,应当及时把这个"流"给关闭掉。
就是调用inputStream.close()或者outputStream.close()。
需要及时关闭"流"的原因:
根据前面的知识,我们了解到,进程当中有一个重要的属性,就是文件描述符表,这样的一个"表"。其实相当于一个数组,记录了进程打开了哪些文件。
一个进程当中的所有线程,共用一个文件描述符表
每次打开文件的操作,就会在文件描述表当中,申请一个位置,把对应文件的信息存放进去。 每一个被打开了的文件,在内核当中,都是一个file_struct对象。
每关闭一个文件,都会把对应的表项释放掉。也就是销毁对应的file_struct对象
而调用fileInputStream.close()或者fileOutputStream.close(),就相当于上面的"释放"操作。
如果没有这个操作,那么会造成什么后果呢?
也就是会让这个file_struct对象一直保留在文件描述符表当中,无法被销毁。这样,会造成极大的内存空间浪费。
并且,当打开的文件数量越来越多的时候,有可能很快就会让这个"数组"被填满了。也就是当需要再次打开文件的时候,已经无法操作了。
虽然Java当中有gc垃圾回收机制,会在inputStream或者outputStream被回收的时候释放资源。但是,如果打开的文件太多,有可能gc也来不及释放了。
六、更加优雅的close()方式
在了解了close()方法的含义之后,可以得出一个结论,就是close()操作一定是在打开文件之后必定进行的一个操作,也就是close()操作是必不可少的。因此,最好把close()操作放在finally代码块当中执行:如下代码:
FileOutputStream outputStream=null;try {outputStream=new FileOutputStream("./hello.txt");outputStream.write(99);}finally {outputStream.close();}
这样,就可以让close()操作一定被执行到了。
但是,Java当中提供了下面的更加"优雅"的关闭方式:
try (FileOutputStream outputStream = new FileOutputStream("./hello.txt")) {outputStream.write(99);outputStream.write(100);outputStream.write(101);outputStream.write(103);}
这样,会在write()完所有的内容之后,自动关闭流,释放资源。
但是,需要注意的是,不是任何一个对象都可以放到try()的括号当中的。
一定要实现closeable接口:
采用Scanner来读取磁盘的内容:
我们通常想从控制台获取输入的时候,会创建Scanner对象:
Scanner scanner=new Scanner(System.in)
其中,System.in就是一个输入流对象:
如果想让Scanner从磁盘当中读取内容,可以考虑这样操作:
public static void main(String[] args) {try (InputStream inputStream=new FileInputStream("./hello.txt")){Scanner scanner=new Scanner(inputStream);//此时读取的内容,就是从hello.txt当中读取的//这里将一次读取全部的内容System.out.println(scanner.next());} catch (IOException e) {e.printStackTrace();}}
那么这样,读取到的内容就是对应hello.txt文件的全部内容了。
同时,inputStream对象也已经被关闭了。,也就意味着scanner也被关闭了。
七、字符流
前面介绍的FileInputStream和FileOutputStream都是字节流,以字节为单位进行读写的。
下面,将介绍字符流:FileReader以及字节流:FileWriter,以字符为单位进行读写的。
public static void main(String[] args) {try (Writer writer=new FileWriter("./hello.txt")){//写入的时候,如果是数字,那么会转化为对应的ASCII码的字母//如果是’单引号的字符,那么会直接写入单引号的字符writer.write('?');writer.flush();} catch (IOException e) {e.printStackTrace();}}
当传入的为一个数字的时候,会把它转换为一个字符来进行读写。
当传入的为一个字符的时候,直接写入一个对应的字符。
flush方法
当直接调用writer.write()方法的时候,其实是写入一个缓冲区当中。
写操作执行完毕之后,内容可能还残留在缓冲区当中,还没有真正进入磁盘当中。
因此,调用flush()方法,可以确保缓冲区当中的内容被同步到磁盘。