目录
前言
在学完File类,IO流后,不难发现将程序中的数据写入文件就是实现了持久化存储,在学习使用数据库进行持久化存储以前,File类和IO流就是实现持久化存储的重要工具。
于我而言就是向持久化存储进军的第一步,今天被一个小游戏搞得头疼,才意识到自己的逻辑真的不严谨,这在编程过程中会出现各种令人抓狂的现象,比如一个循环条件是“>=”,我漏了“=“”,导致死循环,比如IO流对文件的同时读写导致我经常读到兰陵王,很生气!这次要好好总结了!
正文
File类
概述
文件和目录路径名的抽象表示,可以把抽象表示理解为对象。
什么意思呢?把路径名字符串通过File类封装成对象。
注意
它封装的并不需要是一个真正存在的文件,仅仅是一个路径名而已。存在与否都可以通过File类中的方法进行操作,例如:
①构建时:
createNewFile()即为创建文件,
mkdir()是创建单层文件夹,
mkdirs(),顾名思义,多个文件夹,即创建多层文件夹。假设你的File对象在"E:\\java\\code"下,但是你的E盘下没有java这个目录,更别提code了,这个时候可以直接调用mkdirs()方法一步到位。
另一个需要注意的点:不要根据路径名来判断是文件还是目录,而是要通过调用的方法来判断,
public boolean isDirectory() 测试此抽象路径名表示的文件是否是一个目录。 | |
public boolean isFile() 测试此抽象路径名表示的文件是否是一个标准文件。 |
可以看一下例子:
File file = new File("E:\\temp\\my.txt");
file.mkdirs();
结果:
②删除时:
public boolean delete() 删除此抽象路径名表示的文件或目录。 | |
public void deleteOnExit() 在虚拟机终止时,请求删除此抽象路径名表示的文件或目录。 |
第一个方法是常用的删除文件方法,第二个方法多用于平常的练习,避免占资源。
删除文件时也需要注意一点:如果一个目录中有内容,不管是目录还是文件,我们不能直接删除,应该先删除目录中的内容,最后才能删除目录。
那我们如何先删除目录中的内容在删除目录呢?不知道大家想到那个熟悉的技巧没有----递归。
递归
概述:从编程的角度来看,它指的是方法定义中调用方法本身的现象。
递归解决问题的思路是把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,它只需要少量的程序就可以秒输出解题过程中所需要的多次重复计算。
有个经典的例子--阶乘,利用递归我们可以很简单的求出某个数的阶乘。
那么在删除目录时又是怎样运用的呢?我们来看代码。
public static void deleteFile(File f){
File[] files = f.listFiles();
for (File file:files){
if (file.isDirectory()) {
deleteFile(file);
} else {
file.delete();
}
}
}
这个方法的参数是一个File对象,我们用对象调用listFiles()方法返回的文件类数组做增强for循环,
对每一个文件判断是否是目录,如果是,则继续调用该方法,如果不是,则说明是文件,删除即可。在继续调用该方法的时候我们的方法参数变成了该文件,也就是说我们在原本的目录下进入到了它的下一级,如果还是目录,则继续前进,一直到该文件不是目录,我们就删除它,程序运行完毕后该目录下的所有文件都会被删除。
我们在使用递归时要找到两个内容:
递归出口:递归一定要有出口,否则会一直递归下去,导致内存溢出。
递归规则:要明白每一次递归做的是处理与原问题相似的规模较小的问题。
IO流
有关于流,我们很有必要了解它们之间的区别与联系,所以给出两个自制的常用到的框架图。
如果有遗漏那肯定是我没学过的~~~
哦,顺便提一下IO流是用来处理文件传输的。
为什么
为什么我们很有必要了解它们之间的区别与联系呢?
四个字----对症下药,因材施教。
搞清楚谁是谁,谁能做什么,谁是谁的爸爸和爷爷,谁又是谁的儿子和孙子,认清一大家子的血缘关系很重要。
是什么
首先我们看到IO流根据处理类型的不同分为字节流和字符流。
最先产生的是字节流,它是指传输过程中,传输数据的最基本单位是字节的流,一个不包含边界数据的连续流;字节流是由字节组成的,主要用在处理二进制数据,即用于处理图片及音频等。
字符流是由字符组成的,Java里字符由两个字节组成。
字符编码方式不同,有时候一个字符使用的字节数也不一样,比如ASCLL方式编码的字符,占一个字节;而UTF-8方式编码的字符,一个英文字符需要一个字节,一个中文需要三个字节。
字节数据是二进制形式的,要转成我们能识别的正常字符,需要选择正确的编码方式。我们生活中遇到的乱码问题就是字节数据没有选择正确的编码方式来显示成字符。
从本质上来讲,写数据(即输出)的时候,字节也好,字符也好,本质上都是没有标识符的,需要去指定编码方式,不过我们通常会有默认的编码方式。
读数据的时候,如果我们需要去“看数据”,那么字节流的数据需要指定字符编码方式,这样我们才能看到我们能识别的字符;而字符流,因为已经选择好了字符编码方式,通常不需要再改了(除非定义的字符编码方式与数据原有的编码方式不一致!)
在传输方面上,由于计算机的传输本质都是字节,而一个字符由多个字节组成,转成字节之前先要去查表转成字节,所以传输时有时候会使用缓冲区。
复制来的好绕啊!
总之
这样记:如果数据通过Windows自带的记事本软件打开,我们还可以读懂里面的内容,就使用字符流,否则使用字节流。如果你不知道该使用哪种类型的流,就是用字节流。
如果不去深究它们的话,通常我们在对文件进行操作的时候使用缓冲流的情况较多,没错就是图中以Buffered开头的流。
多提一句,这些流的继承关系其实是通过前缀的方式,基类都是没有加什么前缀的,而它们的子类就在它们的基础上加上前缀。通过这一点也可以做区分。
好了介绍缓冲流。
缓冲流
拿字节缓冲流和字符缓冲流来说,它们的区别其实也很明显。
字节缓冲流操作的是以字节构成的文件(我这样表述可能不大恰当,大家理解意思就行)例如图片、音频等。
字节缓冲流:
BufferedInputStream(字节缓冲输入流) | 创建BufferedInputStream将创建一个内部缓冲区数组,当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次很多字节。 | 构造方法BufferedOutputStream(OutPutStream out) |
BufferOutputStream(字节缓冲输出流 | 该类实现缓冲输出流,通过设置这样的输出流,应用程序可以向底层输出流写入字节而不必为写入的每个字节导致底层系统的调用。 | 构造方法BufferedInputStream(InputStream in) |
它的好处我们用人话来说---------读写效率变高。
至于构造方法,它需要的是对应流的实现类,例如BufferedInputStream的构造方法里可以new一个FileInputStream。
原因:字节缓冲流仅仅提供缓冲区,而真正的读写数据还得依靠基本的字节流对象进行操作。
字符缓冲流操作的是以字符的文件,一般我们指文本文件。
BufferedWriter(字符缓冲输出流) | 将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接收默认大小,默认值足够大, 可用于大多数用途、 | BufferWriter bw=new BufferWriter(new FileWriter(()) |
BufferedReader(字节缓冲输入流) | 从字符输入流读入文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。默认值足够大,可用于大多数用途。 | BufferReader br=new BufferReader(new FileReader(xx)) |
它的好处同字节缓冲流,不过它针对的大多为文本文件。
它的构造方法中的FileWriter继承自转换流(字符到字节的转换)
用于输出字符流,
FileReader继承自转换流(字节到字符的转换)用于读取字节流。
怎么做
光说不练假把式~~~下面展示一下字节缓冲流和字符缓冲流的实际案例。
1,将模块目录下的1.png文件复制为2.png文件。
思路:
①根据数据源创建字节输入流对象
②根据目的地创建字节输出流对象
③读写数据,复制图片(一次读取一个字节数组,一次写入一个字节数组)
④释放资源
代码如下:
package EX1;
import java.io.*;
/**
* @author 尤小鹏
* 切忌一味模仿!
* 2021/8/16/016
*/
public class Demo1 {
public static void main(String[] args) throws IOException {
// 4.使用缓冲流实现1.png文件复制为2.png文件的操作
// BufferedReader reader = new BufferedReader(new FileReader("1.png"));
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("1.png"));
// BufferedWriter writer = new BufferedWriter(new FileWriter("2.png"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("2.png"));
byte[] bytes = new byte[1024];
int len;
while ((len=bis.read(bytes))!=-1){
bos.write(bytes,0,len);
}
bis.close();
bos.close();
}
}
1.png是这样的~~~
程序运行完后,
2.png是这样的~~
没区别,应该没区别吧~~
可以看到我注释掉的那两行是字符缓冲流,我也试过用它们来复制图片文件,结果是一堆乱码,png格式打不开。因此我们只能用字节来操作图片数据。
2.利用字符缓冲流写入文件并输出在控制台以及复制为新的文件
在指定的路径下新建一个 .txt 文件 "test.txt",利用程序在文件中写入如下内容:
"Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由Sun Microsystems公司于 1995年5月推出的Java程序设计语言和Java平台(即JavaSE, JavaEE, JavaME)的总称。Java 技术具有 卓越的通用性、高效性、平台移植性和安全性,广泛应用于个人PC、数据中心、游戏控制台、科 学超级计算机、移动电话和互联网,同时拥有全球最大的开发者专业社群。在全球云计算和移动互 联网的产业环境下,Java更具备了显著优势和广阔前景。"
并且成功读出在控制台打印,而且利用程序复制 test.txt 给 test1.txt.
思路:
①根据数据源创建字符输入流对象
②根据目的地创建字符输出流对象
③写数据,读数据(使用BufferedWriter的特有方法 readLine()每次读一行),打印并输出()
④释放资源
package EX1;
import java.io.*;
/**
* @author 尤小鹏
* 切忌一味模仿!
* 2021/8/16/016
*/
public class Demo2 {
/*1.在指定的路径下新建一个 .txt 文件 "test.txt",利用程序在文件中写入如下内容:
"Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由Sun Microsystems公司于 1995年5月推出的Java程序设计语言和Java平台(即JavaSE, JavaEE, JavaME)的总称。Java 技术具有 卓越的通用性、高效性、平台移植性和安全性,广泛应用于个人PC、数据中心、游戏控制台、科 学超级计算机、移动电话和互联网,同时拥有全球最大的开发者专业社群。在全球云计算和移动互 联网的产业环境下,Java更具备了显著优势和广阔前景。"
并且成功读出在控制台打印,而且利用程序复制 test.txt 给 test1.txt*/
public static void main(String[] args) throws IOException {
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("test.txt"));
BufferedReader bufferedReader = new BufferedReader(new FileReader("test.txt"));
BufferedWriter writer = new BufferedWriter(new FileWriter("test1.txt"));
bufferedWriter.write("Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由Sun Microsystems公司于 1995年5月推出的Java程序设计语言和Java平台(即JavaSE, JavaEE, JavaME)的总称。Java 技术具有 卓越的通用性、高效性、平台移植性和安全性,广泛应用于个人PC、数据中心、游戏控制台、科 学超级计算机、移动电话和互联网,同时拥有全球最大的开发者专业社群。在全球云计算和移动互 联网的产业环境下,Java更具备了显著优势和广阔前景。"
);
bufferedWriter.flush();
String line;
while ((line=bufferedReader.readLine())!=null){
System.out.print(line);
writer.write(line);
writer.newLine();
}
bufferedReader.close();
bufferedWriter.close();
writer.close();
}
}
结果就不展示了。
以上都是对于常见流的使用。为了更好地实现持久化存储,对象序列化流和Properties集合提供了很强的帮助。当然这是进阶用法,写的好累,下期再见。
咳咳,开个玩笑。。。。。给个案例。
(综合案列 )
有一个TransRecord.txt文件,保存的是交易记录明细。一行是一条交易明细,每行分7列。
请编码,实现如下功能:
1、设计一个交易记录类TransRecord (金额字段数据类型定为double)
2、解析文件(IO流),将文件中数据,将每条交易明细封装为1个TransRecord对象。所有TransRecord对象,添加到一个集合中,并且打印到控制台;(就是将对象添加到集合并打印集合)BufferedReader 方法readLine()
3、完成一个功能(方法):输入客户号,查询交易明细记录并打印出来,封装成一个方法,通过传入客户号作为参数
4、定义一个方法,计算总金额并打印到控制台;(遍历集合中对象,获取每个对象的金额相加)
5、定义一个方法,按金额升序排序,并且打印到控制台;Set自然或定制排序
TransRecord.txt文件如下:
客户号 客户姓名 所属机构号 性别 账号 发生时间 发生额
000001 小刘 0000 1 4155990188888888 2014-07-20 300.00
000201 小赵 0002 1 4155990199999999 2019-07-20 500.00
000101 小钱 0012 0 4155990100000000 2019-05-20 1000.50
000102 小孙 0012 1 4155990155555555 2014-07-20 600.99
000301 小李 0013 0 41559901111111111 2014-07-22 5000.00
000001 小周 0000 1 155990188888888 2020-01-25 21200.00
代码如下:
1.TransRecord.java
把程序中需要的类建立好。
package EX2.Demo4;
/**
* @author 尤小鹏
* 切忌一味模仿!
* 2021/8/18/018
*/
public class TransRecord {
//1、设计一个交易记录类TransRecord (金额字段数据类型定为double)
//客户号客户姓名 所属机构号 性别账号 发生时间发生额
private String id;
private String name;
private String typeid;
private String gender;
private String account;
private String time;
private double money;
public TransRecord(String id, String name, String typeid, String gender, String account, String time, double money) {
this.id = id;
this.name = name;
this.typeid = typeid;
this.gender = gender;
this.account = account;
this.time = time;
this.money = money;
}
public TransRecord() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTypeid() {
return typeid;
}
public void setTypeid(String typeid) {
this.typeid = typeid;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
@Override
public String toString() {
return
id + "\t" +
name + "\t" +
typeid + "\t" +
gender + "\t" +
account + "\t" +
time + "\t" +
money;
}
}
2.Demo.java
在这里面实现需求功能。
package EX2.Demo4;
import Example.ArrListToFile;
import java.io.*;
import java.util.ArrayList;
import java.util.Properties;
import java.util.Scanner;
/**
* @author 尤小鹏
* 切忌一味模仿!
* 2021/8/18/018
*/
public class Demo {
public static void main(String[] args) throws IOException {
/* 2、解析文件(IO流),将文件中数据,将每条交易明细封装为1个TransRecord对象。所有TransRecord对象,
添加到一个集合中,并且打印到控制台;(就是将对象添加到集合并打印集合)BufferedReader 方法readLine()
3、完成一个功能(方法):输入客户号,查询交易明细记录并打印出来,封装成一个方法,通过传入客户号作为参数
4、定义一个方法,计算总金额并打印到控制台;(遍历集合中对象,获取每个对象的金额相加)
5、定义一个方法,按金额升序排序,并且打印到控制台;Set自然或定制排序*/
WriteFile();
readFile().forEach(System.out::println);
System.out.println("请输入客户号:");
String s = new Scanner(System.in).nextLine();
selectFile(s);
ArrayList<TransRecord> transRecords = readFile();
computeTotalMoney(transRecords);
sortByMoney(transRecords);
}
private static void sortByMoney(ArrayList<TransRecord> transRecords) {
transRecords.sort((o1, o2) -> {
return (int) (o1.getMoney()-o2.getMoney());
});
transRecords.forEach(System.out::println);
}
private static void computeTotalMoney(ArrayList<TransRecord> transRecords) {
double sum=0;
for (TransRecord t:transRecords){
sum+=t.getMoney();
}
System.out.println(sum);
}
private static void selectFile(String s) throws IOException {
//3、完成一个功能(方法):输入客户号,查询交易明细记录并打印出来,封装成一个方法,通过传入客户号作为参数
ArrayList<TransRecord> transRecords = readFile();
BufferedReader reader = new BufferedReader(new FileReader("MyFile\\src\\EX2\\Demo4\\TransRecord.txt"));
for (TransRecord t:transRecords){
if (t.getId().equals(s)){
System.out.println(t);
}
}
}
private static ArrayList<TransRecord> readFile() throws IOException {
BufferedReader reader = new BufferedReader(new FileReader("MyFile\\src\\EX2\\Demo4\\TransRecord.txt"));
ArrayList<TransRecord> transRecords = new ArrayList<>();
String line;
reader.readLine();
while ((line=reader.readLine())!=null){
String[] split = line.split("\t");
TransRecord transRecord = new TransRecord(split[0], split[1], split[2], split[3], split[4], split[5], Double.parseDouble(split[6]));
transRecords.add(transRecord);
}
return transRecords;
}
private static void WriteFile() throws IOException {
ArrayList<TransRecord> transRecords = new ArrayList<>();
transRecords.add(new TransRecord("000001", "小刘", "0000", "1", "4155990188888888", "2014-07-20", 300.00));
transRecords.add(new TransRecord("000201", "小赵", "0002", "1", "4155990199999999", "2019-07-20", 500.00));
transRecords.add(new TransRecord("000101", "小钱", "0012", "0", "4155990100000000", "2019-05-20", 1000.50));
transRecords.add(new TransRecord("000102", "小孙", "0012", "1", "4155990155555555", "2014-07-20", 600.99));
transRecords.add(new TransRecord("000301", "小李", "0013", "0", "41559901111111111", "2014-07-22", 5000.00));
transRecords.add(new TransRecord("000001", "小周", "0000", "1", "155990188888888", "2020-01-25", 21200.00));
// transRecords.forEach(System.out::println);
PrintWriter writer = new PrintWriter(new FileWriter("E:\\java\\Code\\MyFile\\src\\EX2\\Demo4\\TransRecord.txt"),true);
writer.println("客户号\t客户姓名\t\t\t所属机构号\t性别\t账号\t发生时间\t发生额");
for (TransRecord t:transRecords){
writer.println(t);
}
}
}
结言
改了一天代码的小鹏失去了动力,对代码的介绍几近于无,但是假如有读者有不懂的地方欢迎和我交流,一起进步!