文件操作
文件的分类:
- 文本文件:里面存储的是字符,文本文件本质上存储的也是字节,但文本文件中相邻字节在一起正好构成一个字符。
- 二进制文件:里面存储的是字节,字节和字节之间没有什么关系。
Java 中通过 java.io.File 类来对一个文件(包括目录)进行抽象的描述。
注意,有 File 对象,并不代表真实存在该文件。
一. File 类
1. 属性
2. 构造方法
3. 方法
getCanonicalPath() : 简化过后的路径。
比如:
./home/../test 等价于 ./test
代码示例:
- 示例1:观察 get 系列的特点和差异
public static void main(String[] args) throws IOException {
File file = new File("..\\hello-world.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());
}
运行结果:
- 示例2:普通文件的创建
public static void main(String[] args) throws IOException {
File file = new File("hello-world.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.exists()); // true
System.out.println(file.isDirectory()); // false
System.out.println(file.isFile()); // true
System.out.println(file.createNewFile()); // false
}
- 示例3:普通文件的删除
public static void main(String[] args) throws IOException {
File file = new File("some-file.txt"); // 要求该文件不存在,才能看到相同的现象
System.out.println(file.exists()); // false
System.out.println(file.createNewFile()); // true
System.out.println(file.exists()); // true
System.out.println(file.delete()); // true
System.out.println(file.exists()); // false
}
- 示例4:观察 deleteOnExit 的现象
public static void main(String[] args) throws IOException {
File file = new File("some-file.txt"); // 要求该文件不存在,才能看到相同的现象
System.out.println(file.exists()); // false
System.out.println(file.createNewFile()); // true
System.out.println(file.exists()); // true
file.deleteOnExit();
System.out.println(file.exists()); // true
}
文件等到程序退出才被删除,所以调用过 deleteOnExit 再进行判断文件是否存在还是 true,但是我们去查看目录就会发现文件被删了。
- 示例5:目录的创建
public static void main(String[] args) throws IOException {
File dir = new File("some-dir"); // 要求该目录不存在,才能看到相同的现象
System.out.println(dir.isDirectory()); // false
System.out.println(dir.isFile()); // false
System.out.println(dir.mkdir()); // true
System.out.println(dir.isDirectory()); // true
System.out.println(dir.isFile()); // false
}
注意:文件/目录创建出来之前它什么都不是,只是一个 File 对象。
- 示例6:目录创建2
public static void main(String[] args) throws IOException {
File dir = new File("some-parent\\some-dir"); // some-parent 和 some-dir 都不存在
System.out.println(dir.isDirectory()); // false
System.out.println(dir.isFile()); // false
System.out.println(dir.mkdir()); // false
System.out.println(dir.isDirectory()); // false
System.out.println(dir.isFile()); // false
}
mkdir() 的时候,如果中间目录不存在,则无法创建成功; mkdirs() 可以解决这个问题。
public static void main(String[] args) throws IOException {
File dir = new File("some-parent\\some-dir"); // some-parent 和 some-dir 都不存在
System.out.println(dir.isDirectory()); // false
System.out.println(dir.isFile()); // false
System.out.println(dir.mkdirs()); // true
System.out.println(dir.isDirectory()); // true
System.out.println(dir.isFile()); // false
}
mkdirs() 同时创建多级目录。
- 示例7:文件重命名
public static void main(String[] args) throws IOException {
File file = new File("some-file.txt"); // 要求 some-file.txt 得存在,可以是普通文件,可以是目录
File dest = new File("dest.txt"); // 要求 dest.txt 不存在
System.out.println(file.exists()); // true
System.out.println(dest.exists()); // false
System.out.println(file.renameTo(dest)); // true
System.out.println(file.exists()); // false
System.out.println(dest.exists()); // true
}
二. 文件读写
针对文件内容的读写,Java 标准库提供了一组类,按照文件内容分为两个系列:
注意:这些抽象类的实现类有很多,这里只列举了几个常见的。
虽然类有很多,但是使用都是差不多的,这里以 InputStream 和 OutputStream 为例。
1. InputStream
方法
注意: 使用完资源要记住关闭 close()
- 写到 finally 里面, 但是这么写太麻烦了,于是 Java 提供了 try with resources.
- try with resources. 后面的代码中也有体现.
代码:
try (InputStream is = new FileInputStream("input.txt")) {
// do something
}
代码中没有显式调用 close(), 但是 try 会帮我们自动调用,执行完 try 语句块后,自动帮我们调用 close().
注意:想要使用 try with resources 必须实现 Closeable 接口,所有的流对象都实现了这个接口。
2. FileInputStream
InputStream 是一个抽象类,FileInputStream 是 InputStream 的一个实现类。
构造方法
代码示例:
- 示例1:将文件完全读完的两种方式。
一次读取一个字节
public static void main(String[] args) throws IOException {
try (InputStream is = new FileInputStream("hello.txt")) {
while (true) {
int b = is.read();
if (b == -1) {
// 代表文件已经全部读完
break;
}
System.out.printf("%c", b);
}
}
}
一次读取多个字节
public static void main(String[] args) throws IOException {
try (InputStream is = new FileInputStream("hello.txt")) {
byte[] buf = new byte[1024];
int len;
while (true) {
len = is.read(buf);
if (len == -1) {
// 代表文件已经全部读完
break;
}
for (int i = 0; i < len; i++) {
System.out.printf("%c", buf[i]);
}
}
}
}
相比较而言,后一种的 IO 次数更少,性能更好。
- 示例2:文件内容中填充中文并读完。
注意,这里面写中文的时候使用的是 UTF-8 编码。
public static void main(String[] args) throws IOException {
try (InputStream is = new FileInputStream("hello.txt")) {
byte[] buf = new byte[1024];
int len;
while (true) {
len = is.read(buf);
if (len == -1) {
// 代表文件已经全部读完
break;
}
// 每次使用 3 字节进行 utf-8 解码,得到中文字符
// 利用 String 中的构造方法完成
// 这个方法了解下即可,不是通用的解决办法
for (int i = 0; i < len; i += 3) {
String s = new String(buf, i, 3, "UTF-8");
System.out.printf("%s", s);
}
}
}
}
注意:这里利用了这几个中文的 UTF-8 编码后长度刚好是 3 个字节和长度不超过 1024 字节的现状,所以这种方式并不是通用的。
3. 使用 Scanner 进行字符读取
字符类型使用 InputStream 读取非常麻烦且困难,所以,我们使用比较熟悉的Scanner 类完成该工作 。
public static void main(String[] args) throws IOException {
try (InputStream is = new FileInputStream("hello.txt")) {
try (Scanner scanner = new Scanner(is, "UTF-8")) {
while (scanner.hasNext()) {
String s = scanner.next();
System.out.print(s);
}
}
}
}
4. OutputStream
flush():
- 我们知道 I/O 的速度是很慢的。
- 因为一次写一点, 分多次写当然没有一次攒一堆,统一 一次写完更高效,读操作也类似。
- 所以, OutputStream 为减少设备操作次数,在写数据时都会将数据先暂时写入内存的一个指定区域里,直到该区域满了或者其他指定条件时才真正将数据写入设备中,这个区域称为缓冲区。
- 这会造成一个结果,就是我们写的数据,很可能会遗留一部分在缓冲区中。需要在最后或者合适的位置,调用 flush(刷新)操作,将数据刷到设备中。
注意:close 操作也会触发缓冲区刷新。
5. FileOutputStream
OutputStream 是一个抽象类,FileOutputStream是 OutputStream 的一个实现类。
注意:
- 按照写的方式打开文件每次都会清空原本文件的内容,从起始位置开始写。
- 想要使用追加的方式,必须使用带有 boolean append 的构造方法并传入 true。
利用 OutputStream 进行字符写入:
- 代码示例:
单个字节的写
public static void main(String[] args) throws IOException {
try (OutputStream os = new FileOutputStream("output.txt")) {
os.write('H');
os.write('e');
os.write('l');
os.write('l');
os.write('o');
// 不要忘记 flush
os.flush();
}
}
一下写入一个字节数组中的数据
public static void main(String[] args) throws IOException {
try (OutputStream os = new FileOutputStream("output.txt")) {
byte[] b = new byte[] {
(byte)'G', (byte)'o', (byte)'o', (byte)'d'
};
os.write(b);
// 不要忘记 flush
os.flush();
}
}
将字节数组的某个区间写入
public static void main(String[] args) throws IOException {
try (OutputStream os = new FileOutputStream("output.txt")) {
byte[] b = new byte[] {
(byte)'G', (byte)'o', (byte)'o', (byte)'d', (byte)'B',
(byte)'a', (byte)'d'
};
os.write(b, 0, 4);
// 不要忘记 flush
os.flush();
}
}
将一个字符串中的内容写入
public static void main(String[] args) throws IOException {
try (OutputStream os = new FileOutputStream("output.txt")) {
String s = "Nothing";
byte[] b = s.getBytes();
os.write(b);
// 不要忘记 flush
os.flush();
}
}
写入中文
public static void main(String[] args) throws IOException {
try (OutputStream os = new FileOutputStream("output.txt")) {
String s = "你好中国";
byte[] b = s.getBytes("utf-8");
os.write(b);
// 不要忘记 flush
os.flush();
}
}
6. 使用 PrintWriter 进行字符写入
上述已经完成输出工作,但总有所不方便,我们将 OutputStream 处理下,使用 PrintWriter 类来完成输出,因为 PrintWriter 类中提供了我们熟悉的 print/println/printf 方法
public static void main(String[] args) throws IOException {
OutputStream os = new FileOutputStream("output.txt");
OutputStreamWriter osWriter = new OutputStreamWriter(os, "utf-8"); // 告诉它,我们的字符集编码是 utf-8 的
PrintWriter writer = new PrintWriter(osWriter);
// 接下来我们就可以方便的使用 writer 提供的各种方法了
writer.print("Hello");
writer.println("你好");
writer.printf("%d: %s\n", 1, "没什么");
// 不要忘记 flush
writer.flush();
}
好啦! 以上就是对 文件操作 的讲解,希望能帮到你 !
评论区欢迎指正 !