【Java EE】文件IO
Author:MTingle
major:人工智能---------------------------------------
Build your hopes like a tower!
目录
一、文件是什么?
二、针对文件系统操作的API
1.文件路径,文件名,文件是否存在
2. 创建文件
3.删除文件(*在线程结束时)
4. 创建File对象代表的⽬录
5. 文件改名及文件移动
三. 针对文件内容的操作(写文件 / 读文件)
1. 字节流(inputStream / outputStream)
1). 打开关闭文件 及 .read()
2). read一次读若干个字节
3). 读取汉字
4).写文件
2. 字符流(Reader / Writer)
3. 查找硬盘上文件的位置
4. 实现文件复制
3. 在目录中搜索,按照文件内容的方式搜索
一、文件是什么?
针对硬盘这种持久化存储的I/O设备,当我们想要进⾏数据保存时, 往往不是保存成⼀个整体,⽽是独⽴成⼀个个的单位进⾏保存,这个独⽴的单位就被抽象成⽂件的概 念,就类似办公桌上的⼀份份真实的⽂件⼀般。
二、针对文件系统操作的API
1.文件路径,文件名,文件是否存在
public class IODemo1 {
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.getName());
System.out.println(f.getPath());
System.out.println(f.getAbsolutePath());
System.out.println(f.getCanonicalPath());
}
}
2. 创建文件
public class IODemo2 {
public static void main(String[] args) throws IOException {
File f=new File("./test.txt");
boolean ret=f.createNewFile();
System.out.println("ret: "+ret);
System.out.println(f.exists());
}
}
3.删除文件(*在线程结束时)
public class IODemo3 {
public static void main(String[] args) throws InterruptedException, IOException {
// File file=new File("./test.txt");
// boolean ret=file.delete();
// System.out.println(ret);
File file=new File("./test.txt");
boolean ret=file.createNewFile();
System.out.println("ret: "+ret);
file.deleteOnExit();
Thread.sleep(5000);
}
}
4. 创建File对象代表的⽬录
public class IODemo4 {
public static void main(String[] args) {
File file=new File("./aaa/bbb/ccc");
boolean ret=file.mkdirs();
System.out.println(ret);
}
}
5. 文件改名及文件移动
public class IODemo5 {
public static void main(String[] args) {
// 改名
// File src=new File("./test.txt");
// File dest=new File("./test2.txt");
// src.renameTo(dest);
// 移动文件
File src=new File("./test2.txt");
File dest=new File("./aaa/test2.txt");
src.renameTo(dest);
}
}
三. 针对文件内容的操作(写文件 / 读文件)
在进行文件操作的时候,我们在结束操作的时候一定要调用 .close() 方法关闭文件,否则就会造成 文件资源泄露 / 内存泄漏 问题.
文件描述符表记录了当前进程都打开了哪些文件,每打开一个文件,都需要在文件描述符表中占据一个位置,如果不关闭的话,一直打开会造成文件描述符表被耗尽(文件描述符表有长度上限),当文件描述符表被耗尽,就会造成一系列的逻辑问题
1. 字节流(inputStream / outputStream)
以字节为单位,一次最少读写一个字节
1). 打开关闭文件 及 .read()
public class IODemo6 {
public static void main(String[] args) throws IOException {
// 打开文件
// InputStream inputStream=null;
// try {
// inputStream=new FileInputStream("./test.txt");
// }finally { // 防止文件结束时未执行到关闭位置,使用finally确保文件被关闭
//关闭文件
// inputStream.close();
// }
try (InputStream inputStream=new FileInputStream("./test.txt")){
while (true) {
int b=inputStream.read(); // 读到文件末尾,read会返回 -1
if (b==-1) {
break;
}
System.out.printf("%x ",b);
}
}
}
}
在此处我们更推荐使用第二种方法进行打开关闭文件,一旦出了 try 代码块,此时 try 自动帮我们调用inputStream的close方法.
此处的read方法,看起来是 int 类型,实际上是 byte ,实际的取值是 0- 255 ,此处有一个特殊情况,若读到文件末尾,则返回 -1 ,所以使用 int .
使用无参数版本,每次调用读取一个字节,返回值就表示读到的这个字节的值.
有参数版本中, offset 在此处表示偏移量
2). read一次读若干个字节
public class IODemo7 {
public static void main(String[] args) throws IOException {
// 一次读若干个字节
try (InputStream inputStream=new FileInputStream("./test.txt")){
while (true) {
byte[] buffer=new byte[1024];
int n=inputStream.read(buffer);
if (n==-1) {
break;
}
for (int i = 0; i < n; i++) {
System.out.printf ("%x ",buffer[i]);
}
}
}
}
}
read 的第二三个版本,返回的 int 表示实际读取的字节个数, buffer 是一个常见的术语,表示 "缓冲区",往往是一个内存空间,读文件就是把硬盘数据读取到内存中,上面这种写法,一次读取若干个字节,会比一次读取一个高效,类似的,一次写若干个字节,也比一次写一个字节更高效,操作硬盘本身就是一个比较低效的操作,期望低效的操作出现的次数越少越好,这样效率就会更高
3). 读取汉字
public class IODemo8 {
public static void main(String[] args) throws IOException {
// 一次读若干个字节(汉字)
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);
}
}
}
}
此处的 String 是通过前 n 个字节构造,而不是整个数组,实际读取的文件内容可能不足1024
4).写文件
写文件与读文件类似,三个版本的含义也相似
public class IODemo9 {
public static void main(String[] args) throws IOException {
// try (OutputStream outputStream=new FileOutputStream("./test.txt")){
// byte[] buffer=new byte[]{97,98,99,100,101,102};
// outputStream.write(buffer);
//
// }
try (OutputStream outputStream=new FileOutputStream("./test.txt",true)){
byte[] buffer=new byte[]{97,98,99,100,101};
outputStream.write(buffer);
}
}
}
注释部分与未注释的部分的区别为少了一个 true ,当我们实施写操作打开文件的时候,注释版本中没有填 true ,系统默认为 false ,此时当我们打开文件时,会清空文件之前的所有内容,若我们不想把文件原来的内容清空,而是继续往下写文件,将内容写到文件的末尾,我们需要在末尾加上一个 true ,此时我们打开文件时就不会将原有的内容清空.
此处的参数 append 的含义为追加.
2. 字符流(Reader / Writer)
以字符为单位进行读写,一次最少读写一个字符,在utf8中,一个汉字需要三个字节来表示,所以读写汉字的时候,每次读写都需要以3个字节为单位进行,不能一次读写半个汉字. Reader 和 Writer 的使用和 inputStream outputStream 类似.只不过此时是按照 char 为单位操作了
public class IODemo10 {
public static void main(String[] args) throws IOException {
// 字符流读
// 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);
// }
// }
// 字符流写
try (Writer writer=new FileWriter("./test.txt")){
String s="你好啊";
writer.write(s);
}
}
}
此处的代码里,有一个关键问题, char 占两个字节,但是我们的汉字是占3个字节(utf8),但是此处读取来的每个字节咋就成2个字节了呢?
此处这个代码,相当于把当前文件的 utf8 在按照字符读取的时候,先转换成 unicode ,每个 char 中存储的是对应的 unicode 的值,如果是基于 unicode 最终还可以构造回 utf8 的 String.
文件utf8 => char[] unicode => String utf8
上述的替换过程我们无法直接感知到, java 里面已经封装好了.
public class IODemo11 {
public static void main(String[] args) {
try (Writer writer=new FileWriter("./test.txt")){
String s="你好啊";
writer.write(s);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Scanner 也可以辅助我们进行读文件, Scanner(System.in) ,括号内部本质上就是一个 inputStream.
public class IODemo12 {
public static void main(String[] args) throws FileNotFoundException {
try (InputStream inputStream=new FileInputStream("./test.txt")){
Scanner scanner=new Scanner(inputStream);
while (scanner.hasNextLine()) {
String s=scanner.next();
System.out.println(s);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
3. 查找硬盘上文件的位置
public class IODemo13 {
// 查找硬盘上的文件位置
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(fileName,rootFile);
}
private static void scanDir(String fileName, File rootPath) {
File[] files=rootPath.listFiles();
if (files==null) {
return;
}
for (File f:files) {
// System.out.println("查找中,当前路径为: "+f.getAbsolutePath());
if (f.isDirectory()) {
scanDir(fileName,f);
}else if (f.isFile()) {
if (fileName.equals(f.getName())) {
System.out.println("找到了,绝对路径为: "+f.getAbsolutePath());
}
}else {
;
}
}
}
}
此处我们使用递归,就是要进行树的遍历,把目录中,以及其中的子目录,所有的文件都遍历一遍,看是否有符合要求的
4. 实现文件复制
public class IODemo14 {
// 复制文件
public static void main(String[] args) throws IOException {
Scanner scanner=new Scanner(System.in);
System.out.println("请输入要复制的源文件: ");
String srcPath=scanner.next();
System.out.println("请输入要复制的目标文件: ");
String destPath=scanner.next();
// 检查文件合理性
// 1.复制的源文件
File srcFile=new File(srcPath);
if (!srcFile.isFile()) {
System.out.println("源文件路径有误!");
return;
}
// 2.目标文件
File destFile=new File(destPath);
if (!destFile.getParentFile().isDirectory()) {
System.out.println("目标文件路径有误!");
return;
}
try (InputStream inputStream=new FileInputStream(srcPath);
OutputStream outputStream=new FileOutputStream(destPath)){
while (true) {
// 把内容读到inputStream
byte[] buffer=new byte[1024];
int n=inputStream.read(buffer);
if (n==-1) {
break;
}
// 把内容写到outputStream
outputStream.write(buffer,0,n);
}
}
}
}
把一个文件复制一下,成为另一个文件,就是把第一个文件读方式打开,一次读取出这里的每个字节,再把读到的内容写入另一个文件里.
3. 在目录中搜索,按照文件内容的方式搜索
public class IODemo15 {
public static void main(String[] args) throws IOException {
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(word,rootFile);
}
private static void scanDir(String word, File rootFile) throws IOException {
File[] files=rootFile.listFiles();
if (files==null) {
return;
}
for (File f:files) {
System.out.println("当前遍历到: "+f.getAbsolutePath());
// 1.找到对应目录
if (f.isFile()) {
searchInFile(word,f);
}else if (f.isDirectory()) {
scanDir(word,f);
}else {
;
}
}
}
private static void searchInFile(String word, File f) throws IOException {
try (InputStream inputStream=new FileInputStream(f);){
// 拼接字符
StringBuilder stringBuilder=new StringBuilder();
while (true) {
byte[] buffer=new byte[1024];
int n=inputStream.read(buffer);
if (n==-1) {
break;
}
String string=new String(buffer,0,n);
stringBuilder.append(string);
}
// 字符匹配
if (stringBuilder.indexOf(word)!=-1) {
System.out.println("找到了"+word+"路径为: "+f.getAbsolutePath());
return;
}
}
}
}
用户输入一个目录,一个要搜索的词,遍历文件的过程中没如果文件包含了要搜索的词,此时就把文件的路径打印出来.