一、File文件/夹操作
在计算机系统中,文件是非常重要的存储方式。Java的标准库java.io
提供了File
对象来操作文件和目录。
要构造一个File
对象,需要传入文件路径:
public class Main {
public static void main(String[] args) {
File f = new File("C:\\Windows\\notepad.exe");
System.out.println(f); //C:\Windows\notepad.exe
}
}
构造File对象时,既可以传入绝对路径,也可以传入相对路径。绝对路径是以根目录开头的完整路径,例如:
File f = new File("C:\\Windows\\notepad.exe");
注意Windows平台使用\
作为路径分隔符,在Java字符串中需要用\\
表示一个\
。Linux平台使用/
作为路径分隔符:
File f = new File("/usr/bin/javac");
传入相对路径时,相对路径前面加上当前目录就是绝对路径:
// 假设当前目录是C:\Docs
File f1 = new File("sub\\javac"); // 绝对路径是C:\Docs\sub\javac
File f3 = new File(".\\sub\\javac"); // 绝对路径是C:\Docs\sub\javac
File f3 = new File("..\\sub\\javac"); // 绝对路径是C:\sub\javac
可以用.
表示当前目录,..
表示上级目录。
File对象有3种形式表示的路径,一种是getPath()
,返回构造方法传入的路径,一种是getAbsolutePath()
,返回绝对路径,一种是getCanonicalPath
,它和绝对路径类似,但是返回的是规范路径。
什么是规范路径?我们看以下代码:
public class Main {
public static void main(String[] args) throws IOException {
File f = new File("..");
System.out.println(f.getPath()); //..
System.out.println(f.getAbsolutePath()); // /app/..
System.out.println(f.getCanonicalPath()); // /
}
}
绝对路径可以表示成C:\Windows\System32\..\notepad.exe
,而规范路径就是把.
和..
转换成标准的绝对路径后的路径:C:\Windows\notepad.exe
。
因为Windows和Linux的路径分隔符不同,File对象有一个静态变量用于表示当前平台的系统分隔符:
System.out.println(File.separator); // 根据当前平台打印"\"或"/"
1.文件和目录
File
对象既可以表示文件,也可以表示目录。特别要注意的是,构造一个File
对象,即使传入的文件或目录不存在,代码也不会出错,因为构造一个File
对象,并不会导致任何磁盘操作。只有当我们调用File
对象的某些方法的时候,才真正进行磁盘操作。
例如,调用isFile()
,判断该File
对象是否是一个已存在的文件,调用isDirectory()
,判断该File
对象是否是一个已存在的目录:
public class Main {
public static void main(String[] args) throws IOException {
File f1 = new File("C:\\Windows");
File f2 = new File("C:\\Windows\\notepad.exe");
//是文件?
System.out.println(f1.isFile()); //false
//是目录?
System.out.println(f1.isDirectory()); //false
System.out.println(f2.isFile());//false
System.out.println(f2.isDirectory());//false
}
}
用File
对象获取到一个文件时,还可以进一步判断文件的权限和大小:
boolean canRead()
:是否可读;boolean canWrite()
:是否可写;boolean canExecute()
:是否可执行;long length()
:文件字节大小。
2.创建和删除文件
当File对象表示一个文件时,可以通过createNewFile()
创建一个新文件,用delete()
删除该文件:
File file = new File("/path/to/file");
file.createNewFile()
file.delete()
有些时候,程序需要读写一些临时文件,File对象提供了createTempFile()
来创建一个临时文件,以及deleteOnExit()
在JVM退出时自动删除该文件。
File f = File.createTempFile("tmp-", ".txt"); // 提供临时文件的前缀和后缀
f.deleteOnExit(); // JVM退出时自动删除
System.out.println(f.isFile()); //true
System.out.println(f.getAbsolutePath()); ///tmp/tmp-11900775370391459620.txt
3.遍历文件和目录
当File对象表示一个目录时,可以使用list()
和listFiles()
列出目录下的文件和子目录名。listFiles()
提供了一系列重载方法,可以过滤不想要的文件和目录:
public class Main {
public static void main(String[] args) throws IOException {
File f = new File("C:\\Windows");
File[] fs1 = f.listFiles(); // 列出所有文件和子目录
printFiles(fs1);
File[] fs2 = f.listFiles(new FilenameFilter() { // 仅列出.exe文件
public boolean accept(File dir, String name) {
return name.endsWith(".exe"); // 返回true表示接受该文件
}
});
printFiles(fs2);
}
static void printFiles(File[] files) {
System.out.println("==========");
if (files != null) {
for (File f : files) {
System.out.println(f);
}
}
System.out.println("==========");
}
}
和文件操作类似,File对象如果表示一个目录,可以通过以下方法创建和删除目录:
boolean mkdir()
:创建当前File对象表示的目录;boolean mkdirs()
:创建当前File对象表示的目录,并在必要时将不存在的父目录也创建出来;boolean delete()
:删除当前File对象表示的目录,当前目录必须为空才能删除成功。4.Path
Java标准库还提供了一个
Path
对象,它位于java.nio.file
包。Path
对象和File
对象类似,但操作更加简单:Path p1 = Paths.get(".", "project", "study"); // 构造一个Path对象
System.out.println(p1);
Path p2 = p1.toAbsolutePath(); // 转换为绝对路径
System.out.println(p2);
Path p3 = p2.normalize(); // 转换为规范路径
System.out.println(p3);
File f = p3.toFile(); // 转换为File对象
System.out.println(f);
for (Path p : Paths.get("..").toAbsolutePath()) { // 可以直接遍历Path
System.out.println(" " + p);
}
如果需要对目录进行复杂的拼接、遍历等操作,使用
Path
对象更方便。
小结:
Java标准库的java.io.File
对象表示一个文件或者目录:创建
File
对象本身不涉及IO操作;- 可以获取路径/绝对路径/规范路径:
getPath()
/getAbsolutePath()
/getCanonicalPath()
; - 可以获取目录的文件和子目录:
list()
/listFiles()
; - 可以创建或删除文件和目录。
5.练习
请利用File
对象列出指定目录下的所有子目录和文件,并按层次打印。
例如,输出:
如果不指定参数,则使用当前目录,如果指定参数,则使用指定目录。Documents/
word/
1.docx
2.docx
work/
abc.doc
ppt/
other/
6.总结
6.1 文件句柄方法
File file = new File("c:/qf/abc/小姐姐.txt");
//操作
file.createNewFile();// 创建文件
file.getParentFile();// 获取到上级目录句柄
file.getParent(); //获取到上级目录字符串
file.mkdir();// 创建文件夹
file.mkdirs();// 创建文件夹(多级文件夹,推荐!!!)
file.renameTo(new File("新名字"));// 重命名
file.delete();// 删除文件
// 查看
System.out.println(file.exists()); // 判断文件是否存在
System.out.println(file.isAbsolute()); // 是否是绝对路径
System.out.println(file.isDirectory()); // 是否是文件夹
System.out.println(file.isFile()); // 是否是文件
System.out.println(file.getName()); //获取文件名字
System.out.println(file.length()); // 文件大小
6.2 创建文件的步骤
File file = new File("c:/qf/abc/小姐姐.txt");
// 首先, 判断文件夹是否存在
File parentFile = file.getParentFile();
if(!parentFile.exists() || parentFile.isFile() ){
parentFile.mkdirs();// 创建上级目录
}
file.createNewFile(); // 创建文件
二、基本IO流
上一节, 我们可以创建文件和删除文件, 也就是说, 我们可以对文件进行操作了. 但是文件的内容还没办法读取和写入. 本节, 咱们就说说这个文件怎么读取和写入. 在java中读写文件使用IO流来完成.
流的分类:
- 按照读写内容的单位来讲, 分为字节流和字符流
- 按照读写的方向来讲, 分为输入流和输出流
- 按照流的功能不同, 分为节点流和处理流
懵逼了吧. 哈哈. 别担心. 我们一个一个看.
1.站在程序的角度. 从文件里读取内容,这个叫输入流,向文件里写入内容叫输出流。
2.对于英文而言我们之前说过, 英文是ascii的范畴。一个ascii是一个字节。所以字节流一次读取的是一个字节,这样的流是可以处理英文的,但是如果是中文就不行了,中文必须用字符流。
3.节点流主要指的是直接插在文件上的流,在文件上开辟一个节点来读取内容。而处理流是用来处理其他流的,就好比我们家的自来水净化器,它是套在我们正常的自来水管道上的。我们从净化器接的水是被净化器处理过的,一样. 从处理流获取到的数据是经过处理的。
接下来,我们来说说流的家族体系。不管是什么流,最终都是基于这个体系产生的。
流一共有4个祖宗.
输入 | 输出 | |
---|---|---|
字节流 | InputStream | OutputStream |
字符流 | Reader | Writer |
万变不离其宗, 所有的流在使用上基本上都是从这几个流过来的. 但是很不幸, 这几个流都是抽象类. 我们不能直接创建这几个类的对象. 所以, 我们必须学习他们的子类间接的来学习流。
节点流:文件流
FileInputStream
FileOutputStream
FileReader
FileWriter
1.文件字节输入流
FileInputStream
public static void main(String[] args) {
try {
//1.以字节流形式打开文件
InputStream fis = new FileInputStream(new File("aa.txt"));
//每次读取一个字节
// int b = fis.read(); //每次读取1个字节
// System.out.println(b); //第1个字节 在ASCII中数字表示
// System.out.println((char)b);
// int b2 = fis.read(); //每次读取2个字节
// System.out.println(b2); //第2个字节 在ASCII中数字表示
// System.out.println((char)b2);
//2.读取一定长度字节,read() 传入一定长度的字节数组
byte[] bs = new byte[1024];
int len=0;
while ((len = fis.read(bs)) != -1){ // 当文件内容读完之后 fis.read() == -1
System.out.println(new String(bs,0,len)); // 字节数组中不全是从文件里读取到的内容(如果文件内容少于1024 剩余位置用null占位)
}
//3.关闭文件
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
2.文件字节输出流
FileOutputStream
构造方法
成员方法
我们发现, 中文可以写出去. 原因是, 我们按照字节的形式写出去. 到了文件里. 又被拼凑成正常的文字了. 所以可以看见中文. 但是read就不行了. read读到的是字节. 对于GBK而言, 你每次读取的其实就是半个字. 没办法显示的. 先更要读取中文. 必须用字符流public static void main(String[] args) {
try {
//准备工作-创建文件
File fw = new File("./notebooks/aa.txt");
File parentFile = fw.getParentFile();
if(!parentFile.exists()){
parentFile.mkdirs();
}
fw.createNewFile();
//1.以字节流形式打开文件
OutputStream fos = new FileOutputStream(fw);
//OutputStream fos = new FileOutputStream("./1.jpg"); //可以直接传入文件路径
//2.读取一定长度字节,read() 传入一定长度的字节数组
byte[] bs = "我是中国人,黄皮肤,棕色眼睛,我是龙的传人".getBytes();
fos.write(bs);
//3.写入文件
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
3.文件字符输入流
FileReader
构造方法
FileReader(File file)
创建一个新的FileReader
,给出要读取的File
对象。FileReader(String fileName)
创建一个新的FileReader
,给定要读取的文件名称。成员方法
close()
关闭流并释放与之相关联的任何系统资源int len = read()
读一个字符int len = read(char[] cbuf, int offset, int length)
将字符读入数组的一部分。
聪明的你应该已经发现了. 这个Reader和InputStream的代码很像. 除了byte和char数组的区别. 其他写法一模一样. 这就对了. 这两个功能本来就是一样的. 区别就是单位的不同. 所以. 关于IO流的代码. 记住一份足以. 其他的照葫芦画瓢就行了.public static void main(String[] args) {
try {
//1.以字符形式打开文件
Reader file_reader = new FileReader(new File("aa.txt"));
//2.读取一定长度字节,read() 传入一定长度的字符数组
char[] temp = new char[1024];
int len=0;
while ((len = file_reader.read(temp)) != -1){ // 当文件内容读完之后 fis.read() == -1
System.out.println(new String(temp,0,len)); // 字符流能读取中文
}
//3.关闭文件
file_reader.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
4.文件字符输出流
FileWriter
练习:使用IO流, 把一张图片从aa文件夹复制到bb文件夹(不存在就先创建).(附加思考: 如果是剪切呢?)public static void main(String[] args) {
try {
//准备工作-创建文件
File fw = new File("./notebooks/aa.txt");
File parentFile = fw.getParentFile();
if(!parentFile.exists()){
parentFile.mkdirs();
}
fw.createNewFile();
//1.以字节流形式打开文件
Writer file_writer = new FileWriter(fw,true); //append 追加写入
//2.直接写入字符串
file_writer.write("\r\n我是下一行数据追加!"); //换行
//3.写入文件
file_writer.flush();
file_writer.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
总结:public static void main(String[] args) throws Exception {
/*使用IO流, 把一张图片从aa文件夹复制到bb文件夹(不存在就先创建).(附加思考: 如果是剪切呢?)*/
File f_source = new File("./aa/meinv.jpg");
InputStream fis = new FileInputStream(f_source);
//写byte
File file = new File("./bb/new_meinv.jpg");
File parentFile = file.getParentFile();
if(!parentFile.exists()){
parentFile.mkdirs();
}
file.createNewFile();
OutputStream fos = new FileOutputStream(file);
byte[] bs = new byte[1024];
int len = 0;
while ((len = fis.read(bs))!= -1){
fos.write(bs,0,len);
}
//剪切也简单-拷贝完之后直接删除fis即可
f_source.delete();
fos.flush();
fos.close();
System.out.println("拷贝完成!");
}
1.文档(含中英文)操作,使用字符流FileReader-Reader
FileWriter-Writer
,可以直接读写字符串;
2.音视频/压缩文件等操作,使用字节流FileInputStream-InputStream
FileOutputStream-OutStream
,read(bs)
write(bs,0,len)
需要传入字节长度数组;三、处理流
1.概念:
上节课我们还学了文件流(即节点流):FileInputStream
,FileOutputStream
,FileReader
,FileWriter
以上流都是节点流,这节课我们来探索一下处理流。
前面我们说过, 从文件中读取内容相当于在文件上插了一个管子,在管子上进行读写数据,就好比我们喝奶茶的时候插根管子从管子里喝。
我们可以在这个管道的基础上进一步的进行处理,处理管道的叫处理流。说白了. 在管道上再套一根管道.
此时读取和写入的内容就会自动被处理流给处理一下.
处理流的分类:
- 缓冲流
- 转换流
- 对象流
2.缓冲流BufferedReader
| | 输入 | 输出 | | —- | —- | —- | | 字节流 |BufferedInputStream
|BufferedOutputStream
| | 字符流 |BufferedReader
|BufferedWriter
|
发现规律了么? 还是4个,没错你把最原始的那四个记住了. 基本上流也就掌握了.
我们发现, BufferedInputStream除了在创建对象的时候多套弄了一层以外, 好像没有什么实质的进展. 不好用. 对于BufferedOutputStream其实也是一样的。所以这两个类一般不怎么使用,关键在BufferReader
上
public static void main(String[] args) throws Exception {
BufferedReader bf = new BufferedReader(new FileReader(new File("麻花藤.txt")));
// String str = bf.readLine(); // 一次读取一行内容. 爽啊. 终于不用再管神马byte[] 了
// System.out.println(str);
String str = "";
while((str = bf.readLine())!=null){ // 读取文本文件最爽的一种写法
System.out.println(str);
}
}
3.转换流
我们已经可以使用流的基本操作来完成读写任务了. 但是, 还有一个事情我们需要解决. 在很多情况下. 我们是拿不到字符流的。就比如System.in
. 这个就是一个字节流。它默认连接着控制台,那我们如何从System.in中读取到数据呢? 我么可以使用转换流,把System.in这种字节流转换成字符流,这样读取数据的时候就方便很多。
转换流有两个, 一个是InputStreamReader
, 另一个是OutputStreamWriter
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println(br.readLine());
运行起来, 感觉和Scanner是一样的.
同理. System.out是一个输出流. 我们就可以把System.out转换成Writer
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
bw.write("我的天啊");
bw.newLine(); //换行
bw.flush();
bw.close();
System.out.println("hahaaha!"); //无法显示
注意, System.out不可以关闭,关闭了 后面我们就不能再打印了
4.对象流
在java中, 万事万物皆为对象, 那我们能不能把一个对象写入到文件里呢? 答案是肯定的. 如何操作呢? 我们需要用到对象流ObjectStream
public static void main(String[] args) throws Exception {
//创建对象
Teacher t = new Teacher(1, "蔡徐坤", 88);
// 创建对象输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("teacher.dat")));
oos.writeObject(t); // 把一个对象写入到一个文件中
oos.flush();
oos.close();
}
代码写完了. 运行, 发现报错了. 为什么呢? 先看看这个错误是什么?
Exception in thread “main” java.io.NotSerializableException: com.xyq.io.Teacher at java.base/java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1185) at java.base/java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:349) at com.xyq.io.Test.main(Test.java:14)
NotSerializableException
: 表示没有开启序列化功能。什么是序列化呢? 我们知道我们创建的对象是保存在内存里的, 数据内存的数据, 而内存和硬盘里的数据保存方式是不一样的. 对于硬盘而言, 只有两种格式, 要么是字符, 要么是字节. 就这两种保存方式, 所以, 我们想要把对象写入到硬盘, 就必须把一个对象转换成字节或者字符。
对于一个对象而言, 转换成字符很显然不是很合适, 那把一个对象转换成字节的过程被称之为序列化, 并且, 序列化的内容将来还可能会被反序列化回我们的对象. JDK为每一个类都准备了序列化功能. 但是由于序列化太过于消耗资源. 默认都是不开启的. 如果想要开启该功能. 需要让你的类实现Serializable接口,这是一个标志性接口, 并没有方法在里面(即不需要重写方法),当JDK发现你的类实现了这个接口了,就意味着你要序列化了。所以. 大家以后只要看到了这个错误. 直接实现一个接口即可。
public class Person implements Serializable {
测试通过…来, 看看这个文件里写的是什么把
我们发现这个文件里好多都是乱码. 隐隐约约能看到蔡徐坤。所以. 记住, 这个文件不是给人看的. 是给程序用的. 那程序怎么用呢? 很简单. 你能写, 就一定能读. 对吧. 写用ObjectOutputStream
. 那读一定是ObjectInputStream
Person.java
public class Person implements Serializable {
private String name;
private int age;
private String gender;
public Person(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
}
对象写入:
public static void main(String[] args) throws Exception {
Person p1 = new Person("刘德华",18,"男");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.dat"));
oos.writeObject(p1);
oos.flush();
oos.close();
}
对象读取:
public static void main(String[] args) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("person.dat")));
Object obj = ois.readObject();
Person p1 = (Person) obj;
System.out.println(p1.getName()); //刘德华
ois.close();
}
四、文件修改
1.替换txt文件关键字
把文件”唐诗三百首.txt”文件中所有的李白替换成李太白.
唐诗三百首
1. 静夜思, 李白, 窗前明月光, 疑是地上霜, 举头望明月, 低头思故乡
2. 望庐山瀑布, 李白, 日照香炉生紫烟, 遥看瀑布挂前川, 飞流直下三千尺, 疑是银河落九天.
3. 早发白帝城, 李白, 朝辞白帝彩云, 千里江陵一日还, 两岸猿声啼不住, 轻舟已过万重山.
结果:
唐诗三百首
1. 静夜思, 李太白, 窗前明月光, 疑是地上霜, 举头望明月, 低头思故乡
2. 望庐山瀑布, 李太白, 日照香炉生紫烟, 遥看瀑布挂前川, 飞流直下三千尺, 疑是银河落九天.
3. 早发白帝城, 李太白, 朝辞白帝彩云, 千里江陵一日还, 两岸猿声啼不住, 轻舟已过万重山.
用咱们现在所知道的知识点还无法直接解决. 为什么会这样呢? 我们知道硬盘是一个连续的存储结构. 假设被写入到磁盘的是这样的:
此时我想把”叫”改成”朋友”, 那么假设可以直接修改. 就会变成这样
所以, 想要实现文件修改功能. 不可以在原有的基础上进行修改. 那怎么办? 我们可以准备一个文件的副本. 把新内容写到副本文件里. 写完之后, 把副本变成原文件. 并把源文件删除. 如果这个动作够快. 大家看到的就是文件修改的效果. 我们平时使用的word就是这样操作的. 所以我们每次操作word文档的时候都能看到一个文件副本出现. 一保存, 文件副本不见了. 其实是把文件副本变成了源文件
public static void main(String[] args) throws Exception {
File f1 = new File("./唐诗三百首.txt");
File f2 = new File("./副本_唐诗三百首.txt");
BufferedReader br = new BufferedReader(new FileReader(f1));
BufferedWriter bw = new BufferedWriter(new FileWriter(f2));
String line = "";
while ((line = br.readLine()) != null){
line = line.replace("李太白","【李太白】");
bw.write(line); //写到新文件
bw.newLine(); //换行
}
br.close();
bw.flush();
bw.close();
//删除源文件
f1.delete();
f2.renameTo(f1);
}
2.计算水果价钱
再来看另一个案例,读取文件中的水果信息. 计算水果的总价(价格x库存). 并写入到文件中
fruit.txt:
名称_价格_库存
香蕉_3.33_20
苹果_1.25_10
橘子_1.33_40
结果:
名称_价格_库存_总价
香蕉_3.33_20_66.60
苹果_1.25_10_12.50
橘子_1.00_40_40.00
代码如下:
public static void main(String[] args) throws Exception {
File file_source = new File("fruit.txt");
BufferedReader br = new BufferedReader(new FileReader(file_source));
File file_ret = new File("temp_fruit.txt");
BufferedWriter bw = new BufferedWriter(new FileWriter(file_ret));
DecimalFormat df = new DecimalFormat(".00");
//读第一行数据
String title = br.readLine();
bw.write(title+"_总价");
bw.newLine();
String line = "";
while ((line = br.readLine())!=null){
String[] price_num = line.split("_");
Double price = Double.parseDouble(price_num[1]);
Double num = Double.parseDouble(price_num[2]);
Double total = price*num;
String totalStr = df.format(total);
bw.write(line+="_"+totalStr);
bw.newLine();
}
br.close();
bw.flush();
bw.close();
//删除源文件并重命名新文件
file_source.delete();
file_ret.renameTo(file_source);
}