java-文件IO
文件IO
操作系统有一个专门的模块-文件系统,来管理文件。并提供了 api 供我们使用。
文件系统 使用 N叉树 来组织文件。
操作系统使用 “路径” 来描述文件的位置。路径的表示又分为 绝对路径 和 相对路径
绝对路径 :从树的的根节点(Windows 下的根节点是盘符,即 C、D 之类的)开始,一层一层的到目标文件;
相对路径 :从 当前目录(也称为 工作目录/基准目录)出发,到目标文件;
例如:D:/steam/appcache/httpcache 该路径即为 此 httpcache 文件的 绝对路径 。
D:/steam/appcache 如果这是当前目录,则此 bin 文件的 相对路径 为 ./bin . 表示是当前目录。
D:/steam/appcache/librarycache 如果这是当前目录,则此 bin 文件的 相对路径 为 …/httpcache … 表示上层目录。可以进行多个上次目录操作。
路径中 优先使用 / ,写作 \ 在代码还需进行转义。
文件分为 文本文件 和 二进制文件
文本文件, 是以 字符编码(如 ASCII、UTF-8 等) 存储数据的文件,(文件中的数据均在码表上可查)
二进制文件, 是以 二进制数据(0 和 1 的序列) 存储信息的文件
通过 记事本 打开文件,可以对文件是 文本文件 还是 二进制文件 进行判断。(乱码对应 二进制文件;反之,即为 文本文件 )
文本文档、.c 、.java、.cpp 等都是 文本文件
图片、音频、视频、可执行程序、动态库 等都是 二级制文件
常用的 .docx(word文档)、.slsx(excel)、 .pptx(PPT) 是 富文本 ,是二进制文件。
java 对文件系统的操作 API 主要分为以下两类:
① 针对文件系统的操作
包括但不限于 创建文件、删除文件、重命名文件、列出目录内容……
② 针对文件内容的操作
读写文件
java中使用 File 类(位于 java.io 包下)对文件系统进行操作
File 属性,构造方法,方法
属性
修饰符及类型 | 属性 | 说明 |
---|---|---|
static String | pathSeparator | 依赖于系统的路径分隔符,String 类型的表示 |
static char | pathSeparator | 依赖于系统的路径分隔符,char 类型的表示 |
路径里的分隔符,根据操作系统的不同会自行调整。这里建议直接使用 /
构造方法
构造方法 | 说明 |
---|---|
File(File parent, String child) | 根据父目录 + 孩子文件路径,创建一个新的 File 实例 |
File(String pathname) | 根据文件路径创建一个新的 File 实例,路径可以是绝对路径或者相对路径。 |
File(String parent, String child) | 根据父目录 + 孩子文件路径,创建一个新的 File 实例,父目录用路径表示 |
File(String pathname) 最常用,除了相对路径和绝对路径外,这里的路径还可以是不存在的。写作相对路径时,①如果是在 idea 中运行,此时的工作目录为项目所在的目录。②如果是把代码打包成一个独立的 jar 包,此时工作目录是 jar 包所在的目录。
方法
返回值类型 | 方法 | 说明 |
---|---|---|
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() | 判断用户是否对文件有可写权限 |
部分常见方法的使用示例:
public class Test {public static void main(String[] args) throws IOException {//File file = new File("C:/Users/lenovo/desktop/test.txt");File file = new File("./test.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;public class Test {public static void main(String[] args) throws IOException {File f = new File("./test.txt");System.out.println(f.exists());System.out.println(f.isFile());System.out.println(f.isDirectory());System.out.println(f.createNewFile());}
}
public class Test{public static void main(String[] args) throws InterruptedException {File file = new File("./test.txt");//file.delete();file.deleteOnExit();//程序退出后删除Thread.sleep(3000);}
}
退出时再删除,这样的称为 “临时文件”。就像用 word 打开文件后,桌面上会出现一个虚的文件(在文件资源管理器中勾选显示隐藏文件,才能看见)。主要作用是应对·正在编辑的内容,没有保存,而意外退出(如:电脑突然断电)的情况,重新供电后这时临时文件依然存在,借助这个临时文件就可以恢复之前未保存的数据。就像word 中的自动保存。
public class Test {public static void main(String[] args) {File file = new File(".");//获取目录下文件文件名//String[] files = file.list();//获取目录的文件File[] files = file.listFiles();System.out.println(Arrays.toString(files));}
}
import java.io.File;public class Test {public static void main(String[] args) {File file = new File("./aaa/bbb/ccc"); //只有一个目录//boolean ret = file.mkdir();//一连串目录boolean ret = file.mkdirs();System.out.println(ret);}
}
import java.io.File;public class demo6_0525 {public static void main(String[] args) {File file1 = new File("./test2.txt");//File file2 = new File("./test2.txt");File file2 = new File("./aaa/test.txt");file1.renameTo(file2);}
}
除了重命名外,还可以用于更换文件的路径。
对文件内容的操作
通过 流 来对文件内容进行操作,
流就像水流一样,以接水为例:
接一杯1000ml水,可以一次接 1000ml;
亦可以分两次,一次 500ml;
也可以分五次,一次 200ml;
……
对文件内容的读写,和着类似;
1000字节的内容,也可以分多次读写;
流又分为 字节流 和 字符流
字节流 以字节为单位进行读写,一次最少读写一个字节。
代表类 InputStream 、OutputStream
字符流 以字符为单位进行读写,比如,如果是 utf8 表示汉字,3字节就是 1 汉字,每次读写以三字节为单位进行,不能一次读半个汉字。
代表类 Reader 、Writer
注 :这里的读与写是相对于 CPU 而言的,假设你坐在 CPU 上,朝你来的 是 读 ;离你去的是 写 ;
InputStream 概述
方法
返回值类型 | 方法 | 说明 |
---|---|---|
int | read() | 读取一个字节的数据,返回 -1 代表已经完全读完了 |
int | read(byte[] b) | 尽量将 b 填满,返回实际读到的数量;-1 代表以及读完了 |
int | read(byte[] b, int off, int len) | 从 offset 下标开始填,尽可能填 len 个,返回实际读到的数量;-1 代表以及读完了 |
void | close() | 关闭字节流 |
观察上图我们发现 读到的数据(不是数据个数)就是 [0-255] 刚好是 1byte ,但是为什么这里使用 int 作为返回值呢?
原因:
1)保障能处理读完情况时,有余地表示 -1
2) 保障读到的数据都是正数,byte 类型有符号,使用 int 可以确保读出的数据都是正数
3) 虽然 short 类型也可以,甚至比 int 少了两个字节,但是建议使用 int ,就像建议使用 double 来代替 float
文件关闭 close
打开文件,就会在系统内核 PCB 结构体中,给 “文件描述符表” 添加一个新元素,记录当前打开的文件的相关信息。close() 就会释放对应元素。如果一直只是打开文件,而不关闭就会使 文件描述符表 被占满,后续打开文件就会失败。这种情况类似 内存泄漏 ,称为 文件资源泄露 。 而且,在表未用完之前是不会出现问题的,很难发现,就像是 随时可能爆炸的雷。
说明
InputStream 只是一个抽象类,要使用还需要具体的实现类。关于 InputStream 的实现类有很多,基本可以认为不同的输入设备都可以对应一个 InputStream 类,我们现在只关心从文件中读取,所以使用 FileInputStream
FileInputStream 概述
构造方法
签名 | 说明 |
---|---|
FileInputStream(File file) | 利用 File 构造文件输入流 |
FileInputStream(String name) | 利用文件路径构造文件输入流 |
代码示例:
public class Test {public static void main(String[] args) throws IOException {InputStream inputStream = null;//inputstream 放在这里是为了能在 finally 中关闭。//放在 try 里面作用域仅在 try 代码块中,finally 够不到try {inputStream = new FileInputStream("./test.txt");} finally {inputStream.close();}}
}
inputstream 放在 try 是为了能在 finally 中关闭, 放在 try 里面作用域仅在 try 代码块中,finally 够不到。(避免忘记 close)
public class Test {public static void main(String[] args) throws IOException {try (InputStream inputStream = new FileInputStream("./test.txt")) {while (true) {int b = inputStream.read();if (b == -1) {break;}System.out.printf("%x ", b);}}}
}
除了在 close 放在 finally 里,还可以将 关闭操作 交给 try ,当出了 try 的 {} 后会自动 close 。
但是前提是实现了 closable 接口。
上面是一个字节一个字节的读
读入 byte 数组
public class Test {public static void main(String[] args) throws IOException {try (InputStream inputStream = new FileInputStream("./test.txt")) {while (true) {byte[] b = new byte[1024];int n = inputStream.read(b);if (n == -1) {break;}for (int i = 0; i < n; i++) {System.out.printf("%x ",b[i]);}}}}
}
这里是将 b 中元素一个一个地打印出来
下面是转成 String 进行打印
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;public class Test {public static void main(String[] args) {try (InputStream inputStream = new FileInputStream("./test.txt")){while (true) {byte[] buffer = new byte[1024];int n = inputStream.read(buffer);if (n == -1) {break;}String s = new String(buffer,0,n);System.out.println(s);}} catch (IOException e) {throw new RuntimeException(e);}}
}
String s = new String(buffer,0,n); 第一个参数为要转的数组,第二个表示从 该下标 开始为转成 String ,第三个为截止下标 (左闭右开型 [参2,参三 ) )
OutputStreaam 概述
方法
返回值类型 | 方法 | 说明 |
---|---|---|
void | write(int b) | 写入给出的字节的数据 |
void | write(byte[] b) | 将 b 这个字符数组中的数据全部写入 文件 中 |
int | write(byte[] b, int off, int len) | 将 b 这个字符数组中从 off 开始的数据写入文件 中,一共写 len 个。 |
void | close() | 关闭字节流 |
void | flush() | 重要:我们知道 I/O 的速度是很慢的,所以,大多的 OutputStream 为了减少设备操作的次数,在写数据的时候都会将数据先暂时写入内存的一个指定区域里,直到该区域满了或者其他指定条件时才真正将数据写入设备中,这个区域一般称为缓冲区。但造成一个结果,就是我们写的数据,很可能会遗留一部分在缓冲区中。需要在最后或者合适的位置,调用 flush(刷新)操作,将数据刷到设备中。 |
说明
OutputStream 同样只是一个抽象类,要使用还需要具体的实现类。我们现在还是只关心写入文件中,所以使用 FileOutputStream
代码示例:
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;public class demo9_0526 {public static void main(String[] args) {try (OutputStream outputStream = new FileOutputStream("./test.txt")) {byte[] buffer = new byte[] {97,98,99,100,101,102};outputStream.write(buffer);} catch (FileNotFoundException e) {throw new RuntimeException(e);} catch (IOException e) {throw new RuntimeException(e);}}
}
直接这样,每次 new 出来是都会清空对应文件的内容。
将 FileOutputStream 第二个参数给 true ,就不会清空了,而是在内容结尾,进行追加。
Reader 和 Writer 和 InputStream 和 OutputStream 类似,不再做具体说明。看一下代码示例:
Reader
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;public class Test {public static void main(String[] args) {try (Reader reader = new FileReader("./test.txt")){while (true) {char[] buffer = new char[1024];int n = reader.read(buffer);if (n == -1) {break;}String s = new String(buffer,0,n);//System.out.println(s);for (int i = 0; i < n; i++) {System.out.print(buffer[i]);System.out.print(" ");}}} catch (IOException e) {throw new RuntimeException();}}
}
Writer
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;public class demo11_0527 {public static void main(String[] args) {try (Writer writer = new FileWriter("./test.txt",true)) {String s = "你好!!!";writer.write(s);} catch (IOException e) {throw new RuntimeException(e);}}
}
Scanner
使用 Scanner 也可以实现读文件
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;public class Test{public static void main(String[] args) {try (InputStream inputStream = new FileInputStream("./test.txt")){//传一个文件输入流Scanner scanner = new Scanner(inputStream);while (scanner.hasNext()) {String s = scanner.next();System.out.println(s);}} catch (FileNotFoundException e) {throw new RuntimeException(e);} catch (IOException e) {throw new RuntimeException(e);}}
}
完整使用示例
T1 : 在某个目录下查找文件
import java.io.File;
import java.util.Scanner;public class Test {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);System.out.println("请输入要搜索的文件名:");String filename = scanner.next();System.out.println("请输入要搜索的目录:");String rootPath = scanner.next();File rootfile = new File(rootPath);if (!rootfile.isDirectory()) {System.out.println("路径有误");return;}scanDir(rootfile,filename);}private static void scanDir(File rootfile, String filename) {File[] files = rootfile.listFiles();if (files == null) {return;}for (File f : files) {System.out.println("当前遍历到:" + f.getAbsoluteFile());if (f.isFile()) {if (filename.equals(filename)) {System.out.println("找到了");}} else if (f.isDirectory()) {scanDir(f,filename);} else {}}}
}
T2:文件复制
import java.io.*;
import java.util.Scanner;public class Test {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);System.out.println("请输入要复制的源文件: ");//E:\vs背景\风景.jpgString srcPath = scanner.next();System.out.println("请输入要复制的目标文件: ");//E:\vs背景\风景1.jpgString destPath = scanner.next();File file = new File(srcPath);if (!file.isFile()) {System.out.println("源文件路径有误!");return;}File destFile = new File(destPath);if (!destFile.getParentFile().isDirectory()) {System.out.println("目标路径有误!");return;}try (InputStream inputStream = new FileInputStream(srcPath);OutputStream outputStream = new FileOutputStream(destFile)){while (true) {byte[] buffer = new byte[1024];int n = inputStream.read(buffer);if (n == -1) {break;}outputStream.write(buffer,0,n);}} catch (FileNotFoundException e) {throw new RuntimeException(e);} catch (IOException e) {throw new RuntimeException(e);}}
}
T3:找出包含指定内容的文件
import java.io.*;
import java.util.Scanner;public class Test {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);System.out.println("请输入要搜索的路径: ");String rootPath = scanner.next();System.out.println("请输入要查询的词: ");String word = scanner.next();File rootFile = new File(rootPath);if (!rootFile.isDirectory()) {System.out.println("输入的要搜索的路径不正确!");return;}scanDir(rootFile, word);}public static void scanDir(File rootfile, String word) {File[] files = rootfile.listFiles();if (files == null) {return;}for (File f : files) {System.out.println("【debug】当前遍历到了" + f.getAbsoluteFile());if (f.isFile()) {searchInFile(f,word);} else if (f.isDirectory()) {scanDir(f,word);} else {}}}private static void searchInFile(File f, String word) {try (InputStream inputStream = new FileInputStream(f)){StringBuffer stringBuffer = new StringBuffer();while (true) {byte[] buffer = new byte[1024];int n = inputStream.read(buffer);if (n == -1) {break;}String s = new String(buffer,0,n);stringBuffer.append(s);}System.out.println("[debug] 文件内容: " + stringBuffer);if (stringBuffer.indexOf(word) == -1) {return;}System.out.println("找到了" + word + ":" + f.getAbsoluteFile());} catch (FileNotFoundException e) {throw new RuntimeException(e);} catch (IOException e) {throw new RuntimeException(e);}}
}