【JavaEE】-- 文件操作和IO
文章目录
- 1. 认识文件
- 1.1 树形结构组织和目录
- 1.1.1 几个常用的命令
- 1.2 文件路径
- 2. Java中操作文件
- 2.1 File概述
- 2.1.1 属性
- 2.1.2 构造方法
- 2.1.3 方法
- 2.1.3.1 观察get系列的特点和差异
- 2.1.3.2 普通文件的创建和删除
- 2.1.3.3
- 2.1.3.4
- 2.1.3.5
- 3. 文件内容的读写--数据流
- 3.1 InputStream概述
- 3.1.1 方法
- 3.1.2 说明
- 3.2 FileInputStream概述
- 3.2.1 构造方法
- 3.3 代码示例
- 3.4 OutputStream概述
- 3.4.1 方法
- 3.4.2 说明
- 3.5.1 OutputStreamWriter进行字符写入
- 3.6 输入字符流--FileReader
- 3.7 输出字符流--FileWriter
- 3.8 利用Scanner进行字符读取
- 3.9 PrinterWriter
- 3.10 处理输入输出流的关闭
- 4. 小程序练习
- 4.1 示例一
- 4.2 示例二
- 4.3 示例三
什么是IO
input和output的首字母
数据进入内存叫输入,数据从内存出去叫输出。
1. 认识文件
1. 狭义上的文件
针对硬盘这种持久化存储的i/O设备,当我们想要进行数据保存时,往往不是保存成一个整体,而是独立成一个个的单位进行保存,这个独立的单位就被抽象成文件的概念,就类似办公桌上的一份份真实的文件一般。
在磁盘中保存的文件,我们常用的:
- 自己写的文本
- 自己下载的程序,比如:QQ、WECHAT
- 系统自带的文件
- 特殊的文件,目录(文件夹)。
不同的文件可以用不同的后缀名去表示(.exe文件、.mp3视频文件、.txt文件…)。
2. 广义上的文件
操作系统的主要功能就是管理计算机设备,Linux系统中所有的计算机设置都是通过文件来描述的,包括键盘、显示器、打印机、网卡…
对于系统而言,文件包括硬件和软件。进程是申请资源的最小单位,当进程需要使用某一个文件时,用一个文件描述符来表示,并加入到文件描述符表中维护起来。当进程启动时会分配三个文件描述符:标准输入(input)、标准输出(output)、标准错误(error)。
1.1 树形结构组织和目录
目录时组织普通文件的,也就是我们通常说的文件夹,目录中也可以包含子目录。
1.1.1 几个常用的命令
cd命令改变所在目录
dir命令显示当前目录中的内容
tree–只显示目录结构,不显示普通文件
tree /F–显示目录结构,并显示普通文件
1.2 文件路径
D:\app-down-load\QQNT\QQ.exe
这是一个完整路径,可以通过正确的完整路径找到系统中任何一个文件。- 在windows中路径分隔符默认是反斜杠(\),但是在这个基础上也保留了斜杠(/),所以在windows中斜杠和反斜杠都是可以识别的,但是推荐使用/,因为使用\需要转义\。
- 在Linux系统中路径的分隔符默认是斜杠(/)。
- 当输入路径时,会先从当前目录下查找对应的文件,如果没有就会去环境变量配置中依次查找。
1. 绝对路径
D:\app-down-load\QQNT\QQ.exe
从盘符或根目录开始,每个子目录用分隔符隔开,一直到目标文件。
2. 相对路径
先要确认当前的工作目录,以工作目录为基准去找目标文件。
比如,现在我所处的目录是
d:\app-down-load\QQNT
,想要返回上一级目录就可以输入cd ..
..
就表示上一级目录
.
表示当前目录
还可以直接写程序名,默认在当前目录下查找
在代码中访问项目中包含的文件,建议使用相对路径,因为不同机器上的工作目录可能不同。
2. Java中操作文件
2.1 File概述
文件是操作系统中的概念,Java为不同的操作系统做了封装,提供了一个File类,来让Java程序员操作文件。
2.1.1 属性
修饰符及类型 | 属性 | 说明 |
---|---|---|
static String | pathSeparator | 依赖于系统的路径分隔符,String类型的表示 |
static Char | pathSeparatorChar | 依赖于系统的路径分隔符,Char类型的表示 |
static String | separator | 目录的分隔符。String类型的表示 |
static Char | separatorChar | Char类型的表示 |
public class Demo101 {public static void main(String[] args) {System.out.println(File.separator);System.out.println(File.pathSeparator);System.out.println("============");System.out.println(File.separatorChar);System.out.println(File.pathSeparatorChar);}
}
输出结果:
\
;
============
\
;
输出的
\
是目录的分隔符。
输出的;
是多个路径之间的分隔符。windows中是;
,Linux中是:
。
2.1.2 构造方法
文件对象,类似于Thread对象,是Java层面的 ,不是系统中的文件,只是对文件的一个描述和定义。
签名 | 说明 |
---|---|
File(File parent, String child) | 根据父目录+孩子文件路径,创建一个新的File实例 |
File(String pathname) | 根据文件路径创建一个新的File实例,路径可以是绝对路径或者相对路径 |
File(String parent, String child) | 根据父目录+孩子文件路径,创建一个新的File实例,父目录用路径表示 |
public class Demo02_Constructor {public static void main(String[] args) {File file = new File("d:/temp");File file1 = new File(file, "test/hello.txt");System.out.println(file);System.out.println(file1);System.out.println("===========================");File file2 = new File("d:\\temp\\test\\hello.txt");System.out.println(file2);System.out.println("===========================");File file3 = new File("d:/temp", "test/hello.txt");System.out.println(file3);}
}
输出结果:
d:\temp
d:\temp\test\hello.txt
===========================
d:\temp\test\hello.txt
===========================
d:\temp\test\hello.txt
2.1.3 方法
2.1.3.1 观察get系列的特点和差异
修饰符及返回值类型 | 方法签名 | 说明 |
---|---|---|
String | getParent() | 返回File对象的父目录文件路径 |
String | getName() | 返回FIle对象的纯文件名称 |
String | getPath() | 返回File对象的文件路径 |
String | getAbsolutePath() | 返回File对象的绝对路径 |
String | getCanonicalPath() | 返回File对象的修饰过的绝对路径 |
public class Demo03_get {public static void main(String[] args) throws IOException {File file = new File("d:/temp/test/hello.txt");System.out.println(file.getPath());//文件路径System.out.println(file.getParent());//父目录System.out.println(file.getName());//文件名System.out.println(file.getAbsolutePath());//绝对路径System.out.println(file.getCanonicalPath());//标准形式的路径System.out.println("=============================");File file1 = new File("./hello.txt");System.out.println(file1.getPath());//文件路径System.out.println(file1.getParent());//父目录System.out.println(file1.getName());//文件名System.out.println(file1.getAbsolutePath());//绝对路径System.out.println(file1.getCanonicalPath());//标准形式的路径}
}
运行结果:
2.1.3.2 普通文件的创建和删除
修饰符及返回值类型 | 方法签名 | 说明 |
---|---|---|
boolean | exists() | 判断File对象描述的文件是否真实存在 |
boolean | isDirectory() | 判断File对象代表的文件是否是一个目录 |
boolean | isFile() | 判断 File 对象代表的文件是否是-个普通文件 |
boolean | createNewFile() | 根据File 对象,自动创建一个空文件。成功创建后返回true |
boolean | delete() | 根据File对象,删除该文件。成功删除后返回true |
public class Demo104_create {public static void main(String[] args) throws IOException {File file = new File("./hello.txt");System.out.println(file.exists());//falseSystem.out.println(file.createNewFile());//trueSystem.out.println(file.exists());//trueSystem.out.println(file.isDirectory());//falseSystem.out.println(file.isFile());//true}
}
删除file文件
2.1.3.3
修饰符及返回值类型 | 方法签名 | 说明 |
---|---|---|
String[] | list() | 返回File对象代表的目录下的所有文件名 |
File[] | listFiles() | 返回File 对象代表的目录下的所有文件,以File 对象表示 |
public class Demo105 {public static void main(String[] args) {File file = new File("d:/temp");System.out.println(Arrays.toString(file.list()));System.out.println(Arrays.toString(file.listFiles()));}
}
输出结果:
[abc.txt, hello, Meme]
[d:\temp\abc.txt, d:\temp\hello, d:\temp\Meme]
2.1.3.4
修饰符及返回值类型 | 方法签名 | 说明 |
---|---|---|
boolean | mkdir() | 创建File对象代表的目录 |
boolean | mkdirs() | 创建File 对象代表的目录,如果必要,会创建中间目录 |
public class Demo106 {public static void main(String[] args) {File file = new File("d:/temp/haha");File file1 = new File("d:/temp/haha1/haha11/haha111");System.out.println(file.mkdir());//trueSystem.out.println(file1.mkdir());//falseSystem.out.println(file1.mkdirs());//true}
}
2.1.3.5
修饰符及返回值类型 | 方法签名 | 说明 |
---|---|---|
boolean | renameTo(File dest) | 进行文件改名,也可以视为我们平时的剪切、粘贴操作 |
boolean | canRead() | 判断用户是否对文件有可读权限 |
boolean | canWrite() | 判断用户是否对文件有可写权限 |
public class Demo107 {public static void main(String[] args) {File file = new File("d:/temp/abc.txt");File file1 = new File("d:/temp/123.txt");if (file.exists()){System.out.println(file.renameTo(file1));//true}}
}
读和写
public class Demo108 {public static void main(String[] args) {File file = new File("d:/temp/123.txt");System.out.println(file.canRead());//trueSystem.out.println(file.canWrite());//true}
}
修改读写权限:
public class Demo108 {public static void main(String[] args) {File file = new File("d:/temp/123.txt");System.out.println(file.canRead());//trueSystem.out.println(file.canWrite());//false}
}
3. 文件内容的读写–数据流
所有的文件操作都是以内存为中心的。
Java中针对输入流和输出流定义了两个抽象类(输出流–InputStream–读取操作、输出流–OutputStream–写入操作)。
在系统中有很多种文件类型,大致分为两类:1. 文本文件类型;2. 二进制文件类型。
所有的文件都可以当作二进制文件来读取,Java针对文本文件也提供了相应的类。
问题:A 、我 是不是字符?A 和 我 分别占用多少字节?
是。A占1byte;我 主要看使用了哪种字符编码集(ASCII码一个字符占用一个字节、UTF8中一个汉字占用2~4个字节)。
3.1 InputStream概述
3.1.1 方法
修饰符及返回值类型 | 方法签名 | 说明 |
---|---|---|
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() | 关闭字节流 |
3.1.2 说明
InputStream只是一个抽象类,要使用还需要具体的实现类。关于InputStream的实现类有很多,基本可以认为不同的输入设备都可以对应一个InputStream类,我们现在只关心从文件中读取,所以使用FilelnputStream
3.2 FileInputStream概述
3.2.1 构造方法
签名 | 说明 |
---|---|
FileInputStream(File file) | 利用File构造文件输入流 |
FileInputStream(String name) | 利用文件路径构造文件输入流 |
public class Demo201 {public static void main(String[] args) throws FileNotFoundException {File file = new File("d:/temp/abc.txt");FileInputStream inputStream = new FileInputStream(file);FileInputStream inputStream1 = new FileInputStream("d:/temp/abc.txt");}
}
3.3 代码示例
public class Demo202_FileInputStream {public static void main(String[] args) throws IOException {FileInputStream inputStream = new FileInputStream("d:/temp/abc.txt");while (true){int read = inputStream.read();if (read == -1){break;}System.out.println(read);}}
}
输出结果:
97
98
99
13
10
230
136
145
但是由于read()方法每次读取一个字节,如果文件很大,那么就需要读取很多次,也就意味着需要大量的IO,对性能有很大的影响,为了提升效率,可以指定每次读多少个字节。
public class Demo203 {public static void main(String[] args) throws IOException {FileInputStream inputStream = new FileInputStream("d:/temp/abc.txt");byte[] bytes = new byte[1024];while (true) {//注意该代码只能放在循环中,如果放在循环外面,那么就会重复处理第一次读取的数据,//放在循环中,每次都会向后读取新的数据,所以能处理任意大的文件int len = inputStream.read(bytes);if (len == -1){break;}for (int i = 0; i < len; i++) {System.out.println(bytes[i]);}}inputStream.close();}
}
输出结果:
97
98
99
13
10
-26
-120
-111
在read()方法中对数组进行了修改,在方法外可以访问到修改过后的内容,这类参数称为输出型参数
为什么要在使用完输入流之后调用close()方法?
在创建一个文件对象时,会申请文件资源,系统中使用文件描述符表示程序使用哪个文件,再用一个文件描述符表来组织文件描述符,当打开一个文件时就会往文件描述符表中加入一个表示当前文档的元素。文件描述符表本质上是一个数组。极端情况下,文件描述符表会有内存不够用的情况,为了解决这个问题,当文件使用完之后,需要把对应的文件描述读从数组中删除。
3.4 OutputStream概述
3.4.1 方法
修饰符及返回值类型 | 方法签名 | 说明 |
---|---|---|
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(刷新)操作,将数据刷到设备中。 |
3.4.2 说明
OutputStream同样只是一个抽象类,要使用还需要具体的实现类。我们现在还是只关心写入文件中,所以使用FileOutputStream
3.5.1 OutputStreamWriter进行字符写入
说明:abc.txt文件是空白的
public class Demo204 {public static void main(String[] args) throws IOException {FileOutputStream outputStream = new FileOutputStream("d:/temp/abc.txt");outputStream.write(97);outputStream.write(98);outputStream.write(99);outputStream.write(13);outputStream.write(10);outputStream.write(230);outputStream.write(136);outputStream.write(145);outputStream.flush();outputStream.close();}
}
运行之后:
问:如果再次运行程序,文档中的内容是不变还是累加?
答:不变。在打开文件之后会把之前的内容全部清空,所以再次运行程序的时候,会重新将内容写进去。
3.6 输入字符流–FileReader
public class Demo205 {public static void main(String[] args) throws IOException {FileReader reader = new FileReader("d:/temp/abc.txt");while (true) {int data = reader.read();if (data == -1){break;}System.out.println(data);}reader.close();}
}
输出结果:
3.7 输出字符流–FileWriter
public class Demo206 {public static void main(String[] args) throws IOException {FileWriter writer = new FileWriter("d:/temp/abc.txt");writer.write('a');writer.write('b');writer.write('c');writer.write('\r');writer.write('\n');writer.write('我');writer.flush();writer.close();}
}
3.8 利用Scanner进行字符读取
public class Demo207 {public static void main(String[] args) throws IOException {FileInputStream inputStream = new FileInputStream("d:/temp/abc.txt");Scanner scanner = new Scanner(inputStream, "UTF-8");while (true) {if (!scanner.hasNext()){break;}String line = scanner.nextLine();System.out.println(line);}scanner.close();inputStream.close();}
}
输出结果:
abc
我
3.9 PrinterWriter
public class Demo208_PrinterWriter {public static void main(String[] args) throws IOException {FileOutputStream outputStream = new FileOutputStream("d:/temp/abc.txt");PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println("hello word, 你好,世界!!!");printWriter.println("嘿嘿!!!");printWriter.printf("%s:%d", "haha", 7);printWriter.flush();printWriter.close();outputStream.close();}
}
3.10 处理输入输出流的关闭
之前我们都是在定义输入输出流并使用完之后会单独调用close()方法来关闭流
//实现读写操作,定义输入流和输出流
FileInputStream inputStream = new FileInputStream(sourceFile);
FileOutputStream outputStream = new FileOutputStream(destFile);//关闭流
inputStream.close();
outputStream.close();
但是我们在写代码时可能会经常忘记关闭流,那么就可能会造成很大的资源损耗。
我们观察到InputStream这个抽象类中实现了Closeable接口,那么可以通过下面的方式来进行处理,它会自动的帮我们关闭流:
try(FileInputStream inputStream = new FileInputStream(sourceFile);FileOutputStream outputStream = new FileOutputStream(destFile);){
} catch (FileNotFoundException e) {throw new RuntimeException(e);
} catch (IOException e) {throw new RuntimeException(e);
}
我们只需要将定义输入输出流的语句写在try后面的小括号中,然后将一些使用资源的业务代码放在try的大括号中,Java会在自动执行完try块之后调用这些资源的close()方法。
4. 小程序练习
4.1 示例一
**要求:**扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件.
package lesson03;import java.io.File;
import java.io.IOException;
import java.util.Scanner;public class Demo301_FindByName {public static void main(String[] args) throws IOException {Scanner scanner = new Scanner(System.in);System.out.println("请输入要扫描的目录的路径。");String str = scanner.nextLine();File file = new File(str);if (!file.exists()){System.out.println("输入的路径不存在,请重新输入!");return;}if (!file.isDirectory()) {System.out.println("输入的路径不是一个目录,请重新输入!");return;}System.out.println("请输入要查询的字符。");String key = scanner.nextLine();if (key.isEmpty() || key == "") {System.out.println("输入的字符串不合法,请重新输入!");return;}scan(file, key);}private static void scan(File file, String key) throws IOException {File[] files = file.listFiles();if (files.length == 0){return;}for (File file1:files) {if (file1.isFile()){String name = file1.getName();if (name.contains(key)) {System.out.println("找到文件:" + file1.getCanonicalPath() + "包含关键字" + key + "是否要删除?(Y/N)");Scanner scanner = new Scanner(System.in);String s = scanner.nextLine();if (s.toLowerCase() == "y"){file1.delete();System.out.println("已删除文件:" + file1.getCanonicalPath());}}}else {//如果是目录scan(file1, key);}}}
}
以下面这个目录来对程序进行测试:
控制台输出结果:
4.2 示例二
要求: 进行普通文件的复制
看到这个需求要考虑一个问题:用字节流还是字符流?
用字节流,这样可以操作任何类型的文件。
package lesson03;import java.io.*;
import java.util.Scanner;public class Demo302_Copy {public static void main(String[] args) {System.out.println("请输入源文件的路径。");Scanner scanner = new Scanner(System.in);String source = scanner.nextLine();File sourceFile = new File(source);//判断源文件的路径是否有效if (!sourceFile.exists()){System.out.println("输入的源文件的路径不存在,请重新输入!");return;}if (!sourceFile.isFile()){System.out.println("输入的源文件不是一个普通文件,请重新输入!");return;}//判断目标文件的路径是否有效System.out.println("请输入目标路径。");String dest = scanner.nextLine();File destFile = new File(dest);if (!destFile.getParentFile().exists()){System.out.println("输入的目标文件的父目录不存在,请重新输入!");return;}if (destFile.exists()){System.out.println("输入的目标路径已存在。");}// //实现读写操作,定义输入流和输出流
// FileInputStream inputStream = new FileInputStream(sourceFile);
// FileOutputStream outputStream = new FileOutputStream(destFile);
//
// //关闭流
// inputStream.close();
// outputStream.close();try(InputStream inputStream = new FileInputStream(sourceFile);OutputStream outputStream = new FileOutputStream(destFile);){byte[] bytes = new byte[1024];while (true) {int len = inputStream.read(bytes);if (len == -1){break;}outputStream.write(bytes, 0, len);}outputStream.flush();} catch (IOException e) {throw new RuntimeException(e);}}
}
源文件路径:
目标文件的路径:
控制台输出结果:
目标文件:
4.3 示例三
**要求:**扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)
package lesson03;import java.io.*;
import java.util.Scanner;public class Demo303_FindByContent {public static void main(String[] args) throws IOException {//1.获取用户输入的要扫描的路径System.out.println("请输入要扫描的目录。");Scanner scanner = new Scanner(System.in);String rootPath = scanner.nextLine();File rootFile = new File(rootPath);//2. 校验路径是否符合要求if (!rootFile.exists()){System.out.println("该路径不存在,请重新输入!");return;}if (!rootFile.isDirectory()){System.out.println("该文件不是一个目录,请重新输入!");return;}//3. 用户输入关键字System.out.println("请输入要查找的关键字。");String keyword = scanner.nextLine();//4. 校验关键字if (keyword.isEmpty() || keyword == null){System.out.println("输入的关键字不能为空。");return;}//5. 循环扫描指定路径下的文件scan(rootFile, keyword);}private static void scan(File rootFile, String keyword) throws IOException {File[] files = rootFile.listFiles();if (files.length == 0){return;}for (File file1:files) {if (file1.isFile()) {boolean check = check(file1, keyword);if (check) {System.out.println("找到文件:" + file1.getCanonicalPath() + "包含关键字:"+ keyword + "是否要删除该文件?(Y/N)");Scanner scanner = new Scanner(System.in);String opt = scanner.nextLine();if (opt.toLowerCase() == "y") {file1.delete();}}else {continue;}}else {//如果是目录scan(file1, keyword);}}}private static boolean check(File file1, String keyword) throws IOException {//判断文件名if (file1.getName() == keyword) {return true;}//判断内容try(InputStream inputStream = new FileInputStream(file1)){StringBuilder stringBuilder = new StringBuilder();Scanner scanner = new Scanner(inputStream);while (true) {if (!scanner.hasNextLine()){break;}stringBuilder.append(scanner.nextLine());}if (stringBuilder.indexOf(keyword) > -1) {return true;}} catch (IOException e) {throw new RuntimeException(e);}return false;}
}