一,认识文件
1.1 树形结构组织和目录
文件是对于"硬盘"数据的一种抽象,在一台计算机上,有非常多的文件,这些文件是通过 "文件系统" 来进行组织的,本质上就是通过 "目录"(文件夹) 这样的树形结构来组织文件的,画个图理解一下:
有了目录,我们就可以使用目录的层次结构来描述文件所在的位置,即 "路径"。如:D:\Program Files (x86)\编程3\Common\VSPerfCollectionTools\vs2022\1033,在这里还有两个概念:
- 绝对路径:以 C:D:盘符开头的,这种路径就是 "绝对路径"。
- 相对路径:需要指定一个目录作为基准目录,从基准目录出发,到达指定的文件,这里的路径就是 "相对路径"。这些路径往往是以 . (代表当前目录) 或者 .. (代表当前目录的上一级目录) 开头的。
1.2 文件类型
文件主要分为两大类:
1)文本文件:文件中保存的数据都是字符串,保存的内容都是合法字符(计算机存储的数据都是二进制的,能通过字符编码将二进制数据转换成字符的就是合法字符)
2)二进制文件:文件中保存的数据是二进制数据,即不是合法的字符
区分文本文件和二进制文件:将文件直接使用记事本打开,如果是乱码,就是二进制文件,如果不是,就是文本文件。
二,文件操作 - FILE
2.1 属性
修饰符及属性 | 属性 | 说明 |
static String | pathSeparator |
依赖于系统的路径分隔符,String 类型的表示
|
static char | pathSeparator |
依赖于系
统的路径分隔符,String 类型的表示
|
E:\01\MSDN 中的 \ 就是 pathSeparator,如果当前的系统是 Windows,\ 或者 / 都可以作为分隔符,如果系统是 Linux 或 Mac ,只能使用 / 作为分隔符,一般建议使用 / 作为分隔符,因为 \ 一般还需要搭配转义字符来使用。
2.2 构造方法
构造方法 | 说明 |
File(File parent, String child)
|
根据父目录
+
孩子文件路径,创建一个新的
File
实例
|
File(String pathname)
|
根据文件路径创建一个新的
File
实例,路径可以是绝对路径或者 相对路径
|
File(String parent, String child)
|
根据父目录
+
孩子文件路径,创建一个新的
File
实例,父目录用路径表示
|
2.3 方法
返回值类型 | 方法名 | 说明 |
String | getParent() |
返回
File
对象的父目录文件路径
|
String | getName() |
返回
FIle
对象的纯文件名称
|
String | getPath() |
返回
File
对象的文件路径
|
String | getAbsolutePath() |
返回
File
对象的绝对路径
|
String | getCanonicalPath() |
返回
File
对象的修饰过的绝对路径
|
boolean | exits() |
判断
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
| canWirte() |
判断用户是否对文件有可写权限
|
public class Demo {
public static void main(String[] args) throws IOException {
File file = new File("./text.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());
}
}
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
public class Demo1 {
public static void main(String[] args) throws IOException {
File file = new File("d:/text.txt");
System.out.println(file.exists());//false
System.out.println(file.isDirectory());//false
System.out.println(file.isFile());//false
System.out.println(file.createNewFile());//true
System.out.println(file.delete());//true
//file.deleteOnExit();在程序全部执行完之后删除文件
File file1 = new File("d:/");
String[] ret = file1.list();
System.out.println(Arrays.toString(ret));
File file2 = new File("d:/aaa/bbb/ccc");
boolean ans = file2.mkdirs();//能创建多级目录
//file2.mkdir();只能创建一级目录,如 d:/aaa
System.out.println(ans);
}
}
三,文件内容读写 - 数据流
数据流根据文件类型也分成了两种:
1)字节流:对应二进制文件,每次读写的最小单位是 "字节"
2)字符流:对应文本文件,每次读写的最小单位是 "字符",英文的字符都是一个字节,一个汉字在不同的字符编码中是不同点大小,在 utf8 是 3 个字节,在 unicode 是 2 个字节。(字符流本质上是针对字节流进行的一层封装)
JAVA针对读写两种操作,分别为字节流提供了 InputStream(输入) 和 OutputStream(输出) 类,为字符流提供了 Reader(输入) 和 Writer(输出) 类。这里有一个注意点,如何区分输入和输出,画个图:
3.1 字符流 - Reader
返回值类型 | 方法名 | 说明 |
int | read() | 从文件中读取一个字符,返回unicode编码 |
int | read(char[] cbuf) | 从文件中读取若干字符,将cbuf数组填满,返回实际读取的字符数 |
int | read(chae[] cbuf, int off, int len) | 从文件中读取作干字符,从off下标开始,长度为len的cbuf数组填满,返回实际读取的字符数 |
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
public class Demo3 {
public static void main(String[] args) throws IOException {
//一次读一个字符
Reader reader = new FileReader("d:/text.txt");//打开文件
while(true){
int n = reader.read();//读取一个字符
if(n == -1){//返回-1表示文件读取完毕
break;
}
char ch = (char) n;
System.out.println(n);
}
reader.close();
}
}
但是这么写还是可能会出现文件资源泄露,如果在while循环中抛出异常,下面的close()方法就执行不到了,所以我们可以使用 try...finally..来实现:
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
public class Demo3 {
public static void main(String[] args) throws IOException {
//一次读多个字符
Reader reader = new FileReader("d:/text.txt");//打开文件
try{
while(true){
char[] ret = new char[10];
int n = reader.read(ret);
if(n == -1) break;
for (int i = 0; i < n; i++) {
System.out.println(ret[i]);
}
}
}finally {
reader.close();//关闭操作
}
}
}
这么写虽然解决了问题,但是不够方便,在这里还有一种写法:
public class Demo3 {
public static void main(String[] args) throws IOException {
//只有实现closeable接口才可以这样写(流对象都可以)
try(Reader reader = new FileReader("d:/text.txt")){
while(true){
char[] ret = new char[10];
int n = reader.read(ret);
if(n == -1) break;
for (int i = 0; i < n; i++) {
System.out.println(ret[i]);
}
}
}
}
}
3.2 字符流 - Writer
方法名 | 说明 |
write(int c) | 一次写一个字符 |
write(String str) | 一次写多个字符 |
write(char[] cbuf) | 一次写多个字符,使用字符数组 |
write(String str, int off, int len) | 从下标off开始往文件中写入,长度为len |
write(char[] cbuf, int off, int len) | 从下标off开始往文件中写入,长度为len |
注:默认情况下,写入文件会将文件中的原有内容清空。
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class Demo4 {
public static void main(String[] args) {
try(Writer writer = new FileWriter("d:/text.txt")) {
writer.write("原神,启动!");//写入,先清空再写入
} catch (IOException e) {
throw new RuntimeException(e);
}
/* 在构造方法参数中加一个 true , 就可以直接在文件后面填写,不需要清空
try(Writer writer1 = new FileWriter("d:/text.txt",true)) {
writer1.write("原神,启动!");//写入
} catch (IOException e) {
throw new RuntimeException(e);
}*/
}
}
3.3 字节流 - InputStream
返回值类型 | 方法名 | 说明 |
int | read() |
读取一个字节的数据,返回
-1
代表已经完全读完了
|
int | read(byte[] b) |
最多读取
b.length
字节的数据到
b
中,返回实际读到的数量;-1
代表以及读完了
|
int |
read(byte[] b, int off, int len)
|
最多读取
len - off
字节的数据到
b
中,放在从
off
开始,返回实际读到的数量;-1
代表以及读完了
|
void |
close()
|
关闭字节流
|
import java.io.*;
public class Demo5 {
public static void main(String[] args) {
try(InputStream inputStream = new FileInputStream("d:/text.txt")) {
byte[] buffer = new byte[10];
while (true){
int n = inputStream.read(buffer);
if(n == -1) break;
for (int i = 0; i < n; i++) {
System.out.printf("%x\n",buffer[i]);
}
}
}catch (IOException e) {
throw new RuntimeException(e);
}
}
}
3.4 字节流 - OutputStream
返回值类型 | 方法名 | 说明 |
void | write() |
写入要给字节的数据
|
void | write(byte[] b) |
将
b
这个字符数组中的数据全部写入
|
int |
write
(byte[] b, int off, int len)
|
将
b
这个字符数组中从
off
开始的数据写入
,一共写
len
个
|
void |
close()
|
关闭字节流
|
void | flush() |
大多的
OutputStream
为 了减少设备操作的次数,在写数据的时候都会将数据先暂时写入内存的 一个指定区域里,直到该区域满了或者其他指定条件时才真正将数据写 入设备中,这个区域一般称为缓冲区。但造成一个结果,就是我们写的 数据,很可能会遗留一部分在缓冲区中。需要在最后或者合适的位置, 调用 flush
(刷新操作,将数据刷到设备中。
|
import java.io.*;
public class Demo5 {
public static void main(String[] args) {
try(OutputStream outputStream = new FileOutputStream("d:/text.txt",true)){
String s = "哈哈哈哈";
outputStream.write(s.getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
3.5 字节流转字符流
当别人传给你的是一个字节流文件,但是你知道实际数据内容是文本数据时,我们可以通过以下方法来实现转换:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class Demo6 {
public static void main(String[] args) {
try (InputStream inputStream = new FileInputStream("d:/text.txt")){
Scanner scanner = new Scanner(inputStream);
String s = scanner.next();
System.out.println(s);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
import java.io.*;
public class Demo7 {
public static void main(String[] args) {
try(OutputStream outputStream = new FileOutputStream("d:/text.txt")){
PrintWriter writer = new PrintWriter(outputStream);
writer.println("fsaf");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
因为 PrintWriter 这个类,在进行写入操作的时候,不一定时直接写入硬盘,而是先把数据写入一个内存中的空间,叫做 "缓冲区"。为什么会出现缓冲区?因为把数据写入内存,是非常快的,而把数据写入硬盘,是非常慢的(比内存慢几千倍甚至更多),为了提高效率,我们选择降低写硬盘的次数。这样就会出现问题,我们将数据写入 "缓冲区" 后,还没有将缓冲区的数据写入硬盘,进程就结束了,此时数据就丢失了,也就会出现上述图片中的问题。
为了解决该问题,确保数据能完整的写入硬盘,我们需要手动的用 flush() 方法刷新缓冲区:
import java.io.*;
public class Demo7 {
public static void main(String[] args) {
try(OutputStream outputStream = new FileOutputStream("d:/text.txt")){
PrintWriter writer = new PrintWriter(outputStream);
writer.println("fsaf");
writer.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}