数据表示实验

实验题目

数据表示实验

实验目的

掌握结构数据的保存和读取的方法。

参考资料

  • C语言函数分类:http://msdn.microsoft.com/zh-cn/library/2aza74he(v=vs.110).aspx
  • C语言字符串函数:http://msdn.microsoft.com/zh-cn/library/f0151s4x(v=vs.110).aspx

实验环境

  • Windows + VS 2012 http://172.18.187.11/netdisk/default.aspx?vm=16net
  • Linux + gcc

这里我使用的环境是Windows 10 + VSCode + gcc version 8.1.0 (x86_64-posix-sjlj-rev0, Built by MinGW-W64 project)

实验内容

结构数据保存和读出

实验要求

循环输入员工(Person)的信息,每输入一个员工的信息,立即写入文件(Persons.stru),直到输入的姓名为空时跳出循环。然后,读出该文件,显示每个Person的信息。 Person的信息表示:

struct Person
{
  char username[USER_NAME_LEN];      // 员工名
  int level;                         // 工资级别
  char email[EMAIL_LEN];             // email地址
  DWORD sendtime;                    // 发送时间
  time_t regtime;                    // 注册时间
};

源代码

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long DWORD;
struct CSV //按照CSV格式保存。
{
	vector<vector<string>> v;
	void get(istream &is)
	{
		string s;
		for (v.clear(); getline(is, s);)
		{
			v.push_back({});
			for (istringstream sin(s); getline(sin, s, ',');)
				v.back().push_back(s);
		}
	}
	void put(ostream &os)
	{
		for (int i = 0; i < v.size(); ++i)
			for (int j = 0; j < v[i].size(); ++j)
				os << v[i][j] << (j + 1 < v[i].size() ? ',' : '\n');
	}
};
struct Person
{
	string username;				   // 员工名
	int level;						   // 工资级别
	string email;					   // email地址
	DWORD sendtime;					   // 发送时间
	time_t regtime;					   // 注册时间
	bool get(istream &is, ostream &os) //从is中读入,并将提示信息写入os;若读入终止返回false
	{
		os << "Input username: ";
		getline(is, username);
		while (!username.empty() && username.back() == ' ')
			username.pop_back(); //删除多余空格
		if (username.empty())	//读入空串,说明已经终止
			return 0;
		os << "Input level: ";
		is >> level;
		os << "Input email: ";
		is >> email;
		os << "Input sendtime: ";
		is >> sendtime;
		os << "Input regtime: ";
		is >> regtime;
		string s;
		return getline(is, s), 1; //要读掉上一行的行末的回车,以免对下一次读入产生影响
	}
	void getCSV(vector<string> &v) //从CSV的某一行读入信息
	{
		username = v[0];
		level = stoi(v[1]);
		email = v[2];
		sendtime = stoull(v[3]);
		regtime = stoull(v[4]);
	}
	string putFileCSV() //返回CSV格式的信息
	{
		return username + "," + to_string(level) + "," + email + "," + to_string(sendtime) + "," + to_string(regtime) + "\n";
	}
	friend ostream &operator<<(ostream &os, Person &p) //打印p的信息到os
	{
		return os << "username: " << p.username << "\n"
				  << "level: " << p.level << "\n"
				  << "email: " << p.email << "\n"
				  << "sendtime: " << p.sendtime << "\n"
				  << "regtime: " << p.regtime << "\n\n";
	}
};
int main()
{
	ofstream fout("Persons.stru");
	for (Person p; p.get(cin, cout);) //从屏幕读入Person直到空
		fout << p.putFileCSV();		  //以CSV格式写入文件
	fout.close();
	ifstream fin("Persons.stru");
	CSV csv;
	csv.get(fin); //读入CSV文件
	fin.close();
	for (auto line : csv.v) //对于读入的每一行信息
	{
		Person p;
		p.getCSV(line);
		cout << p;
	}
}

多文件合并保存和读出

实验要求

循环输入多个文件名(不超过200MB,可以自己确定),每输入一个,就把该文件的文件名(最多300字节)、文件大小(long)和文件内容写入文件FileSet.pak中,输入文件名为空时跳出循环。然后,读FileSet.pak,每读出一个文件就把它原来的文件名加上一个序号保存起来。

合并时可以先取得文件大小,然后边读边写。

源代码

数据的交换格式格式使用JSON,正文内容使用Base64编码。

encode.cpp
#include <bits/stdc++.h>
using namespace std;
const int BUFSIZE = 200 << 20;
const string CONVERT_TABLE =
	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	"abcdefghijklmnopqrstuvwxyz"
	"0123456789+/";
