Java文件IO操作
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
目录
一、了解什么是文件
文件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");
//输出true
System.out.println(file.isDirectory());
//输出false
System.out.println(file1.isDirectory());
⑤file.createNewFile在指定的目录创建一个文件如果创建成功返回true否则false
//test为一个txt文本文档
File file1=new File("./test1.txt");
//创建成功返回true,创建失败返回false
System.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()方法可以确保缓冲区当中的内容被同步到磁盘。