一、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:\DocsFile f1 = new File("sub\\javac"); // 绝对路径是C:\Docs\sub\javacFile f3 = new File(".\\sub\\javac"); // 绝对路径是C:\Docs\sub\javacFile 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()); //falseSystem.out.println(f2.isFile());//falseSystem.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()); //trueSystem.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()) { // 可以直接遍历PathSystem.out.println(" " + p);}
如果需要对目录进行复杂的拼接、遍历等操作,使用
Path对象更方便。
小结:
Java标准库的java.io.File对象表示一个文件或者目录:创建
File对象本身不涉及IO操作;- 可以获取路径/绝对路径/规范路径:
getPath()/getAbsolutePath()/getCanonicalPath(); - 可以获取目录的文件和子目录:
list()/listFiles(); - 可以创建或删除文件和目录。
5.练习
请利用File对象列出指定目录下的所有子目录和文件,并按层次打印。
例如,输出:
如果不指定参数,则使用当前目录,如果指定参数,则使用指定目录。Documents/word/1.docx2.docxwork/abc.docppt/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 |
万变不离其宗, 所有的流在使用上基本上都是从这几个流过来的. 但是很不幸, 这几个流都是抽象类. 我们不能直接创建这几个类的对象. 所以, 我们必须学习他们的子类间接的来学习流。
节点流:文件流
FileInputStreamFileOutputStreamFileReaderFileWriter1.文件字节输入流
FileInputStreampublic 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() == -1System.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() == -1System.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);//写byteFile 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-ReaderFileWriter-Writer,可以直接读写字符串;
2.音视频/压缩文件等操作,使用字节流FileInputStream-InputStreamFileOutputStream-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);}

