目录
如果你觉得这篇文章帮到你的话,可以点个赞,这是对作者最好的鼓励
实验要求
一、实验目的
1.掌握面向对象程序设计的基本方法
2.应用Java语言编写应用程序
二、实验要求:
1. 编写一个Java应用程序,实现对某个目录中的所有Java源程序文件(包含该目录的子目录中的源程序文件)进行统计。统计内容包括:
(1) 目录中每个源程序文件的总行数和空白行数,文件的字节数;
(2) 目录中所有源程序文件合计总行数、合计空白行数、合计文件的字节数。
2. 具体实现要求如下:
(1) 程序运行首先显示如下所示的菜单:
(2) 选择菜单中第1项时,要求输入一个目录名
如果输入目录名称对应的目录不存在或不是目录,则输出:
[目录名称] 不是合法的目录名称!
例如:
如果是合法存在的目录,则对该目录中的Java源程序文件进行分析,分析内容包括:
细节部分:每个源程序文件的行数、其中空行数、字节数。
合计部分:源程序文件个数、源程序文件行数、其中空行数、总的字节数。
注意,分析时包括输入目录中的所有子目录。
分析的结果的保存要求:
在当前项目目录中建立一个名为result的目录,结果文件存放在该目录中。
结果文件是一个文本文件,命名方式是用被分析的目录名作为主文件名,扩展名为目录名。
例如:分析D:demo目录,结果文件名为“demo.txt”。结果文件中数据存放格式如下示例:
其中,
第1行:被分析目录的完整名称
第2行:空行
第3行:Files detail:
第4行:被分析目录的短名称,前面有一个 + 号
第5行:从本行开始依次输出被分析目录中的子目录和源程序文件
如果是子目录,则该行是 + 号 和 子目录的短名称
如果是源程序文件,则该行以 - 号开始,依次是:文件名、总行数、空白行数、字节数
注意:一个目录中如果既有子目录也有源程序文件,则先依次排列子目录,再依次排列文件。并且要按照名称升序排序。同时,每深入一层子目录,要缩进4个空格。
第X行:Total:
第X+1行:目录中总文件个数
第X+2行:目录中总的行数
第X+3行:目录中总的空白行数
第X+4行:目录中总字节数
(3) 选择菜单中第2项时,
如果result目录中还没有结果文件,则显示:还没有分析结果!
如果result目录中已经有结果文件,则以下面格式显示文件列表:
可以查看的结果文件有:
输入文件编号后,显示结果文件,如果输入0表示不查看任何结果。编号输入错误应该提示。
3. 可以使用给定目录进行测试,点击下载(需要华农校园网,具体文件在文末) 测试用目录的压缩包,该目录分析的结果应该如下图:
思路讲解
首先分析题目要求:
- 两个菜单:主菜单以及查看分析结果时的文件列表
- 分析过程:对于源码文件,统计空行数,总行数以及字节数;对于目录文件,排序后递归统计子目录及该目录下所有源码文件总空行数,总行数以及总字节数;最后保存到Result目录的文件
- 查看结果:查看Result的文件列表,而后选择查看文件并输出到控制台
几个主要方法思路介绍:
- 菜单直接输出即可,选项的实现可以用 if-else 也可以用 switch-case
- 源码文件也是字符文件的一种,通过 BufferedReader 中的 readLine 方法从单个.java文件中一行一行循环读取数据,统计空行数,总行数,字节数
- 目录文件需要用深度优先搜索算法,也就是递归,终止条件为找到一个 .java 文件并调用方法 2,循环条件为找到一个目录,递归调用自身
- 保存到文件,本猿用的是 System.setOut 的重定向方法,方便直接输出到文件中
- 读取结果文件与方法 2 的读取源码文件相同
程序类图
代码实现
一、Main 类
//Main类
import View.Menu;
public class Main {
public static void main(String[] args){
Menu menu = new Menu();
menu.showMenu();
System.out.println("程序结束");
}
}
二、Menu 类
//Menu 类
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Scanner;
import element.Directory;
public class Menu {
private Scanner scan = new Scanner(System.in);
//输出菜单
public void showMenu() {
System.out.println("-------MENU-----");
System.out.println("1. 分析目录中的源程序文件");
System.out.println("2. 查看分析结果");
System.out.println("0. 退出程序");
System.out.println("----------------");
System.out.print("请选择:");
chooseFunction();
}
//选择执行的操作
private void chooseFunction() {
int type = scan.nextInt();
if (type == 1)
analyseSrcFiles();//调用下文私有方法
else if (type == 2)
checkResult();//调用下文私有方法
if(type == 1 || type == 2)
showMenu();
}
//操作1
private void analyseSrcFiles() {
System.out.println();
System.out.print("输入目录名称:");
String dirName = scan.next();
//指定分析目录
File dir = new File(dirName);
Directory directory = new Directory(dir);
if(!dir.isDirectory()){
System.out.println("错误:["+dirName+"]不是目录名或不存在!\n");
}else{
//调用directory 类 analyseSubDir 方法
directory.analyseSubDir();
System.out.println("分析完成\n");
}
}
private void checkResult() {
System.out.println();
File file = new File("Result");
File[] files = file.listFiles();
if(files == null){
System.out.println("还没有分析结果!");
}else{
System.out.println("----------------");
for(int i=0; i<files.length; ++i){
System.out.println(i+1 + "." + files[i].getName());
}
System.out.println("----------------");
System.out.print("选择要查看的结果文件(0表示放弃):");
int type = scan.nextInt();
if(type == 0)
return;
//下标 = 序号 - 1
checkFile(files[type-1]);
System.out.println();
}
}
//传入形参:操作的文件,相当于水池
private void checkFile(File file){
BufferedReader br = null;
try {
//指定输入流,相当于水管
br = new BufferedReader(new FileReader(file));
while(true){
String data = br.readLine();
if(data == null)
break;
System.out.println(data);
}
} catch (IOException e) {
//eclipse自动生成的异常处理
e.printStackTrace();
} finally{
//关闭输入流,避免发生其他错误
if(br != null){//确保下文close不会访问空指针
try {
br.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
三、SourceFile 类
//SourceFile 类
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class SourceFile {
private File file;
private long numOfLines;//单个源码文件行数
private long numOfBlank;//单个源码文件空行数
private long bytes;//单个源码文件字节数
//参数为源码文件
public SourceFile(File file){
this.file = file;
bytes = file.length();
}
private void getNumOfLinesAndBlank(){
//文件操作原理同 Menu 类
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(file));
//从源码文件中读取每一行
while(true){
String line = br.readLine();
if (line == null) //文件读取结束
break;
numOfLines++; //每读一行,总行数加一
if("".equals(line)){ //读到空行
numOfBlank++;
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
if(br != null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public long getNumOfLines(){
if(numOfLines == 0) //第一次调用计算后返回,下次无需计算直接返回
getNumOfLinesAndBlank();
return numOfLines;
}
public long getNumOfBlank(){
if(numOfBlank == 0)//第一次调用计算后返回,下次无需计算直接返回
getNumOfLinesAndBlank();
return numOfBlank;
}
public long getBytes(){
return bytes;
}
}
四、Directory 类
//Directory 类
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Comparator;
public class Directory {
private File directory;
private long numOfSrcFile;//该目录下所有文件数
private long sumLines;//所有源码文件总行数
private long sumBlank;//所有源码文件总空行数
private long sumBytes;//所有源码文件总字节数
//形参为菜单中输入的目录,非法目录已被 Menu 处理,该 File 必为合法
public Directory(File dir) {
directory = dir;
}
private void changeOut(){
File content = new File("Result");//相对当前Project的路径,与src同级
content.mkdir();//生成目录文件夹
File resultTxt = new File("Result\\"+directory.getName() + ".txt");
try {
resultTxt.createNewFile();//生成保存 result 的文件
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
//从控制台重定向输出到文件中
PrintStream ps = new PrintStream(resultTxt);
System.setOut(ps);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void analyseSubDir() {
//提前保存控制台,输出到文件完毕后恢复输出到控制台
PrintStream out = System.out;
changeOut();
System.out.println("[" + directory.getAbsolutePath() + "] Result:\n\nFiles detail:");
dfsAnalyse(directory, 0);//递归统计目录下每个源码文件数据
System.out.println("Total:");
System.out.printf("\t%5d Java Files\n\t%5d lines in files\n\t%5d blank lines\n\t%5d bytes", numOfSrcFile,
sumLines, sumBlank, sumBytes);
System.setOut(out);//恢复输出到控制台
}
//判断文件是否为源码文件
private boolean isJava(File file) {
String name = file.getName();
int len = name.length();
String postName = (String) name.subSequence(len - 5, len);//参数区间左闭右开,获取文件名后五个字母形成的子串
// System.out.println(postName);
return ".java".equals(postName);
}
private void sortFiles(File[] files) {
//匿名内部类构造比较器,对目录下文件排序
Arrays.sort(files, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
if (o1.isFile() && o2.isFile()) {//同为文件,按字典序排序
return o1.getName().compareTo(o2.getName());
} else if (o1.isDirectory() && o2.isDirectory()) {//同为目录,按字典序排序
return o1.getName().compareTo(o2.getName());
} else if (o1.isDirectory() && o2.isFile()) {//目录“小于”文件,排在前面
return -1;
} else if (o1.isFile() && o2.isDirectory()) {
return 1;
} else {
return 0;//理论上不会走到这里,为了确保返回值为 int,编译不报错
}
}
});
}
//File形参为当前递归的文件,level为递归的层数,也是缩进的倍数
private void dfsAnalyse(File file, int level) {
for (int i = 0; i < level; ++i)
System.out.print(" ");
if (file.isDirectory()) {//递归到目录继续递归
System.out.println("+" + file.getName());
File[] files = file.listFiles();
sortFiles(files);
for (int i = 0; i < files.length; ++i) {
dfsAnalyse(files[i], level + 1);
}
} else if (isJava(file)) {//递归文件统计信息并输出
// System.out.println("isJava: " + isJava(file));
System.out.print("-" + file.getName());
SourceFile srcFile = new SourceFile(file);
numOfSrcFile++;
sumBlank += srcFile.getNumOfBlank();
sumLines += srcFile.getNumOfLines();
sumBytes += srcFile.getBytes();
int spaceLength = file.getName().length();
for (int i = spaceLength; i < 35; ++i) {//对齐,为了方便没有统计最长的文件名,随便写了个35
System.out.print(" ");
}
System.out.println("\tTotal:\t" + String.format("%5d", srcFile.getNumOfLines()) + ", Blank:\t"
+ String.format("%5d", srcFile.getNumOfBlank()) + ",\t" + String.format("%5d", srcFile.getBytes())
+ " Bytes");
}
}
}