软件工程第二次作业--文件读取

222100303陈昕 2024-03-02 20:24:29

目录

  • 1.Gitcode项目地址
  • 2.PSP表格
  • 3.解题思路描述
  • 3.1 json解析
  • 3.2数据提取(以功能一为例)
  • 3.3 读取并分析指令
  • 3.4 数据输出
  • 4.接口设计和实现过程
  • 4.1 接口设计
  • 4.2实现过程
  • 5.关键代码展示
  • 5.1 解析json获取实体类对象
  • 5.2 主要流程
  • 5.3 指令判断
  • 5.4 输出至文件
  • 6.性能改进
  • 7.单元测试
  • 8.异常处理
  • 9.心得体会

这个作业属于哪个课程2302软件工程社区-CSDN社区云
这个作业要求在哪里软件工程第二次作业--文件读取-CSDN社区
这个作业的目标json文件读取
其他参考文献代码规范

1.Gitcode项目地址

项目地址:qq_61965860 / Project C · GitCode

2.PSP表格

PSPPersonal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划2530
• Estimate• 估计这个任务需要多少时间2530
Development开发9301140
• Analysis• 需求分析 (包括学习新技术)90120
• Design Spec• 生成设计文档3045
• Design Review• 设计复审1515
• Coding Standard• 代码规范 (为目前的开发制定合适的规范)2530
• Design• 具体设计4570
• Coding• 具体编码500600
• Code Review• 代码复审4560
• Test• 测试(自我测试,修改代码,提交修改)180200
Reporting报告105200
• Test Repor• 测试报告45120
• Size Measurement• 计算工作量3040
• Postmortem & Process Improvement Plan• 事后总结, 并提出过程改进计划3040
合计10601370

3.解题思路描述

3.1 json解析

这次作业使用了picojson进行解析。大致流程为

1.格式化json数据,了解数据的大概结构(以文件example_datas_athletes.json为例)

根对象r可以视作一个以国籍划分的数组,每个数组项a[i]对应一个国家,数组项中包含有CountryName、Participations字段:

其中CountryName为字符串类型,对应我们要提取的信息“国籍”;

Participations字段保存了以运动员为单位的数组,该数组每一项对应一名运动员,数组项内包含Gender、PreferredLastName和PreferredFirstName字段,分别为我们另外两个要提取的内容。

img

2.程序中访问json文件并读入其中内容。

3.picojson解析。picojson::parse(picojson::value jsonData,std::string json_data);

3.2数据提取(以功能一为例)

每条指令的输出结果由大量小项组成。故采用了vector向量保存功能一、二所提取的数据。

  • 首先将根对象获取为picojson::array;
  • 第一层循环:循环访问Country数组对象,每一循环对象对应一个国家;提取字符串数据"CountryName";提取数组"Participations"
  • 第二层循环:循环访问Participations数组对象,每一循环对象对应一名运动员;提取字符串数据Gender、PreferredLastName和PreferredFirstName。
  • 结合上一层循环提取的"CountryName"字段,至此一名运动员的信息提取完毕,保存入vector中。
//部分代码
const picojson::array& countries = jsonData.get<picojson::array>();
const picojson::array& participations = country.at("Participations").get<picojson::array>();
athlete.gender = (athleteData.at("Gender").get<double>() == 0) ? "Male" : "Female";

3.3 读取并分析指令

使用流从input.txt文件中按行读取指令。采用循环形式。每读取一条指令为一次循环。循环内容包括

  • 判断指令是否合法;
  • 判断指令类型(对应功能一/功能二/附加功能);
  • 调用指令内容对应的指令函数;
  • 其他操作包括异常处理,输出等

img

3.4 数据输出

根据3.2中内容,首先对指令进行合法性判断:

  • 若指令不合法(含大写字母):则无需执行,直接输出ERROR
  • 若指令不正确:则无需执行,则输出N/A
  • 若指令合法:首先判断指令属于哪一功能(编号为1、2、3对应三种功能),再根据判断结果分别调用对应函数。解析后调用专门函数向output.txt文件写入。