char buf[BUFSIZE];
string base64Encode(char sourcebuf[], int buflen)
{
	string ret;
	char split3[3];
	unsigned int split4[4], len = buflen, i = 0;
	while (len--)
	{
		split3[i++] = *(sourcebuf++);
		if (i == 3)
		{
			split4[0] = (split3[0] & 0xfc) >> 2;							   //第一个字节取前6位并将其右移2位
			split4[1] = ((split3[0] & 0x03) << 4) + ((split3[1] & 0xf0) >> 4); //第一个字节取后2位并左移4位 + 第二个字节取前4位并右移4位
			split4[2] = ((split3[1] & 0x0f) << 2) + ((split3[2] & 0xc0) >> 6); //第二个字节取后4位并左移2位 + 第三个字节取前2位并右移6位
			split4[3] = (split3[2] & 0x3f);									   //第三个字节取后六位并且无需移位
			for (int j = 0; j < 4; ++j)										   //从Base64编码表中查询split对应元素索引的Base64编码
				ret += CONVERT_TABLE[split4[j]];
			i = 0;
		}
	}
	if (i)
	{
		if (i == 1)
		{
			split4[0] = (split3[0] & 0xfc) >> 2;
			split4[1] = (split3[0] & 0x03 << 4);
			for (int j = 0; j < 2; ++j)
				ret += CONVERT_TABLE[split4[j]];
			ret += "==";
		}
		if (i == 2)
		{
			split4[0] = (split3[0] & 0xfc) >> 2;
			split4[1] = ((split3[0] & 0x03) << 4) + ((split3[1] & 0xf0) >> 4);
			split4[2] = ((split3[1] & 0x0f) << 2);
			for (int j = 0; j < 3; j++)
				ret += CONVERT_TABLE[split4[j]];
			ret += "=";
		}
	}
	return ret;
}
int main()
{
	ofstream fout("FileSet.pak");
	fout << "{\"data\": [\n";
	int first = 1;
	for (string name; cout << "Input file name:", getline(cin, name);)
	{
		ifstream fin(name, ios::binary);
		if (!fin.is_open())
		{
			cout << "Can not open " << name << "\n";
			continue;
		}
		fin.read(buf, BUFSIZE);
		if (first)
			first = 0;
		else
			fout << " ,\n";
		fout << " {\n"
			 << "  \"name\": \"" << name << "\",\n"
			 << "  \"size\": \"" << to_string(fin.gcount()) << "\",\n"
			 << "  \"content\": \"" << base64Encode(buf, fin.gcount()) << "\"\n"
			 << " }";
	}
	fout << "\n]}";
}
decode.cpp
#include <bits/stdc++.h>
using namespace std;
const string CONVERT_TABLE =
	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	"abcdefghijklmnopqrstuvwxyz"
	"0123456789+/";
string base64Decode(const string &s)
{
	string::const_iterator sourcebuf = s.begin();
	const int buflen = s.size();
	string ret;
	unsigned char split3[3];
	unsigned char split4[4];
	int len = buflen, ec = 0, seg = buflen / 4; //计算共可以切成几组
	while (sourcebuf[--len] == '=')				//判断末尾有几个等号,由此可知最后四个字节该如何处理
		++ec;
	for (int k = 0; k < seg; ++k)
	{
		for (int j = 0; j < 4; ++j) //每四字节为一组进行处理
		{
			int index = 0;
			char c = *(sourcebuf++);
			for (int i = 0; i < CONVERT_TABLE.size(); ++i) //由码索引数
			{
				if (CONVERT_TABLE[i] == c)
				{
					index = i;
					break;
				}
			}
			split4[j] = index;
		}
		if (k + 1 == seg)
		{
			if (ec == 2) //当末尾有两个等号时的处理
			{
				split3[0] = (split4[0] << 2) + ((split4[1] & 0x30) >> 4);
				ret += split3[0];
			}
			else if (ec == 1) //当末尾有一个等号时的处理
			{
				split3[0] = (split4[0] << 2) + ((split4[1] & 0x30) >> 4);
				split3[1] = ((split4[1] & 0x0f) << 4) + ((split4[2] & 0x3c) >> 2);
				ret += split3[0];
				ret += split3[1];
			}
			else
			{
				split3[0] = ((split4[0] & 0x3f) << 2) + ((split4[1] & 0x30) >> 4);
				split3[1] = ((split4[1] & 0x0f) << 4) + ((split4[2] & 0x3c) >> 2);
				split3[2] = ((split4[2] & 0x03) << 6) + split4[3];
				for (int j = 0; j < 3; ++j)
					ret += split3[j];
			}
		}
		else
		{
			split3[0] = ((split4[0] & 0x3f) << 2) + ((split4[1] & 0x30) >> 4);
			split3[1] = ((split4[1] & 0x0f) << 4) + ((split4[2] & 0x3c) >> 2);
			split3[2] = ((split4[2] & 0x03) << 6) + split4[3];
			for (int j = 0; j < 3; ++j)
				ret += split3[j];
		}
	}
	return ret;
}
int main()
{
	ifstream fin("FileSet.pak");
	unordered_map<string, string> mp;
	int cnt = 0, tmp;
	for (string s; getline(fin, s);)
	{
		if (s.find("[") != s.npos)
			continue;
		if (s.find("]") != s.npos)
			break;
		if (tmp = s.rfind(","), tmp != s.npos)
			s.erase(tmp);
		if (s.find("}") != s.npos)
		{
			ofstream fout(to_string(++cnt) + "." + mp["name"]);
			fout << base64Decode(mp["content"]);
			mp.clear();
		}
		else if (tmp = s.find(":"), tmp != s.npos)
		{
			string t = s.substr(tmp + 1);
			s.erase(tmp);
			s.erase(s.find_last_not_of("\" ") + 1);
			t.erase(t.find_last_not_of("\" ") + 1);
			mp.insert({s.substr(s.find_first_not_of("\" ")), t.substr(t.find_first_not_of("\" "))});
		}
	}
}

实验体会

在结构数据保存和读出实验中,使用了CSV表格的格式来存储数据,然而这个格式操作有些繁琐,因此单独设计了一个struct对其进行操作,使得代码更加清晰易读。

在多文件的合并保存和读出实验中,使用了流行的JSON格式来保存数据。然而,使用JSON保存文件的正文时,很难区分JSON本身格式上的内容和文本文档的内容;并且很多文件是不可以按照ascii文本文档的方式打开的。解决方式是使用二进制方式打开文件,并对其内容使用Base64形式进行编码,最终得以解决问题。