【实验7-1】 批量操作文件功能
任务介绍
1.任务描述
在日常工作中,经常会遇到批量操作系统文件的事情,通常情况下,只能手动重复的完成批量文件的操作,这样很是费时费力。本案例要求编写一个文件管理器,实现文件的批量操作。文件管理器具体功能要求如下:
(1) 用户输入指令1,代表“指定关键字检索文件”,此时需要用户输入检索的目录和关键字,系统在用户指定的目录下检索出文件名中包含关键字的文件,并将其绝对路径展示出来。
(2) 用户输入指令2,代表“指定后缀名检索文件”,此时需要用户输入检索的目录和后缀名(多个后缀名用逗号分隔),系统在用户指定的目录下检索出指定后缀名的文件,并将其绝对路径展示出来。
(3) 用户输入指令3,代表“复制文件/目录”,此时需要用户输入源目录和目标目录,程序执行后会将源目录下的内容复制到目标目录下。
(4) 用户输入指令4,代表“删除文件/目录”,此时需要用户输入需要删除掉的文件目录,程序执行后会将目录以及目录下的内容全部删除。
(5) 用户输入指令5,代表“退出”,即退出该系统。
2.运行结果
任务运行结果如图7-1所示。
源路径G:\itcast下的文件,如图7-2所示。
任务目标
-
学会分析“模拟文件管理器”程序的实现思路。
-
能够根据思路独立完成“模拟文件管理器”程序的源代码编写、编译及运行。
-
掌握File类的常用方法。
-
掌握文件遍历的方法和文件名过滤器FilenameFilter的用法。
实现思路
(1)根据任务介绍和运行结果分析可知,首先需要创建一个文件管理器类。可以在类中使用while循环实现控制台中操作指令的多次输入,并使用switch语句根据控制台输入的操作指令来判断执行什么操作。
(2)执行指令1时,代表指定关键字检索文件,即在目录下查找包含关键字的文件。执行时先要从控制台获取目录和关键字,然后将其传到后台的方法中,后台可将传入的关键字利用过滤器将其制定成“规则”,通过递归的方式遍历文件夹,在每个子文件夹下调用过滤器,来获取符合规则的文件路径的集合,并将集合返回,最后打印出来;
(3)执行指令2时,代表指定后缀名检索文件,即在目录下查找名称结尾是指定后缀的文件。执行时可以先从控制台获取目录和后缀名,然后将后缀名拆分成数组,并将数组和目录传到后台方法中。后台可用过滤器将后缀名数组循环遍历,制定成“规则”,通过递归的方式遍历文件夹,在每个子文件夹下调用过滤器,来获取符合规则的文件路径的集合,并将集合返回,最后并打印出来;
(4)执行指令3时,代表将源目录的内容复制到目标目录中。执行时可以先从控制台获取源目录和目标目录,然后将其传入后台。后台主要是通过递归的方法执行文件和文件夹复制的。每次调用此方法时,首先会获取当前目录下的文件和文件夹列表,然后循环列表并判断当前元素是文件还是文件夹。如果是文件夹,就利用File类的mkdirs()方法在目标目录中创建此文件夹,再调用自身;如果是文件,由于不一定是纯文本文件,所以可以采用字节流将文件写入到目标目录;
(5)执行指令4时,执行代表将输入的文件删除。执行是先从控制台获取文件地址,后台先判断File对象是否存在,再使用foreach循环遍历删除文件夹下的所有文件,最后删除该文件夹;
(6)执行指令5时,执行退出该程序的操作,该操作可以通过System.exit(0)来实现。
实现代码
(1)定义DocumentManager类,具体如文件7-1所示。
文件7-1 DocumentManager.java
package chapter0705;
import java.io.File;
import java.util.ArrayList;
import java.util.Scanner;
public class DocumentManager {
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
System.out.println("--1:指定关键字检索文件 2:指定后缀名检索文件 "
+ "3:复制文件/目录 4:删除文件/目录 5:退出--");
while(true){
System.out.print("请输入指令:");
int command = sc.nextInt();
switch (command) {
case 1:
searchByKeyWorld();//指定关键字检索文件
break;
case 2:
searchBySuffix();//指定后缀名检索文件
break;
case 3:
copyDirectory();//复制文件/目录
break;
case 4:
deleteDir();//删除文件/目录
break;
case 5:
exit();//退出
break;
default:
System.out.println("您输入的指令错误!");
break;
}
}
}
// *********1.指定关键字检索文件*********
private static void searchByKeyWorld() {
Scanner sc = new Scanner(System.in);
System.out.print("请输入要检索的目录位置:");
String path = sc.next();//从控制台获取路径
File file = new File(path);
if (!file.exists() || !file.isDirectory()) {//判断目录是否存在,是否是目录
System.out.println(path + " (不是有效目录)");
return;
}
System.out.print("请输入搜索关键字:");
String key = sc.next();//获取关键字
//在输入目录下获取所有包含关键字的文件路径
ArrayList<String> list = FileUtils.listFiles(file,key);
for (Object obj : list) {
System.out.println(obj);//将路径打印到控制台
}
}
// *********2.指定后缀名检索文件********//
private static void searchBySuffix() {
Scanner sc = new Scanner(System.in);
System.out.print("请输入要检索的目录位置:");
String path = sc.next();//从控制台获取路径
File file = new File(path);
if (!file.exists() || !file.isDirectory()) {//判断目录是否存在,是否是目录
System.out.println(path + " (不是有效目录)");
return;
}
System.out.print("请输入搜索后缀:");
String suffix = sc.next();
String[] suffixArray = suffix.split(",");//获取后缀字符串
//在输入目录下获取所有指定后缀名的文件路径
ArrayList<String> list = FileUtils.listFiles(file, suffixArray);
for (Object obj : list) {
System.out.println(obj);//将路径打印到控制台
}
}
// *********3.复制文件/目录**********//
private static void copyDirectory() throws Exception {
Scanner sc = new Scanner(System.in);
System.out.print("请输入源目录:");
String srcDirectory = sc.next();//从控制台获取源路径
File srcFile = new File(srcDirectory);
//判断是否存在 是否是目录
if (!srcFile.exists() || !srcFile.isDirectory()) {
System.out.println("无效目录!");
return;
}
System.out.print("请输入目标位置:");
String destDirectory = sc.next();//从控制台获取目标路径
File destFile = new File(destDirectory);
//判断是否存在 是否是目录
if (!destFile.exists() || !destFile.isDirectory()) {
System.out.println("无效位置!");
return;
}
//将源路径中的内容复制到目标路径下
FileUtils.copySrcPathToDestPath(srcFile, destFile);
}
// *********4.删除文件/目录**********//
public static void deleteDir() {
Scanner sc = new Scanner(System.in);
System.out.print("请输入需要删除的源目录:");
String delpath = sc.next();// 从控制台获取源路径
File dir = new File(delpath); // 创建一个代表目录的File对象
if (dir.exists()) { // 判断传入的File对象是否存在
File[] files = dir.listFiles(); // 得到File数组
for (File file : files) { // 遍历所有的子目录和文件
if (file.isDirectory()) {
deleteDir(); // 如果是目录,递归调用deleteDir()
} else {
// 如果是文件,直接删除
file.delete();
}
}
// 删除完一个目录里的所有文件后,就删除这个目录
dir.delete();
}
}
// *********5.退出**********//
private static void exit() {
System.out.println("您已退出系统,谢谢使用!");
System.exit(0);
}
}
在文件7-26中,第10~33
行代码,通过while循环等待指令输入,并通过switch来判断执行什么动作。第36~52
行执行了根据指定的关键字检索文件的动作,其中第41行代码用来判断了输入的目录是否有效。第48行代码用于调用后台方法,获取符合规则的列表。
第54~71
行代码执行指定后缀名检索文件,其中第65行代码将获得的后缀名字符串拆分成数组,并利用第67行代码传入后台,获取符合规则的列表。
第73~93
行代码的作用是将源目录下的内容复制到目标目录中,其中第79和89行代码用于判断目录是否有效,第92行代码用于调用后台方法,实现源目录内容复制的功能。
第93-113
行代码的作用是将控制台输入的目标文件删除,其中第100行代码是判断File对象是否存在,第102-109行代码使用foreach循环删除子目录与文件。
(2)定义FileUtils类,具体如文件7-2所示。
文件7-2 FileUtils.java
package cn.itcast.chapter07.task03;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.util.ArrayList;
public class FileUtils {
/**
* 指定关键字检索文件
* @param file File对象
* @param key 关键字
* @return 包含关键字的文件路径
*/
public static ArrayList<String> listFiles(File file, fina* String key){
FilenameFilter filter = new FilenameFilter() { // 创建过滤器对象
public boolean accept(File dir, String name) {// 实现accept()方法
File currFile = new File(dir, name);
// 如果文件名包含关键字返回true,否则返回false
if (currFile.isFile() && name.contains(key)) {
return true;
}
return false;
}
};
//递归方式获取规定的路径
ArrayList<String> arraylist = fileDir(file, filter);
return arraylist;
}
/**
* 指定后缀名检索文件
* @param file File对象
* @param suffixArray 后缀名数组
* @return 指定后缀名的文件路径
*/
public static ArrayList<String> listFiles(File file, fina* String[] suffixArray) {
FilenameFilter filter = new FilenameFilter() { // 创建过滤器对象
public boolean accept(File dir, String name) {// 实现accept()方法
File currFile = new File(dir, name);
if (currFile.isFile()) {
for (String suffix : suffixArray) {
if (name.endsWith("." + suffix)) {
return true;
}
}
}
return false;
}
};
//递归方式获取规定的路径
ArrayList<String> arraylist = fileDir(file, filter);
return arraylist;
}
/**
* 递归方式获取规定的路径
* @param dir File对象
* @param filter 过滤器
* @return 过滤器过滤后的文件路径
*/
public static ArrayList<String> fileDir(File dir, FilenameFilter filter){
ArrayList<String> arraylist = new ArrayList<String>();
File[] lists = dir.listFiles(filter); // 获得过滤后的所有文件数组
for (File list : lists) {
//将文件的绝对路径放到集合中
arraylist.add(list.getAbsolutePath());
}
File[] files = dir.listFiles(); // 获得当前目录下所有文件的数组
for (File file : files) { // 遍历所有的子目录和文件
if (file.isDirectory()) {
// 如果是目录,递归调用fileDir()
ArrayList<String> every = fileDir(file, filter);
arraylist.addAll(every);//将文件夹下的文件路径添加到集合中
}
}//此时的集合中有当前目录下的文件路径,和当前目录的子目录下的文件路径
return arraylist;
}
/**
* 复制文件/目录
* @param srcFile 源目录
* @param destFile 目标目录
*/
public static void copySrcPathToDestPath(File srcDir, File destDir)
throws Exception {
File[] files = srcDir.listFiles();// 子文件目录
for (int i = 0; i < files.length; i++) {
File copiedFile = new File(destDir, files[i].getName());
if (files[i].isDirectory()) {// 如果是目录
if(!copiedFile.mkdirs()){//创建文件夹
System.out.println("无法创建:"+copiedFile);
return;
}
// 调用递归,获取子文件夹下的文件路径
copySrcPathToDestPath(files[i], copiedFile);
} else {// 复制文件
FileInputStream input = new FileInputStream(files[i]);//获取输入流
FileOutputStream output = new FileOutputStream(copiedFile);//获取输出流
byte[] buffer = new byte[1024];//创建缓冲区
int n = 0;
//循环读取字节
while ((n = input.read(buffer))!= -1) {
output.write(buffer, 0, n);
}
input.close();//关闭输入流
output.close();//关闭输出流
}
}
}
}
在文件7-27中,执行指令1时调用第14~28
行的代码,其中第15~24
行代码用于创建过滤器,第26行代码用于调用递归方法。
执行指令2时,将调用第35~52
行代码,其中第36~48
行代码用于创建过滤器,其中第40~44
行代码通过循环,将所有后缀名都配成了过滤规则。第50行代码调用了递归方法,此方法的代码是59~75
行,方法中第61~65
行代码获取当前目录下经过过滤后的文件数组,通过循环取其路径存入集合中。
第66~73
行代码将当前目录下内容循环,如果是文件夹则调用自身。执行指令3时调用第81~106
行代码,第83行代码先获取了子文件列表,然后将列表中文件进行循环,如果循环获取的当前目录是文件夹,则执行第87~92
行的代码,创建此文件夹,之后调用自身;当前目录是文件时,通过第94~103行代码,将此文件用字节流形式写入并关闭资源。
【实验7-2】商城进货记录交易
【任务介绍】
1.任务描述
每个商城都需要进货,而这些进货记录整理起来很不方便,本案例要求编写一个记录商城进货交易的程序,使用字节流将商场的进货信息记录在本地的csv文件中。程序具体要求如下:
当用户输入商品编号时,后台会根据商品编号查询到相应商品信息,并打印商品信息。接着让用户输入需要进货的商品数量,程序将原有的库存数量与输入的数量相加,将相加后的结果信息保存至本地的csv文件中。在csv文件中,每条记录包含商品编号、商品名称、购买数量、单价、总价、审核人等数据,每条记录的数据之间直接用英文逗号或空格分隔;每条记录之间由换行符分隔。文件命名格式为“进货记录”加上当天日期加上“.csv”后缀,如进货记录“20200430.csv”。保存文件时,需要判断本地是否存在当天的数据,如果存在则追加,不存在则新建。
2.运行结果
任务运行结果如图7-1所示。
运行结束后在本地生成一个“进货记录20191209.csv”文件,用Excel方式打开此文件,如图7-1所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tWqHnfsn-1649838946269)(https://s2.loli.net/2022/04/12/o2QtSw7THaBvliV.jpg)]
【任务目标】
-
学会分析"商城进货交易记录"程序的实现思路。
-
根据思路独立完成"商城进货交易记录"的源代码编写、编译及运行。
-
掌握字节流操作本地文件的方法。
-
掌握ArrayList和StringBuffer的使用,以及异常的处理
-
了解csv的文件格式。
【实现思路】
(1) 为了方便保存商品的相关信息,可以将图书信息封装为一个实体类。商品进货过程中可能会打印图书相关信息,所以需要对该实体类的toString()方法进行重写,使其能更清晰地显示商品信息,商品每次进货后要修改库存数量,需要在实体类中编写一个操作库存数量的方法。
(2) 对于一个超市,首先会有很多商品,商品需要不断进货。这里我们需要创建一个集合用于模拟超市仓库,然后向集合中添加有具体信息的商品对象,这样一个超市就有了商品。
(3) 管理员进货是通过在控制台键盘输入商品编号和购买数量的方式进行的,如果商品编号正确,且购买数量也正常,则商品进货成功,并将此商品的进货信息保存到csv文件中,同时要将库存数量增加。
(4) 查询商品信息时,可以通过Scanner类的nextInt()方法从控制台获取商品编号,之后根据这个编号到库存中查询此商品的信息,如果查到了商品的信息,从控制台获取进货的数量之后,将此商品的所有信息进行封装。
(5) 将商品的销售信息写入到csv文件之前,需先拼凑好csv文件名,再判断本地是否已存在此文件,这里可通过输入流尝试获取此文件的字节流,如果获取成功,则证明这个文件已存在,那么就通过输出流向文件末尾追加销售信息,如果获取失败,即异常,说明之前并没有生成当日的销售信息,则需要新建此文件。
(6) 将封装的信息写入csv文件中时,csv格式的文件以纯文本形式存储表格数据,写入文件时可以用图7-1的格式写入,当此类文件用Excel格式打开的时候,展现信息如图7-11。
(7) 在拼凑csv文件名时,需要获取当日的日期。这里可以通过以下代码来获取并拼凑csv文件名:
DateFormat format = new SimpleDateFormat("yyyyMMdd");// 定义日期格式
String name = "进货记录" + format.format(date) + ".csv";// 拼接文件名
【实现代码】
(1)将商品信息封装成一个实体类Good,具体如文件7-1所示。
文件7-1 Good.java
package chapter0701;
public class Good {
int id;
String name; //商品的价格
double price; //商品的单价
int number; //进货的数量
double money; //总价
String people; //审批人
public Good(int id, String name, double price, int number, double
money, String people) {
this.id = id;
this.name = name;
this.price = price;
this.number = number;
this.money = money;
this.people = people;
}
@Override
public String toString() {
String message="进货记录编号:"+id+"\n商品名称:"+name+"\n联系
人:"+people+"\n单价:"+price+"\n库存数量:"+number+"\n";
return message;
}
public void setNumber(int number) {
this.number=number;
}
}
在文件7-1中,第3~8
行代码定义了用于标识商品的信息各种字段,,第9~17
行代码定义了一个有参的构造方法,用于对象的创建和初始化,在第18~23
行,重写了toString()方法,用于返回商品的详细信息。第24~26行的代码定义了一个setNumber()的方法,用于修改商品的库存量。
(2)定义RecordGoodOrder类来记录和操作商品信息,具体如文件7-2所示。
文件7-2 RecordGoodOrder.java
package chapter0701;
import java.util.ArrayList;
import java.util.Scanner;
public class RecordGoodOrder {
//创建商品库存
static ArrayList<Good> goodsList=new ArrayList<Good>();
public static void main(String[] args) {
init(); //初始化商品库存
//将书架上所以商品信息打印出来
for (int i = 0; i < goodsList.size(); i++) {
System.out.println(goodsList.get(i));
}
while(true) {
//获取控制台输入的信息
Scanner scan=new Scanner(System.in);
System.out.println("请输入商品编号");
int goodId=scan.nextInt();
Good stockGood=getGoodsById(goodId);
if (stockGood != null) {// 判断是否存在此商品
System.out.println("当前商品库存信息" + stockGood);
System.out.print("请输入进货数量:");
int bookNumber = scan.nextInt();
// 将输入信息封装成Books对象
Good good = new Good(stockGood.id, stockGood.name,
stockGood.price, bookNumber, stockGood.price
* bookNumber, stockGood.people);
FileUtil.saveBooks(good);// 将本条数据保存至本地文件
// 修改库存
stockGood.setNumber(stockGood.number + bookNumber);
} else {
System.out.println("商品编号输入错误!");
}
}
}
/*
* 初始化商品库存的信息 将商品存入库存
*/
private static void init() {
Good good1=new Good(1001,"百事可乐",4.5,100,450,"张三");
Good good2=new Good(1002,"可口可乐",4,100,400,"李四");
Good good3=new Good(1003,"百事雪碧",3.8,100,380,"张三");
goodsList.add(good1);
goodsList.add(good2);
goodsList.add(good3);
}
/*
* 根据输入的商品编号查找图书信息,循环遍历库存中商品信息,找到商品编号相等的取出
*/
private static Good getGoodsById(int goodId) {
for (int i = 0; i < goodsList.size(); i++) {
Good thisGood=goodsList.get(i);
if (goodId==thisGood.id) {
return thisGood;
}
}
return null;
}
}
在文件7-8中,第6行代码创建了ArrayList类型的全局变量作为商品的仓库。第39~46
代码,初始化了商品信息,向ArrayList中添加了3种商品的信息,并在第10~12
代码中,通过for循环进行展示。
第13~34
行代码使用while循环来获取和处理用户输入信息,每次循环先由第16~17
行代码,从控制台获取商品编号的数据,再由第49~57
行的代码,根据商品编号查询到商品信息,当获得的商品信息不为空时,可从第21~22代码获得购买的数量,可通过24~29
的代码,将所有数据封装,再利用第27行代码,调用FileUtil类中的saveBooks()方法,将其保存至本地。最后在第29行代码中调用setNumber()方法,修改库存。
(3)定义工具类FileUtil保存图书信息,具体如文件7-3所示。
文件7-3 FileUtil.java
package chapter0701;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 工具类
*/
public class FileUti* {
public static fina* String SEPARATE_FIELD = ",";// 字段分隔 英文逗号
public static fina* String SEPARATE_LINE = "\r\n";// 行分隔
/**
* 保存商品信息
*/
public static void saveBooks(Good good) {
// 判断本地是否存在此文件
Date date = new Date();
// 定义日期格式
DateFormat format = new SimpleDateFormat("yyyyMMdd");
// 拼接文件名
String name = "进货记录" + format.format(date) + ".csv";
InputStream in = null;
try {
in = new FileInputStream(name);// 判断本地是否存在此文件
if (in != null) {
in.close();// 关闭输入流
// 可获取输入流,则存在文件,采取修改文件方式
createFile(name, true, good);
}
} catch (FileNotFoundException e) {
// 输入流获取失败,则不存在文件,采取新建新文件方式
createFile(name, false, good);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 将进货记录的信息保存到本地,可通过label标识来判断是修改文件还是新建文件
* @param name 文件名
* @param labe* 文件已存在的标识 true:已存在则修改; false:不存在则新建
* @param good 商品信息
*/
public static void createFile(String name,boolean label,Good good) {
BufferedOutputStream out = null;
StringBuffer sbf = new StringBuffer();// 拼接内容
try {
if (label) {// 当已存在当天的文件,则在文件内容后追加
// 创建输出流,用于追加文件
out = new BufferedOutputStream(new FileOutputStream(name, true));
} else {// 不存在当天文件,则新建文件
// 创建输出流,用于保存文件
out = new BufferedOutputStream(new FileOutputStream(name));
String[] fieldSort = new String[] { "商品编号", "商品名称", "购买数量","
单价", "总价", "联系人" };// 创建表头
for (String fieldKye : fieldSort) {
// 新建时,将表头存入本地文件
sbf.append(fieldKye).append(SEPARATE_FIELD);
}
}
sbf.append(SEPARATE_LINE);// 追加换行符号
sbf.append(good.id).append(SEPARATE_FIELD);
sbf.append(good.name).append(SEPARATE_FIELD);
sbf.append(good.number).append(SEPARATE_FIELD);
sbf.append((double) good.price).append(SEPARATE_FIELD);
sbf.append((double) good.money).append(SEPARATE_FIELD);
sbf.append(good.people).append(SEPARATE_FIELD);
String str = sbf.toString();
byte[] b = str.getBytes();
for (int i = 0; i < b.length; i++) {
out.write(b[i]);// 将内容写入本地文件
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null)
out.close();// 关闭输出流
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
}
}
在文件7-3中,当saveBooks()方法被调用时,获取当前日期并格式化后,拼出了文件名,再通过第29行代码尝试获取此文件的字节输入流。当能够获取输入流时,可通过第30~34
行代码,先关闭输入流,再在文件末尾追加信息。
当不能获取输入流时则抛出异常,在异常处理中调用第37行代码的createFile()方法,可以通过此方法中的label参数来区分,是新建文件还是在已有文件中追加内容,如果label值是true则追加内容,如果label值是false则新建文件,并写入表头,其中进行追加还是新建操作,由构造函数的append参数来定义。
然后利用65~72
行代码拼出一行数据,且在每次拼接之前都要加上换行符“\r\n”,每个字段之间通过“,”分隔字段,再利用第73~76
行的代码写入文件。最后80~86行代码关闭了输出流。
【实验7-3】 日记本
【任务介绍】
**1.**任务描述
编写一个日记本功能的程序,使用字节流经日记的具体信息记录在本地的txt文件中。当用户输入日记的特定内容后,会将输入的内容保存至本地的txt文件中。需要输入的内容包括“姓名”,“天气”、“标题”、“内容”的数据。保存的时候需要判断本地是否存在文件,如果存在则追加,不存在则新建。文件命名格式为“黑马日记本”加上“.txt”后缀,如“黑马日记本.txt”
**2.**运行结果
任务运行结果如图7-1所示。
运行结束后在本地生成一个“黑马日记本.txt”文件,如图7-2所示。
【任务目标】
-
学会分析"日记本小功能"程序的实现思路。
-
根据思路独立完成"日记本小功能"的源代码编写、编译及运行。
-
掌握字节流操作本地文件的方法。
-
掌握ArrayList和StringBuffer的使用,以及异常的处理。
【实现思路】
(1)为方便保存日记的相关信息,可以将日记信息封装成一个实体类。
(2)用户编写日记时,首先创建一个集合用于存放日记,然后用户依次填写的内容为“姓名”,“天气”、“标题”和“内容”,并将这些内容存放在集合中,再将实体类保存到txt文件中。
(3)查询日记时,使用字节流的读取实现控制台打印日记信息。
(4)将日记信息写入到txt文件之前,需先拼凑好txt文件名,再判断本地是否已存在此文件,这里可通过输入流尝试获取此文件的字节流,如果获取成功,则证明这个文件已存在,那么就通过输出流向文件末尾追加日记信息,如果获取失败,即异常,说明之前并没有生成日记信息,则需要新建此文件。
【实现代码】
日记小功能的实现代码,如文件7-1所示。
(1)将日记信息封装成一个实体类Diary,具体如文件7-1所示。
文件7-1 Diary.java
package chapter0702;
public class Diary {
String time; //时间
String name; //姓名
String weather;//天气
String title;//标题
String content;//内容
public Diary(String time, String name, String weather, String title,
String content) {
this.time = time;
this.name = name;
this.weather = weather;
this.title = title;
this.content=content;
}
}
在文件7-1中,第3-7行代码定义了用于标识日记信息的各个字段,第8-14行代码重新了toString()方法。
(2)定义DiaryOrder类来记录和操作日记信息,具体如文件7-2所示。
文件7-2 DiaryOrder.java
package chapter0702;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Scanner;
public class DiaryOrder {
static ArrayList<Diary> diarysList=new ArrayList<Diary>();
public static void main(String[] args) throws Exception {
System.out.println("--------欢迎来到黑马日记本--------");
boolean falg = true;
Scanner scan=new Scanner(System.in);
while(falg) {
System.out.println("1.编写日记");
System.out.println("2.查看日记");
int a = scan.nextInt();
if(a==1) {
//编写日记
System.out.println("请输入姓名:");
String name=scan.next();
System.out.println("请输入天气:");
String weather=scan.next();
System.out.println("请输入标题:");
String title=scan.next();
System.out.println("请输入内容:");
String content=scan.next();
Diary diary = addDiary(name,weather,title,content);
FileUtil.saveBooks(diary);
}else if(a==2) {
//查看日记
FileUtil.readFile();
}else {
System.out.println("输入错误");
}
}
}
/*
* 初始化日记列表 将输入的字符暂存为实体
*/
private static Diary addDiary(String name,String weather,String
title,String content) {
Date date = new Date();
DateFormat format = new SimpleDateFormat("yyyyMMdd");
String a=format.format(date).toString();
Diary diary=new Diary(a,name,weather,title,content);
return diary;
}
}
在文件7-2中,第8行代码创建了ArrayList类型的全局变量作为存储日记的仓库。第13~35
行代码使用while循环输入操作,第19~26
行代码让用户输入日记,第27~28
行代码将用户输入的日记存入本地文件,第31行代码调用readFile()方法显示日记,第40~47行代码初始化日记信息。
(3)定义工具类FileUtil保存日记信息,具体如文件7-3所示。
文件7-3 FileUtil.java
package chapter0702;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 工具类
*/
public class FileUti* {
public static fina* String SEPARATE_FIELD = "\n";// 换行
public static fina* String SEPARATE_LINE = "\t"; // 分隔
/**
* 保存日记信息
*/
public static void saveBooks(Diary diary) {
// 判断本地是否存在此文件
String name = "黑马日记本.txt";
InputStream in = null;
try {
in = new FileInputStream(name);// 判断本地是否存在此文件
if (in != null) {
in.close();// 关闭输入流
// 可获取输入流,则存在文件,采取修改文件方式
createFile(name, true, diary);
}
} catch (FileNotFoundException e) {
// 输入流获取失败,则不存在文件,采取新建新文件方式
createFile(name, false, diary);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 将日记信息保存到本地,可通过label标识来判断是修改文件还是新建文件
* @param name 文件名
* @param labe* 文件已存在的标识 true:已存在则修改; false:不存在则新建
* @param diary 日记信息
*/
public static void createFile(String name, boolean label, Diary diary) {
BufferedOutputStream out = null;
StringBuffer sbf = new StringBuffer();// 拼接内容
try {
if (label) {// 当已存在当天的文件,则在文件内容后追加
// 创建输出流,用于追加文件
out = new BufferedOutputStream(new FileOutputStream(name, true));
} else {// 不存在当天文件,则新建文件
// 创建输出流,用于保存文件
out = new BufferedOutputStream(new FileOutputStream(name));
String fieldSort = "欢迎来到黑马日记本" ;// 创建表头
// 新建时,将表头存入本地文件
sbf.append(fieldSort).append(SEPARATE_FIELD);
}
sbf.append("时间:").append(diary.time);// 追加换行符号
sbf.append("姓名:").append(diary.name).append(SEPARATE_LINE);
sbf.append("标题:").append(diary.title).append(SEPARATE_LINE);
sbf.append("天气:").append(diary.weather).append(SEPARATE_FIELD);
sbf.append("内容:").append(diary.content).append(SEPARATE_FIELD);
String str = sbf.toString();
byte[] b = str.getBytes();
for (int i = 0; i < b.length; i++) {
out.write(b[i]);// 将内容写入本地文件
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null)
out.close();// 关闭输出流
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
/*
* 读取日记并显示
*/
public static void readFile() throws Exception{
//创建文件字节输出流
FileInputStream in = new FileInputStream("黑马日记本.txt");
byte[] b = new byte[in.available()];
in.read(b);
String str=new String(b);
System.out.println(str);
in.close();
}
}
在文件7-3中,当saveBooks()方法被调用时,获取当前日期并格式化后,拼出了文件名,再通过第29行代码尝试获取此文件的字节输入流。当能够获取输入流时,可通过第27~29
行代码,先关闭输入流,再在文件末尾追加信息。
当不能获取输入流时则抛出异常,在异常处理中调用第33行代码的createFile()方法,可以通过此方法中的label参数来区分,是新建文件还是在已有文件中追加内容,如果label值是true则追加内容,如果label值是false则新建文件,并写入表头,其中进行追加还是新建操作,由构造函数的append参数来定义。
然后利用58~63
行代码拼出一行数据,且在每次拼接之前都要加上换行符“\r\n”,每个字段之间通过“,”分隔字段,再利用第64~67
行的代码写入文件。最后71~77行代码关闭了输出流。
【实验7-4】 升级版日记本
【任务介绍】
**1.**任务描述
本案例要求编写一个模拟日记本的程序,通过在控制台输入指令,实现在本地新建日记本、打开日记本和修改日记本等功能。
(1) 用户输入指令1代表“新建日记本”,可以从控制台获取用户输入的日记内容。
(2) 指令2代表“打开日记本”,读取指定路径的txt文件的内容并输出到控制台。
(3) 指令3代表“修改日记本”,修改日记时,既可以修改新建日记本的内容,也可以修改已打开日记本的内容。
(4) 指令4代表“保存”,如果是新建的日记本需要保存,则将日记本保存到用户输入的路径;如果是打开的日记本需要保存,则将原来内容覆盖;
(5) 指令5代表“退出”,即退出本系统。
2**.运行结果**
任务运行结果如图7-1所示。
运行过程中,本地D:\chapter07\task02路径下生成了note.txt文件,打开后如图7-2所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H9WS0GRY-1649838946272)(C:/Users/23061/AppData/Local/Temp/msohtmlclip1/01/clip_image004.jpg)]
运行结束后,本地D:\chapter07\task02路径下的note.txt文件已经修改,打开后如图7-3所示。
【任务目标】
-
学会分析“日记本”任务的实现思路。
-
能够根据思路独立完成“模拟记事本”程序的源代码编写、编译及运行。
-
掌握利用字符流操作本地文件的方法。
【实现思路】
(1)根据任务介绍和运行结果分析可知,此任务需要创建一个记事本类,在类中可以使用while循环实现控制台中操作指令的多次输入,使用switch语句根据控制台输入的操作指令来判断执行什么操作。
(2)输入指令1时进行新建文件操作,此时需要从控制台获取输入的内容并暂存到全局变量中,以便后期进行保存和修改内容时使用。可以使用StringBuffer来保存控制台中每次输入的内容,输入内容时可能会涉及换行,这里可以使用while循环来循环输入,每循环一次都将控制台获取的内容拼接换行符后,追加到文本内容中,当内容中输入“stop”字符串时,输入文本内容的操作结束,并将新建文本的内容暂存在全局变量中。
(3)执行指令2时,可以打开指定路径的文件,由于是模拟记事本程序,因此限定此功能只支持打开txt文件。此操作首先要获取输入的路径,判断此路径的文件是否是txt文件,如果是则通过字符流读取此文件,然后将此文件的内容打印至控制台,并且暂存在全局变量中,将文件的路径也保存至全局变量中。
(4)执行指令3时,可以对暂存在全局变量中的文本内容进行修改,修改后的内容也需要暂存到全局变量中。此操作首先要判断一下修改之前是否先经过新建文件或者打开文件操作,当确认经过上述操作后,可以将输入的字符串与文本内容中目标字符串进行替换,来完成修改内容功能。修改过程可通过while循环来进行多次的修改,当捕捉到“stop”字符串时修改结束,并将修改后的内容暂存到全局变量中。
(5)执行指令4时,将所有全局变量中的内容保存至本地,如果是新建的文件则保存至用户输入的路径,如果是打开后的文件则将原文件覆盖。可以通过文件路径的全局变量判断是哪种保存方式,当是打开文件的方式时,直接通过字符流写入;当是新建的方式时,先获取用户输入的路径,再用字符流写入。
(6)执行指令5,直接退出系统,可以通过代码“System.exit(0);”实现。
【实现代码】
(1)创建记事本类,在类中编写执行程序的main方法,如文件7-1所示。
文件7-1 Notepad.java
package cn.itcast.chapter07.task02;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;
public class Notepad {
private static String filePath;
private static String message = "";
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
System.out.println("--1:新建文件 2:打开文件 3:修改文件 4:保存 5:退出--");
while (true) {
System.out.print("请输入操作指令:");
int command = sc.nextInt();
switch (command) {
case 1:
createFile();//1:新建文件
break;
case 2:
openFile();// 2:打开文件
break;
case 3:
editFile();//3:修改文件
break;
case 4:
saveFile();// 4:保存
break;
case 5:
exit();//5:退出
break;
default:
System.out.println("您输入的指令错误!");
break;
}
}
}
}
在文件7-1中,第7行和第8行分别创建了文件路径和内容的全局变量。第12~35行代码,是循环主体,用于循环接收用户的指令。当从第14行代码获取指令后,通过switch语句判断应该执行的操作。
(2)在Notepad中编写新建文件方法,如文件7-2所示。
文件7-1 createFile()
/**
* 新建文件 从控制台获取内容
*/
private static void createFile() {
message = "";//新建文件时,暂存文件内容清空
Scanner sc = new Scanner(System.in);
System.out.println("请输入内容,停止编写请输入\"stop\":");//提示
StringBuffer stb = new StringBuffer();//用于后期输入内容的拼接
String inputMessage = "";
while (!inputMessage.equals("stop")) {//当输入“stop”时,停止输入
if (stb.length() > 0) {
stb.append("\r\n");//追加换行符
}
stb.append(inputMessage);//拼接输入信息
inputMessage = sc.nextLine();//获取输入信息
}
message = stb.toString();//将输入内容暂存
}
在文件7-2中,createFile()方法是Notepad类中的新建文件方法。第5行代码用于将文件内容清空。第8行代码定义了一个字符串缓冲区StringBuffer,用于存储每次控制台输入的内容。第10~16行代码,通过while循环,使控制台可以多次输入内容,每次输入结束后,就将输入字符串拼入字符串缓冲区中,并在末尾追加一个换行符,直至输入“stop”字符串时循环结束,输入停止。第17行代码,将StringBuffer中的数据转成String类型,并存入全局变量message中,以便修改和保存时使用。
(3)在Notepad中编写打开文件方法,如文件7-3所示。
文件7-1 openFile()
/**
* 打开文件
*/
private static void openFile() throws Exception {
message = "";//打开文件时,将暂存内容清空
Scanner sc = new Scanner(System.in);
System.out.print("请输入打开文件的位置:");
filePath = sc.next();//获取打开文件的路径
//控制只能输入txt格式的文件路径
if (filePath != null && !filePath.endsWith(".txt")) {
System.out.print("请选择文本文件!");
return;
}
FileReader in = new FileReader(filePath);//实例化一个FileReader对象
char[] charArray = new char[1024];//缓冲数组
int len = 0;
StringBuffer sb = new StringBuffer();
// 循环读取,一次读取一个字符数组
while ((len = in.read(charArray)) != -1) {
sb.append(charArray);
}
message = sb.toString();//将打开文件内容暂存
System.out.println("打开文件内容:" + "\r\n" + message);
in.close();// 释放资源
}
在文件7-3中,openFile()方法是Notepad类中的打开文件方法。第5行代码实现将内容的message全局变量清空。第6~8
行获取到控制台传入的文件打开的路径,并将其存储到filePath全局变量中。第10~13行代码,判断将要打开的文件的格式是否是文本文件。第14~21
行代码,利用了字符输入流读取文件的内容。第22行代码,将StringBuffer中的内容转成String类型,并存入全局变量message中,以便修改和保存时使用。
(4)在Notepad中编写修改文件方法,如文件7-4所示。
文件7-1 editFile()
/**
* 修改文件内容 通过字符串替换的形式
*/
private static void editFile() {
if(message == "" && filePath == null){
System.out.println("请先新建文件或者打开文件");
return;
}
Scanner sc = new Scanner(System.in);
System.out.println("请输入要修改的内容(以 \"修改的目标文字:修改之后的文字\" 格式),"
+ "停止修改请输入\"stop\":");
String inputMessage = "";
while (!inputMessage.equals("stop")) {//当输入stop时,停止修改
inputMessage = sc.nextLine();
if (inputMessage != null && inputMessage.length() > 0) {
// 将输入的文字根据“:”拆分成数组
String[] editMessage = inputMessage.split(":");
if (editMessage != null && editMessage.length > 1) {
//根据输入的信息将文件中内容替换
message=message.replace(editMessage[0], editMessage[1]);
}
}
}
System.out.println("修改后的内容:" + "\r\n" + message);
}
在文件7-4中,editFile()是Notepad类中的修改文件方法。第13~23行代码,使用while循环实现多次替换文件内容的功能,每次由第14行代码获取替换的指令,再由第17行代码将其根据“:”拆分成数组(:是英文格式的),并再将内容的全局变量message进行替换,此时注意判断数组长度,否则容易报数组下标越界的错误。
(5)编写保存文件方法,如文件7-5所示。
文件7-1 saveFile()
/**
* 保存 新建文件存在用户输入的路径 打开的文件将原文件覆盖
*/
private static void saveFile() throws IOException {
Scanner sc = new Scanner(System.in);
FileWriter out = null;
if (filePath != null) {// 文件是由“打开”载入的
out = new FileWriter(filePath);//将原文件覆盖
} else {//新建的文件
System.out.print("请输入文件保存的绝对路径:");
String path = sc.next();//获取文件保存的路径
filePath = path;
//将输入路径中大写字母替换成小写字母后判断是不是文本格式
if (!filePath.toLowerCase().endsWith(".txt")) {
filePath += ".txt";
}
out = new FileWriter(filePath);// 构造输出流
}
out.write(message);//写入暂存的内容
out.close();//关闭输出流
message = "";// 修改文件前现将写入内容置空
filePath = null;//将文件路径至null
}
在文件7-5中,saveFile()方法是Notepad类中的保存文件的方法。第7行代码,根据路径的全局变量是否为空,来判断此时内容的全局变量是新建文件产生的还是打开文件产生的,如果是打开文件产生的,则用第8行代码,将此文件覆盖掉,如果是新建文件产生的,则执行第10~17行代码。第11行代码获得控制台输入的路径之后,执行第14到16行的代码判断其是否是文本格式,如果不是将其转换成文本格式。第19行代码将message中暂存的内容写入文本文件。最后第21和22行的代码将文件路径和文件内容的全局变量message置空,完成了一次对文本的操作。
(6)编写退出程序的方法,如文件7-6所示。
文件7-1 exit()
/**
* 退出
*/
private static void exit() {
System.out.println("您已退出系统,谢谢使用!");
System.exit(0);
}
在文件7-6中,exit()方法是Notepad类中的退出的方法。第6行代码执行了退出的命令。
【实验7-5】 微信投票
【任务介绍】
1.任务描述
如今微信聊天已经普及到几乎每一个人,在聊天中,经常会有人需要帮忙在某个APP中投票。本案例要求编写一个模拟微信投票的程序,通过在控制台输入指令,实现添加候选人、查看当前投票和投票的功能。每个功能的具体要求如下。
(1) 用户输入指令1代表”添加候选人”,可以在本地文件中添加被选举人。
(2) 用户输入指令2代表”查看当前投票”,将本地文件中的数据打印到控制台。
(3) 用户输入指令3 代表”投票”功能,在控制台输入被投票人的名字进行投票操作。
(4) 用户输入指令4代表”退出”操作。
2.运行结果
任务运行结果如图7-1所示。
运行过程中,本地D:\下会生成一个count.txt文件,打开后如图7-2所示。
【任务目标】
-
学会分析”投票小功能”任务实现的逻辑思路。
-
能够独立完成” 投票小功能”程序的源代码编写、编译以及运行。
-
能够利用字符流操作本地的方法。
-
掌握StringBuffer和数组的使用。
【实现思路】
(1) 查看任务介绍和运行结果分析可知,此任务需要使用while循环实现控制台中操作指令的多次输入,使用switch语句根据控制台输入的操作指令来判断执行什么操作。
(2) 输入指令1时进行添加候选人操作,先从控制台获取输入的被投票人与起始票数并暂存,再判断是否已有count.txt文件存储数据,如果有直接在文件后追加新的数据,如果没有需要新建文件夹在存入数据。这里需要使用到StringBuffer的字符串拼接来实现存入count.txt文件的特定格式,例如:“姓名:票数,”,这样方便我们读取和修改票数。
(3) 输入指令2时查看当前投票,使用字符流读取count.txt文件并在控制台打印即可。
(4) 输入指令3时进行投票操作,从控制台输入被投票人姓名,投票成功后,被投票人的票数加一,先取出count.txt的内容存入String类型中,根据“,”拆分为String数组(editMessage),再获取控制类中输入的被投票人姓名,使用for循环判断是否存在此人的投票,如果存在,则取出此人的票数加一,最后将修改后的数据使用StringBuffer替换到String数组(editMessage)并覆盖原有的count.txt文件
(5) 输入指令4,直接退出系统,可以通过代码”System.exit(0);”实现。
【实现代码】
投票小功能的代码实现如文件7-1所示。
文件7-1 vote.java
package chapter0704;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.util.Scanner;
public class vote {
private static String message = "";
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
System.out.println("1:添加候选人 2:查看当前投票 3:投票 4:退出");
while (true) {
System.out.print("请输入操作指令:");
int command = sc.nextInt();
switch (command) {
case 1:
System.out.println("请输入需要被投票的人:");
String name=sc.next();
System.out.println("请输入"+name+"的起始票数:");
int num=sc.nextInt();
addvote(name,num);// 1:添加投票人
break;
case 2:
String me = readvote();// 2:查看投票
System.out.println(me);
break;
case 3:
editvote();// 3:投票
break;
case 4:
System.out.println("您已退出系统,谢谢使用!");
System.exit(0);
break;
default:
System.out.println("您输入的指令错误!");
break;
}
}
}
/*
* 查看投票
*/
public static String readvote() throws Exception{
FileInputStream in = new FileInputStream("D:\\count.txt");
byte[] b = new byte[in.available()];
in.read(b);
message =new String(b);
in.close();
return message;
}
/*
* 添加投票
*/
public static void addvote(String name,int num) throws
FileNotFoundException {
String SEPARATE_FIELD = "\n";// 换行
BufferedOutputStream out = null;
StringBuffer sbf = new StringBuffer();// 拼接内容
File file=new File("D:\\count.txt"); //判断文件是否存在
try {
// 当已存在count.txt文件,则在文件内容后追加
if (file.canExecute()) {
// 创建输出流,用于追加文件
out = new BufferedOutputStream(new
FileOutputStream("D:\\count.txt", true));
} else {// 不存在当天文件,则新建文件
// 创建输出流,用于保存文件
out = new BufferedOutputStream(new
FileOutputStream("D:\\count.txt"));
}
sbf.append(name).append(":");
sbf.append(num).append(","+SEPARATE_FIELD);
String str = sbf.toString();
byte[] b = str.getBytes();
for (int i = 0; i < b.length; i++) {
out.write(b[i]);// 将内容写入本地文件
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null)
out.close();// 关闭输出流
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
/*
* 投票 将控制台输入的姓名的票数加一后保存
*/
private static void editvote() throws Exception {
FileWriter out = null;
// 将count.txt文字根据“,”拆分成数组
String[] editMessage = message.split(",");
Scanner sc = new Scanner(System.in);
System.out.println("请输入要投票人的姓名");
String inputMessage = sc.next();
//for循环拆分后的数组
for (int i = 0; i < editMessage.length; i++) {
//当数组中有有包含输入的名字时
if(editMessage[i].contains(inputMessage)) {
//取出输入名字现在的票数
String a =
editMessage[i].substring(editMessage[i].indexOf(":")+1,
editMessage[i].length());
//将取出的票数强转为int类型
int b = Integer.parseInt(a);
//将票数+1
b++;
//new出一个StringBuffer用于后面的字符拼接
StringBuffer sb = new StringBuffer();
//取出原文件中的姓名和:
String c=editMessage[i].substring(0,editMessage[i].indexOf(":")+1);
//使用StringBuffer拼接姓名和选票
sb.append(c);
sb.append(b);
sb.append(",");
//将拼接后的字符强转为String类型
String s = sb.toString();
//修改好的字符替换原有的字符
System.out.println("投票后的票数:");
System.out.println(s);
editMessage[i] = s;
}
}
//editMessage是数组,我们需要将editMessage数组拼接为StringBuffer类型。
StringBuffer sb1 = new StringBuffer();
for (int i = 0; i < editMessage.length; i++) {
sb1.append(editMessage[i]);
}
out = new FileWriter("D:\\count.txt");//覆盖原有文件
//sb1是StringBuffer类型,需要使用toString()方法
out.write(sb1.toString());// 写入暂存的内容
out.close();
}
}
在文件7-1中,第十行代码创建了文件内容的全局变量。第14~40行代码是循环主体,用于循环接收用户的指令,当16行代码获取指令后,通过switch语句判断应该执行的操作,第45-52行代码是查看投票功能,第56-90行代码是添加投票功能,使用StringBuffer拼接字符串再存入本地文件。第94-139行代码是投票功能,先将count.txt的内容使用split方法拆分为数组,在循环查询出需要增加投票的人,将票数加一后暂存,再将修改好的字符替换原有的count.txt文件。