4.接口设计和实现过程

4.1 接口设计

1.定义类Players。类中包含了实现获取运动员数据的相关函数.调用类中getPlayers()即可获取解析结果.

2.定义类Results。类中包含了实现获取赛况数据的相关函数.调用类中getResults()即可获取解析结果.

3.2实现过程
导入头文件"picojson.h",对应函数内部解析对应数据

然后调用内部api实现searchTeam与searchWinner的方法,去查找胜利的队伍内对应玩家的简称的关键代码

getPlayers函数与getResults函数只需要调用对应api即可

4.2实现过程

三个功能分别定义了三个结构体用来保存对应信息,分别为Player,Result,Detail

// 选手信息
struct Athlete {
    string fullName;
    string gender;
    string country;
};

// 比赛结果 
struct Result {
    std::string fullName;
    string rank;
    std::string score;
};

三个类分别单独执行各自功能Players, Results, Details.以功能一Players类为例

class Players{
    private: 
        vector<Athlete> athletes;
    public: 
        //解析出功能一所需数据 
        vector<Athlete> playerParse(picojson::value jsonData);    
        string getString(vector<Athlete> athletes);
        //返回完整解析结果
        string getPlayers();
}; 

其他函数

string toLowerCase(const std::string& str);//将字符串转换为小写
string getJsonData(string jsonFile);//打开并读取json文件内容(未解析)
map<string,string> getAllFileName();//解析所有项目详细文件名并保存至map
int checkInstruction(const std::string& instruction);//判断指令是否合法和属于哪一功能

执行流程(仅展示功能一 /二 ,附加功能与功能二大致相似)

img

问题1(输出所有选手信息)

打开所需的文件,并读取其中内容

利用picojson将内容进行解析

从内容中提取出所需要的详细部分:通过二重循环读取PreferredLastName、Gender、CountryName。

将完整结果转换为字符串待输出

问题2(输出决赛每个运动项目结果)

根据input.txt中的指令获取所对应的项目

打开“event.json”文件,读取其中内容进行解析。DisciplineName即比赛项目名称,通过对其进行匹配来查找,当查找到对应数组项时,获取该数组项的属性'"Id',即项目对应编号。在results文件夹中存放的json文件,id对应字符串即为项目对应详细内容json文件。

根据步骤二,即可获得json文件名,根据文件名打开文件,并读取文件内容和解析。

首先获取根对象root;获取根对象下的数组Heats,Heats的数组项即为初赛、半决赛、决赛具体赛况。数组项内有属性“Name",匹配得Name为”Final“的数组项即为决赛赛况。获取Final内的”Results“并解析为数组,result中每一数组项为每名运动员的得分详情。

将完整结果转换为字符串待输出

5.关键代码展示

此处展示的代码有所简化,仅呈现思路

5.1 解析json获取实体类对象

const picojson::array& countries = jsonData.get<picojson::array>();//获取根对象
    for (picojson::array::const_iterator it = countries.begin(); it != countries.end(); ++it) {
    const picojson::object& country = it->get<picojson::object>();
    const picojson::array& participations = country.at("Participations").get<picojson::array>();

    for (picojson::array::const_iterator it2 = participations.begin(); it2 != participations.end(); ++it2) {
        const picojson::object& participation = it2->get<picojson::object>();
        const picojson::object& athleteData = participation;
        //结构体保存运动员信息
        Athlete athlete;
        athlete.fullName = athleteData.at("PreferredLastName").get<string>() + " " + athleteData.at("PreferredFirstName").get<string>();
        athlete.gender = (athleteData.at("Gender").get<double>() == 0) ? "Male" : "Female";
        athlete.country = country.at("CountryName").get<string>();
        athletes.push_back(athlete);//向量保存所有运动员
            }
        }

