狭义上的文件,指的是硬盘这样的持久化存储的I/O设备。文件除了能打开的数据内容等,还有一部分的信息,比如文件名、文件类型等,这些部分不作为文件的数据,我们将其看成文件的元信息。
当文件越来越多的时候,对文件需要进行系统的管理。按照层级结构进行组织,也就是数据结构中的树形结构,称为文件夹或者目录。
要在文件系统中找到我们需要的文件,就需要谈到文件的路径。在树形结构中,树的每一个结点都可以从根开始,一直到结点处。这些的距离称为文件的绝对路径。如果是从当前的位置到达结点处,这样的路径就是相对的路径。
文件中的数据保存经常被分成不同的类型。一般简单的分成文本文件和二进制文件,分别指代保存被字符集编码的文本和按照标准格式保存的非被字符集编码过的文件。文本文件中,想要正确的显示,就需要字符集,比如utf-8、GBK这样的字符编码。
文件由于被操作系统进行了管理,所以根据不同的用户,会赋予用户不同的对待该文件的权限,一般地 可以认为有可读、可写、可执行权限。
Java对文件进行了封装,封装成File类。
Java官方的文档:
File (Java Platform SE 8 ) (oracle.com)https://docs.oracle.com/javase/8/docs/api/index.html
在idea的src文件夹下,使用File类的构造方法来创建文件。创建文件之前,确保要创建的文件是不存在的,观察效果。
public static void main(String[] args) throws IOException {
File file = new File("hello.txt");
boolean flg = file.createNewFile();
System.out.println(flg);
}
使用java官方的方法,来查看该文件的一些属性,比如文件是不是存在,文件的路径等。更多的方法,只需要查阅Java的文档就可以了。
public static void main(String[] args) throws IOException {
File file = new File("hello.txt");
//该方法已经创建出了一个文件,再次调用返回false
boolean flg = file.createNewFile();
System.out.println(flg);
//文件名
System.out.println(file.getName());
//文件相对路径
System.out.println(file.getPath());
//文件绝对路径
System.out.println(file.getAbsolutePath());
//文件是否存在--true
System.out.println(file.exists());
//是不是目录文件--false
System.out.println(file.isDirectory());
}
public static void main(String[] args) throws IOException {
File file = new File("hello.txt");
//删除文件
boolean flg = file.delete();
System.out.println(flg);
}
读取文件,有两种方式:按照字节流读取文件和按照字符流读取文件。按照字节流就是以字节为单位的流,操作二进制文件。使用的接口是InputStream。使用字符流,是以字符为单位的流,操作文本文件,使用的类是Reader类。
在进行读取文件的操作之前,先在项目下创建一个test.txt文件,文件内容是hello world。
InputStream是一个抽象类,要使用还需要具体的实现类,不同的输入设备对应一个InputStream类。这里主要是从文件中读取,使用FileInputStream类。
public static void main(String[] args) {
//抽象类实例化出对象
InputStream inputStream = null;
try {
//向上转型
inputStream = new FileInputStream("test.txt");
//读取文件的read方法,无参数.返回的类型是int,实际上是byte.
//读取到EOF(文件的末尾),返回-1结束.正常情况下范围是0-255
while (true) {
int b = inputStream.read();
if (b == -1) {
break;
}
System.out.print(b);
}
System.out.println();
} catch (IOException e) {
e.printStackTrace();
} finally {
//要关闭文件,否则造成内存泄漏
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
读取来的结果是数字。这个时候,需要转换一下就行了。
使用一个数组来保存输入的字节,然后再输出到控制台。
public static void main(String[] args) {
//抽象类实例化出对象
InputStream inputStream = null;
try {
//向上转型
inputStream = new FileInputStream("test.txt");
//接收字节
byte[] b = new byte[1024];
int len = inputStream.read(b);
for (int i = 0; i < len; i++) {
System.out.print((char)b[i] + " ");
}
System.out.println();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
使用字符串来更加方便的转换。
//使用String来转换
public static void main(String[] args) {
//抽象类实例化出对象
InputStream inputStream = null;
try {
//向上转型
inputStream = new FileInputStream("test.txt");
//接收字节
byte[] b = new byte[1024];
int len = inputStream.read(b);
//转换成字符串
String string = new String(b, 0, len, "utf8");
System.out.println(string);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
String类的该构造方法可以去Java的官方文档中查询。
上面的三种捕获异常的写法写起来让代码看起来不美观而且写起来有点麻烦。美观的写法是使用try的带括号的形式。这种形式不需要手动关闭,会自动调用close方法。不过能这样写的前提是该方法要实现Closeable接口。实际写法中经常使用这样的写法。
//try () {}的写法
public static void main(String[] args) {
try (InputStream inputStream = new FileInputStream("test.txt")){
byte[] b = new byte[1024];
int len = inputStream.read(b);
//转换成字符串
String string = new String(b, 0, len, "utf8");
System.out.println(string);
} catch (IOException e) {
e.printStackTrace();
}
}
上面的InputStream类是以字节为单位操作二进制的文件。但是处理器文本比较的麻烦。Reader类是使用字符为单位的流,操作文本文件比较方便。Reader类是一个抽象类,需要进行向上转型,使用FileReader类。构造方法和FileInputStream类类似。使用的方法也相同。
//Reader
public static void main(String[] args) throws IOException {
Reader reader = new FileReader("test.txt");
char[] buffer = new char[1024];
int len = reader.read(buffer);
for (int i = 0; i < len; i++) {
//不需要转换
System.out.println(buffer[i]);
}
reader.close();
}
我们在输入输出的时候,最常见的类就是Scanner,也可以使用Scanner来读取文件,输出到控制台上。
这样的话读取就更加的方便。
public static void main(String[] args) throws IOException {
InputStream inputStream = new FileInputStream("test.txt");
Scanner scanner = new Scanner(inputStream);
String string = scanner.nextLine();
System.out.println(string);
inputStream.close();
}
写入文件和读取文件类似,都有按照字节流和字符流进行写入的方式。这里先介绍字节流的输入,在介绍字符流的输入。具体的方法使用请查阅官方的文档。
FileOutputStream (Java Platform SE 8 ) (oracle.com)https://docs.oracle.com/javase/8/docs/api/index.html
关于flush方法,需要注意的是,I/O的速度是很慢的,大多数的写入流为了减少设备的操作次数,会把数据暂时写入内存的一个指定的区域缓冲区。一直到写满了缓冲区才真正的将数据写入。所以,会造成一部分留在了缓冲区。需要flush方法把数据刷新到设备中。
和InputStream类类似,OutputStream也是一个抽象类。要使用的话需要具体的类FileOutputStream类,完成向上转型。
继续使用hello.txt文件。在使用前,先清空文件中的数据。每一次调用main方法,都会清空文件中的数据并且重新写入。
public static void main(String[] args) {
try (OutputStream outputStream = new FileOutputStream("hello.txt")) {
outputStream.write('h');
outputStream.write('e');
outputStream.write('l');
outputStream.write('l');
outputStream.write('o');
} catch (IOException e) {
e.printStackTrace();
}
}
更加简单的方法:
//转成字节类型
public static void main(String[] args) {
try (OutputStream outputStream = new FileOutputStream("hello.txt")) {
String string = "hello world";
outputStream.write(string.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
面向字符流的写入文件的方法,比起面向字节流的方法,要简单一些。
public static void main(String[] args) {
try (Writer writer = new FileWriter("hello.txt")) {
writer.write("hello world");
} catch (IOException e) {
e.printStackTrace();
}
}
使用熟悉的 println 之类的方法写入文件中。
public static void main(String[] args) {
try (OutputStream outputStream = new FileOutputStream("hello.txt")) {
PrintWriter printWriter = new PrintWriter(outputStream);
//类似于System.out.println();
printWriter.println("hello world");
} catch (IOException e) {
e.printStackTrace();
}
}
调用main方法后,发现文件中没有结果。
这个需要使用flush方法来刷新缓冲区,关于flush方法在前面有说明。