【文件操作】IO流
文章目录
- 1. 文件定义
- 2. 文件操作
- 2.1 文件系统的操作
- 2.2 文件内容的操作
- 2.3 InputStream
- 2.4 OutputStream
- 2.5 Reader
- 2.6 Writer
- 2.7 InputStream对象作为Scanner的参数
- 3. 案例
- 3.1 查找硬盘上的文件位置
- 3.2 实现文件复制
- 3.3 实现文件复制
1. 文件定义
文件是指硬盘上的文件。
操作系统为了管理文件,引入了专门的模块:文件系统。
每个文件都有一个“路径”描述文件所在位置。
绝对路径:从盘符出发,到文件名结束。
相对路径:有一个基准路径,以这个基准做为参考,. 表示当前目录,…/表示当前目录的上一级目录。
文本文件:存储的内容虽然是二进制数据,但是这些二进制能从对应的字符集码表中查找出来翻译成对应的合法字符。
二进制文件:存储的是二进制数据,在字符集码表中查不出对应字符。
2. 文件操作
2.1 文件系统的操作
文件操作包括:创建文件、删除文件、创建目录、重命名,在Java中提供了File类,来进行文件操作。
File属性概述:
修饰符及类型 | 属性 | 说明 |
---|---|---|
static String | pathSeparator | 依赖于系统的路径分隔符,String类型的表示 |
static char | pathSeparator | 依赖于系统的路径分隔符,char类型的表示 |
在Java中,pathSeparator 是一个表示路径分隔符的常量,定义在 java.lang.System 类中。它用于在文件路径中分隔多个路径。pathSeparator 的作用是帮助程序跨平台兼容不同的文件系统路径分隔符。
构造方法(常用构造方法2):
方法 | 说明 |
---|---|
File(File parent, String child) | 根据父目录+孩子文件路径,创建一个新的File实例 |
File(String pathname) | 根据文件路径创建一个新的File实例,路径可以是绝对路径或者相对路径 |
File(String parent, String child) | 根绝父目录+孩子文件路径,创建一个新的File实例,父目录用路径表示 |
方法:
修饰符及返回类型 | 方法名 | 说明 |
---|---|---|
String | getParent() | 返回 File 对象的父目录文件路径 |
String | getName() | 返回 File 对象的纯文件名称 |
String | getPath() | 返回 File 对象的文件路径 |
String | getAbsolutePath() | 返回 File 对象的绝对路径 |
String | getCanoniaclPath() | 返回 File 对象的修饰过的绝对路径 |
boolean | exists() | 判断 File 对象描述的⽂件是否真实存在 |
boolean | isDirectory() | 判断 File 对象代表的⽂件是否是一个目录 |
boolean | isFile() | 判断 File 对象代表的⽂件是否是一个普通文件 |
boolean | createNewFile() | 根据 File 对象,⾃动创建⼀个空⽂件,成功返回true |
boolean | delete() | 根据 File 对象,删除该⽂件,成功删除返回true |
void | deleteOnExit() | 根据 File 对象,删除文件,在程序运行结束才会删除 |
String[] | list() | 返回 File 对象代表的目录下的所有的文件名 |
File[] | listFiles() | 返回 File 对象代表的目录下的所有的文件,以 File 对象的形式返回 |
boolean | mkdir() | 创建 File 对象代表的目录 |
boolean | mkdirs() | 创建 File 对象代表的目录,必要时会创建中间目录 |
boolean | renameTo(() | 进行文件名修改 |
boolean | canRead() | 判断用户是否对文件有可读权限 |
boolean | canWrite() | 判断用户是否对文件有可写权限 |
getParent()、getName()、getPath()、getAbsolutePath()、getCanonicalPath()
public class IODemo {
public static void main(String[] args) throws IOException {
File file = new File(".//text.txt");
System.out.println("File对象的父目录文件路径: " + file.getParent());
System.out.println("File对象的文件名称: " + file.getName());
System.out.println("File对象的文件路径: " + file.getPath());
System.out.println("File对象的绝对路径: " + file.getAbsolutePath());
System.out.println(file.getCanonicalPath());
}
}
createNewFile()
File file=new File(".//text.txt");
//根据 File 对象,⾃动创建⼀个空⽂件,成功返回true
boolean ret=file.createNewFile();
System.out.println(ret);
list()
public class IODemo {
public static void main(String[] args) {
File file=new File(".");
//返回File对象代表的目录下的所有的文件名,返回值类型String[]
System.out.println(Arrays.toString(file.list()));
}
}
mkdir() 和mkdirs()
public class demo5 {
public static void main(String[] args) {
File f1 = new File("./abc");
File f2 = new File("./abc/def/ghi");
// boolean ok = f1.mkdir();//创建目录,只能创建一个
boolean ok = f2.mkdirs();//创建目录,可以创建多个子目录
System.out.println(ok);
}
}
renameTo的使用,renameTo既可以修改文件名,也可以移动目录
public class demo6 {
public static void main1(String[] args) {
File srcFile = new File("./abc");
File destFile = new File("./abc11");
boolean ok = srcFile.renameTo(destFile);//修改srcFile的文件名,修改成destFile
System.out.println(ok);
}
public static void main(String[] args) {
File srcFile = new File("./abc/def");
File destFile = new File("./def");
//相当于移动文件,把当前目录下的abc目录下的def,移动到当前目录下
boolean ok = srcFile.renameTo(destFile);//修改srcFile的文件名,修改成destFile
System.out.println(ok);
}
}
2.2 文件内容的操作
文件内容操作主要包括读文件和写文件,Java中对这些操作进行了封装,叫:文件流/IO流,这个叫法也是跟随操作系统的。
字节流:读写数据的基本单位是字节,一次只能读写一个字节。主要有InputStream、OutputStream这两个类,分别是输入、输出的字节流。
字符流:读写数据的基本单位是字符,一次只能读写一个字符(一个字符是多少个字节不确定,取决于编码方式,字符流内部会自动查询码表,把二进制转换成对应的字符)。主要有Reader、Writer这两个类,分别是输入、输出的字符流。
谈的输入、输出是针对cpu来说的,数据远离cpu就是输出、数据靠近cpu就是输入。
InputStream、OutputStream、Reader、Writer都是抽象类,如果要使用,就得使用实现了上述4个抽象类的类,主要了解:FileInputStream、FileOutputStream、FileReader、FileWriter 这4个类即可。
补充:
文件资源泄漏:打开文件,其实是在该进程中的"文件描述表"中,创建了一个新的表项,文件描述表描述了该进程需要操作哪些文件,文件描述表可以认为是一个数组,数组的每个元素包含了操作的文件的信息,而数组的下标就称为文件描述符,每次打开一个文件就相当于在数组中占据一个位置,数组长度是固定的,不能扩容,必须调用close才能释放空间,否则当数组满了,后面再打开文件就会失败。
为了避免上述问题,防止忘记关闭文件,我们可以将使用try catch和finally,将close操作放在finally中,另外还需要注意的一点是,在创建InputStream对象的同时,会有一个隐藏操作:打开文件,所以并不需要我们手动打开文件。
public class demo1 {
public static void main1(String[] args) throws IOException {
InputStream inputStream = null;
try {
inputStream = new FileInputStream("./test.txt");//创建对象,创建对象的同时会有一个隐含的操作:打开文件
} catch (IOException e) {
e.printStackTrace();
} finally {
inputStream.close();//关闭文件
}
}
}
如果我们通过try-finally方法关闭文件,虽然有效果但是会显得很繁琐,于是Java中的try方法还提供了try with resources 如下方代码
public class demo1 {
public static void main1(String[] args) throws IOException {
InputStream inputStream = null;
//改善方法
//Java try还提供了try with resources这样的方法,在try后边的括号中直接new,出了大括号try会自动调用close方法
//try括号中new的必须要实现closeable接口才可以
try (InputStream inputStream=new FileInputStream("./text.txt")){
byte[] buffer=new byte[1024];
while (true){
int n=inputStream.read(buffer);
if (n==-1){
break;
}
for (int i = 0; i < n; i++) {
System.out.printf("%x",buffer[i]);
}
}
}
}
}
使用try with resources会在出了大括号之后自动帮你关闭文件,在try()括号中的类必须要实现Closeable接口才可以。
public abstract class InputStream implements Closeable
2.3 InputStream
每次读写最小的单位是字节,站在cpu的角度就是迎面向我们走来
package IO;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class StreamDemo01 {
public static void main(String[] args) throws IOException {
//打开文件
// InputStream inputStream = null;
// try{
// inputStream = new FileInputStream("D:/Astudy/bit/IOtest/test.txt");
// }finally {
// //关闭文件
// inputStream.close();
// }
//实现了CLoseable接口,出了try就自动调用inputStream的close方法
// try(InputStream inputStream = new FileInputStream("D:/Astudy/bit/IOtest/test.txt")){
// while (true){
// int ch = inputStream.read();
// if(ch == -1){
// break;//文件读取完毕
// }
// System.out.printf("%x ", ch);
// }
// }
try(InputStream inputStream = new FileInputStream("D:/Astudy/bit/IOtest/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]);
}
System.out.print(new String(buffer,0,n));
}
}
}
}
1 使用try with resources辅助我们关闭文件(try()括号中的类必须是实现Close接口的)
2 InputStream inputStream = new FileInputStream
3 因为 InputStream 是抽象类,不能实例化,所以只能实例化 FileInputStream
4 输出型参数:将byte数组传进read方法中,read()方法将值存入到byte数组中
5 如果读到文件末尾,继续read方法读取的话就会返回-1
6 构造字符串,将值打印出来
这里的read方法相当于我们去食堂吃饭
1.没有参数的:相当于我们空着手去吃饭,食堂阿姨会给我一个盘子用来装
2.带一个参数的是相当于我们自己带一个饭盒子过去,让阿姨把饭装在我们自己的盒子里
3.带三个参数的:相当于我们指定让食堂阿姨将饭菜放到饭盒的指定格子中
2.4 OutputStream
每次读写最小的单位是字节,站在cpu的角度就是离cpu远去。
import org.omg.CORBA_2_3.portable.OutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class OutputStreamDEmo {
public static void main(String[] args) {
try(FileOutputStream outputStream = new FileOutputStream("D:/Astudy/bit/IOtest/test.txt",true)) {
byte[] buffer = new byte[]{97,98,99,100};
outputStream.write(buffer);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
FileOutputStream(“D:/Astudy/bit/IOtest/test.txt”,true))
当我们创建文件时,OutputStream 默认会将文件原来的内容都清空
如果我们不想清空原来的内容,而是在原有的基础上再次加新内容,就需要在文件名字后面加上true
FileOutputStream(“./text.txt”,true))
2.5 Reader
package IO;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
public class ReaderDemo {
public static void main(String[] args) {
try(Reader reader = new FileReader("D:/Astudy/bit/IOtest/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);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Reader的read方法,也有3个版本(注意这里传参的类型是char)
2.6 Writer
package IO;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class WriterDemo {
public static void main(String[] args) {
try(Writer writer = new FileWriter("D:/Astudy/bit/IOtest/test.txt",true)) {
String s = "你好啊";
writer.write(s);
} catch (IOException e) {
throw new RuntimeException(e);
} ;
}
}
write也有四个版本
2.7 InputStream对象作为Scanner的参数
package IO;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class ScannerDemo {
public static void main(String[] args) {
try(InputStream inputStream = new FileInputStream("D:/Astudy/bit/IOtest/test.txt")) {
Scanner scanner = new Scanner(inputStream);
while (scanner.hasNext()){
String s = scanner.next();
System.out.println(s);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
3. 案例
3.1 查找硬盘上的文件位置
给定一个文件名,去指定的目录中进行搜索,找到文件名匹配的结果,并打印出完整的路径。
思路:文件系统的目录结构是"树形"结构,既然是搜索,就会涉及到遍历。针对树的遍历,一般都是要借助递归的。
package IO;
import java.io.File;
import java.util.Scanner;
public class Test01 {
public static void main(String[] args) {
//1.输入必要的信息
Scanner sc = new Scanner(System.in);
System.out.println("请输入要搜索的文件名:");
String fileName = sc.next();
System.out.println("请输入要搜索的目录名:");
String rootPath = sc.next();
File rootFile = new File(rootPath);
if (!rootFile.exists()) {
System.out.println("输入路径有误");
return;
}
//2.有了搜索的路径之后,就可以按照 递归 的方式来搜索
scanDir(rootFile,fileName);
}
private static void scanDir(File rootFile, String fileName) {
//1.把当前目录的文件和子目录都列出来
File[] files = rootFile.listFiles();
if (files == null) {
return;//空目录,直接返回
}
//2.遍历上述files,判定每一个files是目录还是文件
for (File file : files) {
System.out.println("当前遍历到"+file.getAbsolutePath() );
if (file.isDirectory()) {
scanDir(file,fileName);
} else if (file.isFile()) {
if (fileName.equals(file.getName())) {
System.out.println("找到文件: "+file.getAbsolutePath());
}
}else {
;//暂时不需要
}
}
}
}
3.2 实现文件复制
把一个文件复制一下,成为另一个文件。
思路:把第一个文件读方式打开,依次读取出这里的每个字节,再把读到的内容,写入到另一个文件里。
package IO;
import java.io.*;
import java.util.Scanner;
public class Test02 {
public static void main(String[] args) {
//1.输入必要的信息
Scanner sc = new Scanner(System.in);
System.out.println("请输入要复制的源文件:");
String srcPath = sc.next();
System.out.println("请输入要复制的目标文件:");
String destPath = sc.next();
File srcFile = new File(srcPath);
if (!srcFile.isFile()) {
System.out.println("源文件路径有误");
return;
}
File destFile = new File(destPath);
if (!destFile.getParentFile().isDirectory()){
System.out.println("目标路径有误");
return;
}
try(InputStream inputStream = new FileInputStream(srcFile);
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 (IOException e) {
throw new RuntimeException(e);
}
}
}
3.3 实现文件复制
在目录中搜索,按照文件内容的方式搜索,用户输入一个目录,一个要搜索的词,遍历文件的过程中,如果文件包含了要搜索的词,此时就把文件的路径打印出来
package IO;
import java.io.*;
import java.util.Scanner;
public class Test03 {
public static void main(String[] args) {
//1.输入必要的信息
Scanner sc = new Scanner(System.in);
System.out.println("请输入要搜索的路径:");
String rootPath = sc.next();
System.out.println("请输入要查询的词:");
String word = sc.next();
File rootFile = new File(rootPath);
if (!rootFile.isDirectory()) {
System.out.println("输入的要搜索的路径不正确");
return;
}
scanDir(rootFile, word);
}
private static void scanDir(File rootFile, String word) {
File[] files = rootFile.listFiles();
if (files == null) {
return;
}
for (File file : files) {
System.out.println("当前遍历到:" + file.getAbsolutePath());
if (file.isFile()){
//在文件内容中搜索
searchInFile(file, word);
} else if (file.isDirectory()) {
//遍历递归
scanDir(file, word);
}else {
;
}
}
}
private static void searchInFile(File file, String word) {
//通过这个方法在文件内部进行搜索
//1.先读取文件内容
try(InputStream inputStream = new FileInputStream(file)) {
StringBuilder stringBuilder =new StringBuilder();
while (true){
byte[] buffer = new byte[1024];
int n = inputStream.read(buffer);
if (n == -1){
break;
}
//此处只是读取文件的一部分,需要把文件整体拼接在一起
String s = new String(buffer, 0, n);
stringBuilder.append(s);
}
//文件读取完毕,循环结束后,此时stringBuilder就是包含文件整个内容的字符串了
if(stringBuilder.indexOf(word) == -1){
return;
}
//找到了,直接打印文件的路径
System.out.println("找到了 "+word+"存在于 "+file.getAbsolutePath());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}