5.2 主要流程

    ifstream inputFile(input);
    if (inputFile.is_open()) {
        ofstream outputFile(ouput, ios::app);//打开output文件 
        stringstream outputBuffer;//缓冲流 
        while (getline(inputFile, line)) {
            //判断指令类型
            //不合法输出ERROR 或 N/A
            string res = output[line];//map保存记录
            if (checkInstruction(line) == 1) {//功能一
                if (res == "") {//该指令未执行过
                    Players p;
                    res=p.getPlayers();//执行
                    output[line]=p.getPlayers();//将结果保存至map
                }
            }
            if (checkInstruction(line) == 2) {
                if (res == "") {
                    Results f2;
                    res = f2.getResults(line, filePath);
                    output[line] = res;
                }
            }
            outputBuffer << res; //指令结果输出至缓冲流
        }
        outputFile << outputBuffer.str();
        //关闭流
    }

5.3 指令判断

int checkInstruction(const string& instruction) {
    //首先判断是否含有大写字母 
    for (……) {    if (isupper(instruction[i])) return 0; // 包含大写字符,返回0}
    //判断指令合法
    if (instruction.substr(0, 7) != "result " || instruction.substr(0, 8) != "players ")
        return 0;
    //功能一 
    if (instruction == "players") return 1;
    
    else if (instruction.substr(0, 7) == "result ")
        //省略代码(判断比赛项目是否合法)
        return 3;
    //比赛项目不合法
    return 4;//输出为N/A 
}

5.4 输出至文件

    //打开output文件 
    ofstream outputFile(output, ios::app);
    if (!outputFile.is_open()) {
        //异常处理
    }
    stringstream outputBuffer;//缓冲流 
    while (getline(inputFile, line)) {
        string res ;//每条指令的执行结果
        //……
        //输出至缓冲流
        outputBuffer << res; 
    }
    outputFile << outputBuffer.str();
    outputFile.close();

6.性能改进

优化前

优化前的代码写的质量很差,导致耗时很惊人,后续优化空间很大。其内容为

1.每读取一条指令,便需要解析一次json文件,同时解析完毕后立即写入output.txt

2.每次执行功能二或附加功能时,都需要先解析events.json获取项目具体id,再利用id解析具体项目文件

第一次优化

删除原本代码中多余的语句,或合并可合并或简略的步骤

第二次优化

功能二和附加功能要求输出成绩,在初始阶段都要求根据指令中的比赛项目名称,访问并解析event.json文件,获取该比赛项目的对应ID,根据ID可以得知该项目详细赛况的json文件名称,即id.json。
同时功能二和附加功能指令仅“detail”不同,但同一项目访问的文件地址相同。用map保存指令项目对应的地址。当下次访问同一项目的功能二或附加功能指令时,即可略去解析获取文件名/地址的步骤

第三次优化

观察可得,有效指令的数目是有限的。当input输入的指令条数大量后。必然有大部分的指令出现重复情况。在原代码中。每次循环读入一条指令后,便根据该指令对文件进行一次解析。该过程中的①访问具体json文件并读取②解析文件内容 较为复杂。随着指令规模的增加,运行耗时将猛增。
为此采用定义了map output,用以保存每条指令对应的输出结果(string形式)。读取指令后,查找map中是否已有保存的记录。如果有则输出;否则调用函数执行该指令相关的读取解析操作,并在解析完毕后将结果存入map

第四次优化

在原代码中,每次循环读入一条指令,相关操作获取结果后进行一次输出。输出活成包括output.txt文件的打开关闭和写入。
频繁访问文件会造成时间的大量消耗。

批量写入:每读取一条指令后,不立即写入文件。等处理完所有指令后,再一次性将这些内容批量写入文件。

内存缓冲区:使用字符串缓冲区缓冲区来存储输出内容,不是每次都直接写入文件,适当的时机将缓冲区中的内容一次性写入文件。这样可以减少文件操作的次数,提高效率。

7.单元测试

测试采用vs2022的Microsoft c++单元测试框架进行测试

部分函数测试代码如下:

        TEST_METHOD(Test_toLowerCase)//将字符串转换为小写
        {
            // 准备测试数据
            std::string input = "HelloWorld";
            std::string expectedOutput = "helloworld";

            // 调用被测试的函数
            std::string result = toLowerCase(input);

            // 断言结果是否与期望相符
            Assert::AreEqual(expectedOutput, result);
        }
        TEST_METHOD(Test_resultParse)//对json内容进行解析
        {
            // 准备测试数据
            Results results; // 替换为实际的类名S
            picojson::value testData; // 替换为实际的测试数据
            std::string jsonStr = "{\"Heats\":[{\"Name\":\"Final\",\"Results\":[{\"FullName\":\"John                     Doe\",\"Dives\":[{\"DivePoints\":\"9.8\"},{\"DivePoints\":\"9.9\"},            {\"DivePoints\":\"9.7\"}],\"TotalPoints\":\"29.4\"}; //此处进行了省略
            string err = picojson::parse(testData, jsonStr);
            Assert::IsTrue(err.empty());
            vector<Result> result = results.resultParse(testData);// 调用被测试的函数
            // 断言结果是否与期望相符
            Assert::AreEqual(static_cast<size_t>(2), result.size());
            Assert::AreEqual(std::string("John Doe"), result[0].fullName);
            Assert::AreEqual(std::string("9.8 + 9.9 + 9.7 = 29.4"), result[0].score);
            //后续相似代码在此处省略
        }

img

8.异常处理

文件个数异常

argv 是一个指向字符指针的数组,每个指针指向一个以 null 结尾的 C 字符串,表示一个命令行参数的内容。(argc 的值至少为 1,包括程序名称本身,所以如果有两个额外的命令行参数,argc 的值将为 3。)

if (argc != 3) 的判断条件是为了确保命令行参数的数量符合预期,即有两个额外的命令行参数(除了程序名称本身)。如果数量不符合预期,代码会输出错误信息并退出程序。

    if (argc != 3) {
        std::cout << "输入的文件个数有误!" << std::endl;
        return 0;
    }

文件打开异常/读取异常

调用至该语句时,则使用try-catch 块来捕获可能发生的异常,并在出现异常时抛出自定义的 std::runtime_error 异常。

try 块中,我们将 JSON 文件的内容读入字符串 json_data 中。如果读取过程中发生异常,我们将关闭文件并抛出带有错误信息的异常。

std::ifstream inputFile(jsonFile.c_str());
if (!inputFile.is_open()) {
    throw std::runtime_error("Failed to open the input file."); // 抛出异常
}
try {
        // 将 JSON 文件内容读入字符串
        json_data.assign((std::istreambuf_iterator<char>(inputFile)),
                         std::istreambuf_iterator<char>());
    } catch (const std::exception& e) {
        inputFile.close();
        throw std::runtime_error("Failed to read JSON data: " + std::string(e.what())); // 抛出异常
    }

9.心得体会

这次作业的难点主要在于
对json数据的解析(功能二和附加功能):没有解析json的操作,为此查看了文档的说明和网上的案例示范。学习已有代码的思路语法,扩展至三个功能的解析。
单元测试的编写:以往并没有单元测试的经验,拿到题目的时候一头雾水,查看了作业中提供的链接,对测试有了初步的了解。为了进一步进行实践操作 ,还需要在网上查阅了相关案例博客以及阅读vs单元测试的手册。
性能提升:性能测试是一个循序渐进的过程,每次的尝试可以获得不同程度的优化(负优化),同时对于结果还需要进行大致的分析。修改代码也花费不少时间。
通过本次实践,学习了json解析的方法和一些编程的规范。PSP表格对工作进行量化,有利于更好的安排,但是预期和实际有所差距,仍需进一步改善评估。同时发现作业耗时较久,要解决拖延症问题和提高效率。

...全文
54 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

122

社区成员

发帖
与我相关
我的任务
社区描述
FZU-SE
软件工程 高校
社区管理员
  • LinQF39
  • 助教-吴可仪
  • 一杯时间
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