本文章讲述了通过java对文件进行IO操作
IO:input/output,输入/输出.
建议配合文章末尾实例食用
目录
文件
针对硬盘来说,会将存储的数据分成一个一个独立的单位进行保存,这个独立的单位就被抽象的称为"文件".
我们平时的文件都是存储在硬盘当中的.
文件的管理
随着存储的数据增多,对于这些数据也会进行一定的管理.
对于文件,我们会进行层级管理,也就是数据结构中的树形结构.就这样,出现了一种管理文件的特殊文件:目录(directory)也就是俗称的文件夹.
文件的路径
路径(path)是一个文件在系统中的具体位置.
从树形结构来看,就是由上到下的从一个根节点开始描述到目标文件.
**路径的表示:**从盘符开始,用"\"或"/"分割开(在Windows中一般都从"此电脑"开始,表示的时候可以将其忽略)
路径根据"基准目录"的不同,又可以分为:
- 绝对路径:从盘符开始往下遍历直到目标文件,所得到的路径
- 相对路径:从指定的目录开始往下遍历直到目标文件,所得到的路径
C:\Program Files\Java\jdk1.8.0_192(绝对目录)
\Java\jdk1.8.0_192(相对目录)
在windows系统上,一个文件的路径是绝对唯一的,可以说文件与路径一一对应的.
文件的分类
根据所保存的数据的不同,文件可以被分为:
二进制文件:按照标志格式保存的没被字符集编码过的文件
文本文件:存储被字符集编码过的文本
文件系统的操作
在JAVA的标准库当中,提供了File这个类.
此处的File实例化出的对象是硬盘上一个文件或目录的抽象表示.
File类的构造方法
构造方法中的路径指定的文件可以是存在的,也可以是不存在的.
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| File(
Fileparent,
String child)
通过父亲抽象路径名与子抽象路径名创建一个File实例 |
| File(
String pathname)
通过转换传输的路径名字符串转化成为抽象路径名来创建File实例 |
| File(
Stringparent,
String child)
从父路径名字符串和子路径名字符串创建新的 File实例。 |
| File(
URI uri)
通过将给定的 file: URI转换为抽象路径名来创建新的 File实例。 |
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() | 判断用户是否对文件有可写权限 |
如果放入的是绝对路径
java
public static void main(String[] args) throws IOException {
File file = new File("D:\\tools\\TMP");
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());
}
放入的是相对路径.
当调用打印绝对路径与相对路径的时候会根据当前所在的工作目录创建.
这时候,文件是并不存在的.调用createNewFile()才会创建新的文件.
java
File file = new File(".\\TMP");
文件内容的读写
针对文件内容的操作,会使用到"流"来操作.
流,从字面意思上可以看作为"水流",是可以随心所读的读写的.对于读取数据,可以一次读取1个字节,5个字节,10个字节也可以一次性读完100个字节.相反,对于写入数据来说,也是一样的.这些数据不会被限制一次只能读取一定量或被捆绑在一起.
因此,就把读写文件相关的对象,成为"流对象".
在java标准库的流对象,从类型上可以分成两大类:
- 字节流对象:InputStream,OutputStream...(可以读取文本文件与二进制文件)
- 字符流对象:Reader,Writer...(只能读取文本文件)
在此处的输入与输出,是以CPU为基准的:
Input是从CPU向硬盘输入,达到读取文件的效果.
Output是从CPU向硬盘输出,达到写入文件的效果.
FileInputStream读取文件
上面介绍的字节流对象的InputStream只是一个抽象类,对于文件的IO来说.使用其中一个实现类FileInputStream就可以了
构造方法
|------------------------------|-----------------|
| 方法 | 说明 |
| FileInputStream(File file) | 利用 File 构造文件输入流 |
| FileInputStream(String name) | 利用文件路径构造文件输入流 |
java
//构造方法1
File file = new File("D:/tools/TMP/book.txt");
FileInputStream inputStream = new FileInputStream(file);
//构造方法2
FileInputStream inputStream1 = new FileInputStream("D:/tools/TMP/book.txt");
常用方法
|-------------|------------------------------------|------------------------------------------------------------|
| 修饰符及 返回值类 型 | 方法 | 说明 |
| 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() | 关闭字节流 |
无参数read
java
while(true){
int n = inputStream.read();
if(n == -1){
break;
}
System.out.printf("%c",n);//%c是字符型格式符,以字符为单位进行输出.直接输出会得到的是ASCII码
}
inputStream.close();//释放资源
byte[]参数read**(更推荐使用此方法)**
带有数组参数的read能够极大的减少对文件的IO次数,减少了发生异常的可能.
java
byte[] buf = new byte[1024];//数组的长度为一次读取的字节数
while(true){ //数组的长度不能一次读完字节,下一次的读取就会覆盖上一次的数据
int len;
len = inputStream.read(buf);
if(len == -1){
break;
}
for(int i = 0; i < len; i++){
System.out.println(buf[i]);
}
}
inputStream.close();//释放资源
Scanner读取
将FileInputStream套上一个Scanner更方便的操作
|-----------------------------------------|---------------------------|
| 构造方法 | 说明 |
| Scanner(InputStream is, String charset) | 使用 charset 字符集进行 is 的扫描读取 |
java
Scanner scanner = new Scanner(inputStream,"UTF_8");
while(scanner.hasNext()){
String s = scanner.next();
System.out.println(s);
}
scanner.close();
FileOutputStream写入文件
构造方法与FileInputStream是基本相同的.
|---------|------------------------------------|-----------------------------------------|
| 返回 值类 型 | 方法签名 | 说明 |
| void | write(int b) | 写入要给字节的数据 |
| void | write(byte[] b) | 将 b 这个字符数组中的数据全部写入 os 中 |
| int | write(byte[] b,int off, int len) | 将 b 这个字符数组中从 off 开始的数据写入 os 中,一共写 len 个 |
| void | close() | 关闭字节流 |
| void | flush() | 刷新此输出流并强制任何缓冲的输出字节被写出 |
- 关于flush方法,计算机为了减少I/O的次数.在写入数据的时候,先会把数据放入到缓冲区当中.如果数据特别小的,可能还在缓冲区中.未能及时的放到文件当中,其他流可能就会读取不到.
java
FileOutputStream outputStream = new FileOutputStream("D:/tools/TMP/book.txt");
outputStream.write('H');//单个字符
byte[] arr = {(byte) 'E',(byte) 'L'};//byte[]
outputStream.write(arr);
String s = "LO";
byte[] arr2 = s.getBytes("UTF-8");//将字符串转换成"UTF-8"字符集的字符放入byte[]
outputStream.write(arr2);
outputStream.flush();//刷新缓冲区
outputStream.close();
写入后,会覆盖原有的数据.
PrintfWriter写入
java
FileOutputStream outputStream = new FileOutputStream("D:/tools/TMP/book.txt");
PrintWriter writer = new PrintWriter(outputStream);
writer.println("hello");
writer.flush();
writer.close();
outputStream.close();
关于close()方法
前面介绍了进程,关于进程会用PCB来进行描述
而PCB里又包括了:
- pid
- 内存指针
- 文件描述符
对于close,我们释放的主要是文件描述符.其记载了在此进程中,打开了什么文件,关闭了什么文件或正在使用什么文件等.就像一个数组一样,每进行一个操作会占上一个下标,但表的长度有限,可能会被完全占满.一旦占满了不进行释放,再继续打开文件就会发生错误,可能造成文件资源泄露的问题.
除了直接的使用close方法释放资源,还可以使用相应的带有资源的try方法.
这样就不怕忘记使用close方法了
java
try(FileInputStream inputStream1 = new FileInputStream("D:/tools/TMP/book.txt")){
byte[] buf = new byte[1024];
while(true){
int len;
len = inputStream.read(buf);
if(len == -1){
break;
}
for(int i = 0; i < len; i++){
System.out.println(buf[i]);
}
}
}
文件操作示例
根据文件名搜索并删除文件
主要思路是:
- 遍历当前的目录,如果遍历到的文件是一个目录继续递归遍历
- 如果是一个普通文件,则判断是否为我们要找的文件
java
public static Scanner scanner = new Scanner(System.in);
public static void main(String[] args) {
//输入要搜索的目录
//判断目录是否存在
//输入要删除的文件名
//遍历目录,搜索文件是否存在
System.out.println("请输入要搜素的根目录的路径");
String dirName = scanner.next();
File dir = new File(dirName);//创建目录实例
if(!dir.isDirectory()){//判断是否为目录
System.out.println("输入的路径并非根目录,或根目录不存在");
return;
}
System.out.println("输入要删除的文件的关键字");
String keyName = scanner.next();
scanner(dir,keyName);
}
public static void scanner(File dir,String keyName){
File[] files = dir.listFiles();//此方法,将dir当中的文件存储到File[]中
if(files == null){
return;//证明这个目录下没有东西了
}
//如果目录下还有东西,就将他遍历一遍
for (File f: files) {
if(f.isDirectory()){
//如果还是目录,递归遍历里面的文件
scanner(f,keyName);
}else{
//如果是普通文件,判断是否带有关键字
if(f.getName().contains(keyName)){
//如果带有关键字
System.out.println("输入\"YES\"来确认是否要删除: " + f.getAbsolutePath());
System.out.println("输入任意其他取消删除");
String s = scanner.next();
if(s.equals("YES")){
System.out.println(f.delete());
}else{
System.out.println("取消");
}
}
}
}
}
文件的复制
主要思路:
- 判断被复制的文件在不在或者是否为一个普通文件
- 判断要去的路径存不存在被复制的文件
- 从被复制的文件进行读操作,再进行写操作写入到目标处
java
public static void main(String[] args) {
//判断被复制的文件在不在或者是否为一个普通文件
//判断要去的路径存不存在被复制的文件
//从被复制的文件进行读操作,再进行写操作写入到目标处
Scanner scanner = new Scanner(System.in);
System.out.println("输入要复制的文件的路径");
String source = scanner.next();//要复制的文件
File sourFile = new File(source);
if(!sourFile.isFile()){
//判断是否为一个普通文件,同时也判断了其是否存在
System.out.println("路径错误,目标无法复制");
return;
}
System.out.println("输入要复制到的路径");
String dir = scanner.next();
File dirFile = new File(dir);//要复制去的路径
if(dirFile.isFile()){
//如果已经有了文件,则不进行复制
System.out.println("文件已存在或路径错误");
return;
}
//进行读写操作
//使用带资源的try,最后会自动释放资源
try(InputStream inputStream = new FileInputStream(sourFile); OutputStream outputStream = new FileOutputStream(dirFile)) {
byte[] buf = new byte[1024];
while(true){
int len = inputStream.read(buf);
if(len == -1){
System.out.println("复制完毕");
break;
}
outputStream.write(buf);
}
} catch (IOException e) {
e.printStackTrace();
}
}
根据文件内容或文件名搜索(简单版)
java
//遍历目录,读取普通文件的内容与关键字进行匹配
//如果是目录则递归遍历
public static List<File> ret = new ArrayList<>();
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("输入要搜索的目录路径");
String s = scanner.next();
File sFile = new File(s);
if(!sFile.isDirectory()){
System.out.println("输入的路径有误");
return;
}
System.out.println("输入关键字");
String keyName = scanner.next();
scan(sFile,keyName);
System.out.println("相关搜索的文件有");
for (File f:ret) {
System.out.println(f.getAbsolutePath());
}
}
public static void scan(File root,String keyName){
File[] files = root.listFiles();//得到当前目录的所有文件
if(files == null){
//递归结束的条件
return;
}
for (File f:files) {
if(f.isDirectory()){
//如果是目录
//继续递归
scan(f,keyName);
}else{
//如果是普通文件
//判断文件名是否存在关键字
//判断内容是否存在关键字
//如果满足其一,则放到列表中
if(isContainOnName(f,keyName) || isContainOnContent(f,keyName)){
ret.add(f);
}
}
}
}
public static boolean isContainOnName(File file,String keyName){
return file.getName().contains(keyName);
}
public static boolean isContainOnContent(File file,String keyName){
//进行读操作
//组成一个字符串,判断keyName是否存在字符串中
//当然这种方法有点蠢,性能非常差.不要用于大文件当中
StringBuffer buffer = new StringBuffer();
try(InputStream inputStream = new FileInputStream(file);Scanner scan = new Scanner(inputStream,"UTF-8")){
while(scan.hasNext()){
buffer.append(scan.next());
}
} catch (IOException e) {
e.printStackTrace();
}
return buffer.indexOf(keyName) != -1;//StringBuffer没有contains可以用indexOf,如果返回值为-1代表无子串
}