Java 文件io
文章目录
- 文件
- 硬盘
- 文件路径
- 文件类型
- 对文件的操作
- pathSeparator
- 文件中的方法使用
- 文件内容的读写 --- 数据流
- Reader
- close
- Writer
- InputStream
- OutputStream
- 案例
文件
- 文件是一个广义的概念,可以代表很多东西,比如:操作系统里,会把很多的硬件设备和软件资源都抽象成 ‘’ 文件 ‘’,统一进行管理
但是大部分情况下,文件都是指硬盘的文件,文件就相当于是针对 ‘’ 硬盘 ''数据的一种抽象
硬盘
- 机械硬盘(HDD):适合顺序读写,不适合随机读写,因为磁头随机读写需要时间进行移动
固态硬盘(SSD):里面都是集成程度很高的芯片(类似于cpu,内存)
固态硬盘就要比机械硬盘效率高很多
文件路径
- 文件的方式操作硬盘
电脑上有很多的文件,这些文件都是通过 ‘’ 文件系统 ‘’ 来进行组织的
操作系统提供的模块:文件系统
操作系统中通过目录这样的结构来组织文件,树形结构的方式
路径是重点
3. 路径:使用目录这样的层次结构来描述文件所在的位置
1.绝对路径:以C盘,D盘这样的盘符开头的
2.相对路径:需要先指定一个目录,作为基准目录,从基准目录出发,看看沿着什么样的路线能够找到指定文件。此时路径就是相对路径,往往是以 . 或者 … 开头的(. 这种情况可以省略)
. 表示当前目录
… 表示当前目录的上一级目录
例子:
文件类型
- 从编程的角度来看,文件类型分为两种:
文本文件
二进制文件 - 如何判定一个文件是文本文件还是二进制文件?
直接打开记事本,如果是乱码就是二进制文件,否则就是文本文件(记事本就是按照字符的方式来展示内容,这个过程会自动查码表)
二进制转utf-8就是记事本中的乱码,如果查到了就显示出来,没有查到就是空白的部分
很多文件都是二进制的,比如.docx,.pptx
文本文件和二进制文件的区分是非常重要的,代码编写的时候,文本文件和二进制文件的编写方式是不同的
对文件的操作
- 文件的操作分为两类:对File类进行操作
文件系统的操作:
创建文件,删除文件,判定文件是否存在,判定文件类型,重命名…
文件内容的操作:流对象
读文件,写文件
pathSeparator
- pathSeparator就是用来跨平台使用的斜杆分割符,pathSeparator是
- 构造File对象指定路径
- 当前文件所在的目录就是父目录
(当前文件前面的所有目录)
文件名 = 前缀 + 扩展名
使用路径构造File对象,一定要把前缀和扩展名都带上
文件权限:约定了哪个用户可以读,哪个用户可以写
文件中的方法使用
- getPath()
import java.io.File;public class Demo1 {public static void main(String[] args) {File file = new File("d:/test.txt");System.out.println(file.getParent());System.out.println(file.getName());System.out.println(file.getPath());System.out.println(file.getAbsoluteFile());System.out.println(file.getAbsolutePath());}
}
2. 创建文件
import java.io.File;
import java.io.IOException;public class Demo2 {public static void main(String[] args) throws IOException {File f = new File("d:/test.txt");System.out.println(f.exists());System.out.println(f.isDirectory());System.out.println(f.isFile());// 创建文件boolean ret = f.createNewFile();System.out.println("ret = " + ret);System.out.println(f.exists());System.out.println(f.isDirectory());System.out.println(f.isFile());}
}
- 删除文件
deleteOnExit可以解决断电异常关闭的文件,再打开电脑这个文件会恢复出来
import java.io.File;
import java.lang.reflect.Field;public class Demo3 {public static void main(String[] args) throws InterruptedException {File f = new File("d:/test.txt");boolean ret = f.delete();System.out.println("ret = " + ret);// 没有返回值,在进程结束之后删除// 先执行删除但是要等待5s后进程结束才真正删除/*f.deleteOnExit();Thread.sleep(5000);System.out.println("进程结束!");*/}
}
- 获取目录中包含的所有文件
t.list针对的是目录,不能是文件
import java.io.File;
import java.util.Arrays;public class Demo4 {public static void main(String[] args) {File f = new File("d:/");String[] files = f.list();System.out.println(Arrays.toString(files));}
}
5. 创建目录
import java.io.File;
import java.lang.reflect.Field;public class Demo5 {public static void main(String[] args) {File f = new File("d:/test/aa/bb/cc");// boolean ret = f.mkdir();// 创建单级目录boolean ret1 = f.mkdirs();// 创建多级目录// System.out.println("ret = " + ret);System.out.println("ret1 = " + ret1);}
}
- 重命名文件名
import java.io.File;public class Demo6 {public static void main(String[] args) {// src是源 dest是目标File srcfile = new File("d:/test1.txt");File destfile = new File("d:/test2.txt");// 把源改成目标boolean ret = srcfile.renameTo(destfile);System.out.println("ret = " + ret);}
}
文件内容的读写 — 数据流
- 流对象:stream
- 在标准库中提供了很多读写文件的流对象,有很多类
这些类可以分为两个大类:
字节流(二进制文件)
字符流(文本文件)
区分输入和输出是站在cpu的角度上的
读 -> 输入
写 -> 输出
Reader
- 无参的read,一次read一个字符
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;// Reader
public class Demo7 {public static void main(String[] args) throws IOException {Reader r = new FileReader("d:/test.txt");// 读取字符while(true){int c = r.read();if(c == -1){System.out.println("读取结束!");break;}char ch = (char)c;System.out.println(ch);}}
}
read方法的三个版本:
还有对read的返回值是int的解释:
utf8是三个字节编码的
unicode是两个字节编码的
把多个unicode字符放到一起是很难区分从哪到哪是完整的一个字符
utf8就可以区分这样连续的字符(其实utf8是从unicode演化过来的)
- read(char[] cbuf),一次read多个字符
应该往这个read里传入的是一个空的字符数组,然后由read方法内部,对这个数组内容进行填充
此时的cbuf这个参数,称为 ‘’ 输出型参数 ‘’
(可以理解函数是一个工厂,参数是原材料,返回值是生产的产品,而有时返回值不好表示生产产品,用了这个参数作为生产的产品)
// read,一次读多个
while(true){char[] cbuf = new char[1024];int n = r.read(cbuf);// n表示读到的字符个数if(n == -1){// 读到了文件末尾break;}System.out.println("n = " + n);for(int i = 0;i < n;i++){System.out.println(cbuf[i]);}
}
这个read会尽可能把cbuf这个数组给填满,如果实际上文件的内容填不满,也没关系,就直接把所有的数据都读出来就好了
这里的while里面加了一个true就是为了反复读多次,如果一次读不完的话
重点掌握前两个方法
close
- 使用close方法最主要的作用是释放文件描述符
// 3. 一个文件使用完毕记得 close
r.close();
不close的后果:
应该使用try finally来执行,保证close可以被执行到
try {// read,一次读多个while (true) {char[] cbuf = new char[1024];int n = r.read(cbuf);// n表示读到的字符个数if (n == -1) {// 读到了文件末尾break;}System.out.println("n = " + n);for (int i = 0; i < n; i++) {System.out.println(cbuf[i]);}}}finally{// 3. 一个文件使用完毕记得 closer.close();}
还有一种更优雅的方式:
try with resources 这个语法的目的就是 ()
定义的变量,会在try代码结束的时候(不管是正常结束,还是抛出异常)都会自动调用其中的close方法
流对象都可以这么写
try(Reader reader = new FileReader("d:/test.txt")) {// read,一次读多个while (true) {char[] cbuf = new char[1024];int n = reader.read(cbuf);// n表示读到的字符个数if (n == -1) {// 读到了文件末尾break;}System.out.println("n = " + n);for (int i = 0; i < n; i++) {System.out.println(cbuf[i]);}}
}
Writer
- 最常用的是一次写一个字符串
write(String str) - Writer默认情况下,会把原有的文件中的内容清空掉,再写入
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;public class Demo8 {public static void main(String[] args) {try(Writer writer = new FileWriter("d:/test.txt")){writer.write("我正在学习!");} catch (IOException e) {throw new RuntimeException(e);}}
}
- 如果不想清空原有的内容,需要在文件的构造方法中多写入一个参数,此时不会清空原有的内容,会在原有内容的末尾加上新的内容
append在末尾加上新加的内容
InputStream
- read也有三个版本:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;public class Demo9 {public static void main(String[] args) {try(InputStream inputStream = new FileInputStream("d:/test.txt")) {byte[] cbuf = new byte[1024];int n = inputStream.read(cbuf);System.out.println("n = " + n);for(int i = 0;i < n;i++){System.out.printf("%x\n",cbuf[i]);}} catch (IOException e) {throw new RuntimeException(e);}}
}
OutputStream
- OutputStream和Writer类似,都是打开一个文件都会清空原有的内容,新写入的信息就是新的数据
- 如果不想要清除原有的文件内容,可以使用追加写的方式,在构造方法中第二个参数传入true
- OutputStream只能写入字节数组,不能写入字符数组,需要使用getBytes使字符转为字节
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;public class Demo10 {public static void main(String[] args) {try(OutputStream outputStream = new FileOutputStream("d:/test.txt",true)) {String s = "你好";outputStream.write(s.getBytes());} catch (IOException e) {throw new RuntimeException(e);}}
}
-
把字节流转为字符流 (读)(办法有好几种)
下面是最简单的方法
System.in是输入流InputStream中的一种
System.in -> InputStream -
Scanner不仅可以从文件中读,还可以从键盘中读,还可以从网络中读
import java.io.*;
import java.util.Scanner;public class Demo11 {public static void main(String[] args) {try(InputStream inputStream = new FileInputStream("d:/test.txt")) {// 此时scanner是从文件中读取了Scanner scanner = new Scanner(inputStream);// 使用scanner读取后续的数据String s = scanner.next();System.out.println(s);} catch (IOException e) {throw new RuntimeException(e);}}
}
- 把字节流转为字符流 (写)
import java.io.*;public class Demo12 {public static void main(String[] args) {try(OutputStream outputStream = new FileOutputStream("d:/test.txt")) {PrintWriter printWriter = new PrintWriter(outputStream);printWriter.printf("hello");} catch (IOException e) {throw new RuntimeException(e);}}
}
- 问题:我们写入了字符串,怎么文本中还是空的?
解决:为了保证数据写入硬盘中,就应该在合适的实际,使用flush方法手动刷新缓冲区,将缓冲区的数据刷新到硬盘上
import java.io.*;public class Demo12 {public static void main(String[] args) {try(OutputStream outputStream = new FileOutputStream("d:/test.txt")) {PrintWriter printWriter = new PrintWriter(outputStream);printWriter.printf("hello");// 确保数据写入到硬盘上printWriter.flush();} catch (IOException e) {throw new RuntimeException(e);}}
}
案例
- 目标:扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件(经典的面试题)
- 写代码的时候,要对用户输入的内容进行合法性检查
import java.io.File;
import java.util.Scanner;public class Demo13 {public static void main(String[] args) {// 1. 输入你要扫描的路径Scanner scanner = new Scanner(System.in);System.out.println("请输入你要扫描的路径: ");String path = scanner.next();// 判定输入的路径是否合法File rootPath = new File(path);if(!rootPath.isDirectory()){// 如果它不是一个目录System.out.println("您当前输入的扫描路径有误");return;}// 2. 让用户输入要查询的关键词System.out.println("请输入要删除文件的关键词: ");String word = scanner.next();// 3. 可以进行递归的扫描了,以刚才的路径为起点进行扫描// 通过这个方法进行递归scanDir(rootPath,word);}private static void scanDir(File rootPath, String word) {// 1.先列出rootPath中所有的文件和目录File[] files = rootPath.listFiles();if(files == null){// 如果当前目录是空目录就直接returnreturn;}// 2. 遍历这里的所有元素,不同的类型做出不同的处理for(File f : files){// 加个日志,方便观察当前递归的执行过程System.out.println("当前扫描的文件 :" + f.getAbsolutePath());if(f.isFile()){// 普通文件 检查文件是否要删除,并执行删除操作checkDelete(f,word);}else{// 目录 递归再去子目录中判定包含的内容scanDir(f,word);}}}private static void checkDelete(File f, String word) {if(!f.getName().contains(word)){// 不必删除,直接结束方法return;}else{// 需要删除System.out.println("当前文件为:" + f.getAbsolutePath() + ",请确定是否要删除(Y/n):");Scanner scanner = new Scanner(System.in);String choice = scanner.next();if(choice.equals("Y") || choice.equals("y")){// 真正删除文件f.delete();System.out.println("删除完毕!");}else{// 如果是其他操作,不一定要是 n,都会取消删除System.out.println("取消删除!");}}}
}
- 文个章节就是了解一下api的使用就可以了