一、实验原理阐述
本实验意在基于散列(Hash),运用C++编写exe文件(后称云盘.exe)实现云盘上传、下载、查看云盘内容、删除文件的功能。特别的,运用散列实现上传的秒传。根据实验目的,做出以下实验设计。
一、云盘的结构。云盘主要有两部分,一部分是客户端,一部分是云端。客户端储存文件信息需要一个哈希表(hashtable,散列表,下称哈希表),客户端在使用时访问云端的哈希表,而客户端本身也是一种哈希表。因此本实验的客户端用哈希表模拟。云端用电脑中指定的一个文件夹模拟(本程序指定“D:\云盘”文件夹模拟,因此要求测试者在测试前在D盘创建一个名为“云盘”的文件夹。)。客户端用于操作,上传、下载、查看云盘文件内容、删除文件等,对象是许多个人。但是云端则由许多个人通过客户端传送的文件的汇总。一方面,当个人在客户端删除文件时,云端的文件不会受到影响。另一方面,每个人再上传文件的时候,根据云盘中的文件的hash值,区分上传的状态,“普通上传”或“秒传”。下面主要讨论云盘上传原理。
二、云盘的上传。云盘的上传分为“普通上传”和“秒传”,如果在云端不存在同样内容的文件,则为“普通上传”,需要花费上传时间。如果在云端存在同样内容的文件,则为“秒传”,不需要花费传送时间。根据这个特点,结合散列表的性质,可以知道文件上传之后信息会储存在哈希表中,每个文件经过一个hash函数,得出一个对于这个内容独特的hash值。每个上传的文件的内容也会经过同样的hash函数计算求出它的hash值,并查看散列表对应hash值的空间是否已被占用,若被占用,说明云端上已存在可能文件名不一样而内容一样的文件,则无需再花费时间上传,故实现“秒传”,然后在客户端记录上传资料的信息。本实验用电脑的“复制文件”功能模拟打开云盘客户端上传文件到云端的操作。而C++自身代码是不能够完成对电脑的文件复制转移功能的,因此需要调用cmd。
三、云盘的下载。本实验用将文件从“云端”(文件夹“D:\云盘”)复制要下载的文件到“下载目录”(由测试者指定的目录)来实现云盘的下载功能。同样需要调用cmd。
四、查看云盘中的文件。在“云盘.exe”中输出上传的。
五、删除云盘中的文件。原理上说,删除客户端中的文件是不会影响到云端的文件的,因此只是将要删除的指定文件夹的信息从客户端的哈希表中删除。
根据以上实验设计,下面使用代码实现模拟云盘。
二、代码实现
1.首先构造哈希表的类:
#include<iostream>
#include<vector>
#include<fstream>
#include<algorithm>
#include<string>
#include<Windows.h>
#include<cstddef>
using namespace std;
template<typename HashedObj>
class HashTable
{
public:
HashTable( int size = 101 )
{
currentSize = size;
vector< vector<HashedObj> > theLists( currentSize );
this -> theLists = theLists;
};//构造函数
bool contains( const HashedObj & x ) const;
void makeEmpty();
bool insert( HashedObj x, HashedObj path );
bool insert( HashedObj x );
int getsize();
bool remove( const HashedObj & x );
bool isEmpty( const HashedObj & x );
void loadcloud();
void upload();
void download();
void viewfile();
void exit();
void deletefile();
private:
vector< vector < HashedObj > > theLists;
int currentSize;
int hash( HashedObj x ) ;
};
2.各函数的实现:
判断是否包含内容 ( contains函数 ):
包含则返回1,不包含则返回0;
template <typename HashedObj> bool HashTable<HashedObj>::contains(const HashedObj &x) const
{
const vector<HashedObj> &whichList = theLists[myhash(x,theLists.size())];
return find( whichList.begin(), whichList.end(), x ) != whichList.end();
}
- 清空所有链表 ( makeEmpty函数 ):
template <typename HashedObj> void HashTable<HashedObj>::makeEmpty()
{
int size0 = theLists.size();
for(int i=0;i<size0;i++)
{
theLists[i].clear();
}
cout<<"已清空云盘"<<endl;
}
- 插入内容 ( insert函数 ):
判断要插入的内容hash值是否冲突:
若冲突,则返回0;
若不冲突,则在hash值对应的向量后面第一格插入内容,第二个插入文件路径,并且返回1。
template <typename HashedObj> bool HashTable<HashedObj>::insert( HashedObj x, HashedObj path )
{
vector< HashedObj > & whichList = theLists[ hash( x ) ];
if( find( whichList.begin(), whichList.end(), x ) != whichList.end() ) {
//cout<<"冲突"<<endl;
return false;
}
whichList.push_back( x );
size_t found = path.find_last_of("/\\");
//cout << path.substr( found + 1 )<<endl;
whichList.push_back( path.substr( found + 1 ) );
return true;
}
- 获得散列大小 ( getsize函数 ):
template <typename HashedObj> int HashTable<HashedObj>::getsize()
{
return theLists.size();
}//获得散列大小
- 移除一个元素 ( remove函数 ):
判断元素是否移除:
用find函数遍历哈希表,如果没有要删除的内容,返回0;
如果找到要删除的内容,则调用vector类的erase函数删除该处的内容,并返回1;
template <typename HashedObj> bool HashTable<HashedObj>::remove( const HashedObj & x )
{
vector<HashedObj> & whichList = theLists[ hash( x ) ];
vector<HashedObj>::iterator itr = find( whichList.begin(), whichList.end(), x );
if( itr == whichList.end() )
return false;
whichList.erase( itr );
--currentSize;
return true;
}
- 哈希函数 ( hash函数 ):
计算输入的内容的hash值。
template <typename HashedObj> int HashTable<HashedObj>::hash( HashedObj x )
{
int a,b,c;
a = x[ x.size() - 1 ];
b = x[ 0 ];
c = x[ ( x.size() - 1 ) / 2 ];
return ( a + b + c ) % 99;//意味着只有99格可以存放
}
- 上传文件 ( upload函数 ):
输入需要上传的文件名(包含地址,形如:D:\新建文件夹\1.txt)。
1.如果输入0,则返回输入上传操作指令步骤;
2.如果输入不存在的文件名,返回“该文件不存在”;
3.如果输入正确的文件则读取文件内容,计算hash值并判断是否冲突。若不冲突,则存入哈希表,上传(调用cmd复制文件)到位于D盘的“云端”文件夹,并输出“状态:正常上传”;若冲突,则只输入“状态:秒传”。
template <typename HashedObj> void HashTable<HashedObj>::upload()
{
string str;
cout << "请输入需要上传的文件:(输入0返回操作选择)" << endl;
cin >> str;
if( int(str[0]) != 48 ) {
string b;
if( isfileexist( str ) ) {
b = loadfile( str );
//cout<<"^"<<b<<endl;
//cout<<hash( b )<<endl;
cout << "文件 " << str << " 已上传" << endl;
if( insert( b, str ) ) {
cout << "状态: 普通上传" << endl;
//复制到云端的文件夹
string cmd1 = "xcopy ";
string cmd2 = " D:\\云盘";
string cmd = cmd1+str+cmd2;
//cout<<cmd<<endl;
//int cmdsize = cmd.size();
const char*c = cmd.c_str();
system( c );
}
else
cout << "状态: 秒传" << endl;
}
else
cout<<"该文件不存在"<<endl;
}
else ;
};
- 下载文件 ( download函数 ):
输入需要下载的文件名(包含地址,形如:D:\新建文件夹\1.txt)。
输入下载目录(形如:D:\)。
把所选文件从云端下载(调用cmd复制)到下载目录中。
template <typename HashedObj> void HashTable<HashedObj>::download()
{
cout<<"请输入要下载的文件名称:(输入0返回操作选择)"<<endl;
string dlocation = "D:\\云盘\\";
string str;
cin >> str;
if( int(str[0]) != 48 ) {
cout<<"请输入下载目录:"<<endl;
string location;
cin >> location;
//cout << dlocation+str <<endl;
string b;
b = loadfile( dlocation + str );
string cmd1="xcopy ";
string space=" ";
//cout<<cmd1+dlocation+str+space+location<<endl;
string cmd = cmd1 + dlocation + str + space + location;
//cout<<cmd<<endl;
int cmdsize = cmd.size();
const char*c = cmd.c_str();
system(c);
//把云端的指定文件复制到指定地址
}
else ;
}
- 查看云盘内容 ( viewfile函数 ):
如果云盘中无文件,则输出“该云盘是空的”。
如果云盘中有文件,则逐个输出文件名(形如1.txt)。
template <typename HashedObj> void HashTable<HashedObj>::viewfile()
{
int k = 0;
string fname;
for( int i = 0; i <= 99; i++ ) {
vector< HashedObj > & whichList = theLists[ i ];
int ListSize = whichList.size();
if( ListSize > 0 ) {
//cout<< whichList[1] <<endl;
fname = whichList[1];
splitfilename( fname );
k = 1;
}
else ;
}
if( k == 0 )
cout << "该云盘是空的" << endl;
}
- 退出云盘 ( exit函数 ):
只输出”退出”。(在main函数中写入break跳出输入云盘命令循环。)
template <typename HashedObj> void HashTable<HashedObj>::exit()
{
cout<<"退出"<<endl;
};
- 删除文件 ( deletefile函数 ):
输入要删除的文件的文件名( 形如1.txt ),若文件正常删除,则输出“文件已删除”,否则输出“文件未删除”。
template <typename HashedObj> void HashTable<HashedObj>::deletefile()
{
string del = "del ";
string dlocation = "D:\\云盘\\";
cout<<"请输入需要删除的文件:(输入0返回操作选择)"<<endl;
string str;
cin>>str;
if( int(str[0]) != 48 ) {
string cmd = del + dlocation + str;
int cmdsize = cmd.size();
const char*c = cmd.c_str();
string a = loadfile( dlocation + str );
if( remove( a ) )
cout<<"文件已删除"<<endl;
else
cout<<"文件未删除"<<endl;
system(c);
}
else ;
}
- 读取文件 ( loadfile函数 ):
读取在path路径下的txt文件内容,以string形式返回。(只能返回txt中第一行第一个空格前或第一行的内容。)
string loadfile( string path )
{
string a;
ifstream f1;
f1.open( path );
f1 >> a;
f1.close();
return a;
}
- 分离文件名称 ( splitfilename函数 ):
将一个路径中的文件名称分离出来。(例如:D:\新建文件夹\1.txt,用splitfilename函数作用后输出“1.txt”。
void splitfilename ( const string & str )
{
size_t found = str.find_last_of("/\\");
cout << str.substr( found + 1 )<<endl;
}
- 加载云盘 ( loadcloud函数 ):
每次启动云盘.exe时加载云盘,读取“云盘”文件夹中所有文件并插入到哈希表中。
template <typename HashedObj> void HashTable<HashedObj>::loadcloud()
{
string DIR = "dir D:\\云盘/on/b>D:\\list.txt";//在D盘的位置生成云端文件夹内容清单
const char *dir = DIR.c_str();
system(dir);
char str[100];
string txtpath = "D:\\list.txt";
const char *tpath = txtpath.c_str();
ifstream f2;
f2.open( tpath );
int i = 0;
while(f2.getline(str,sizeof(str))) {
string dlocation = "D:\\云盘\\";
string sum = dlocation + str;
//const char *d = sum.c_str();
string s;
s = loadfile( sum );
insert( s, str );
}
f2.close();
}
- 判断文件是否存在 ( isfileexist函数 ):
对于输入的路径path,判断是否能够读取该路径文件。若成功读取,返回1,否则返回0。
bool isfileexist( string path )
{
const char* j = path.c_str();
ifstream fin( j );
if(!fin) return 0;
else return 1;
}
- 主函数
打开云盘.exe界面的显示内容、操作指引的设计。在“请选择功能(输入下面功能的编号)”下:若输入1,则执行“上传”操作;输入2,则执行“查看云盘内容”操作;输入3,则执行“下载”操作;输入4,则执行“退出”操作,跳出循环;输入5,则执行“删除文件”操作;其余输入显示“非法命令”。(标注隐藏项将在实验缺陷处提及。)
int main() {
HashTable<string> H(100);
H.loadcloud();//加载云盘
cout<<endl;
cout<<"———这是一个云盘客户端———"<<endl<<endl;
cout<<" made by Yang"<<endl;
cout<<"____________________________________"<<endl<<endl;
cout<<"请选择功能(输入下面功能的编号)"<<endl;
cout<<"1 上传"<<endl;
cout<<"2 查看云盘内容"<<endl;
cout<<"3 下载"<<endl;
cout<<"4 退出"<<endl;
cout<<"5 删除文件"<<endl;
cout<<endl;
cout<<"请输入功能的编号"<<endl;
string n;
while( cin >> n ) {
if( n[0] == 49 )
H.upload();
else
if( n[0] == 50 )
H.viewfile();
else
if( n[0] == 51 )
H.download();
else
if( n[0] == 52 ) {
H.exit();
break;
}
else
if( n[0] == 53 )
H.deletefile();
else
if( n[0] == 54 )
H.makeEmpty();//隐藏
else
cout<<"非法命令"<<endl;
cout<<endl;
cout<<"请输入功能的编号"<<endl;
H.loadcloud();
}
return 0;
}
三、实验缺陷
在探索实验过程中,发现本模拟实验存在很多缺陷。其中有:
1.没有很好的区分出云盘和客户端。原因是客户端和云端使用同一哈希表。实验最初为了实现云端的文件在运行“云盘.exe”时自动将文件夹中信息存入哈希表,这可以实现上传的秒传功能,而删除客户端哈希表信息的时候必须同云端的文件一同删除。而代码中”隐藏”项则是只清空散列表而不对云盘文件夹做出任何改变,此处为一大bug。
2.面向对象单一,不能多人使用。
3.当与云端有内容一样但文件名不一样的文件上传时,不会将文件的文件信息存入哈希表中。
4.上传的文件只能是txt文件。
5.hash函数不严谨,可能产生不同的文件也有冲突的情况。
四、实验心得体会
本次实验收获挺多,为了实现云盘去查阅了很多的资料,对散列的理解更加深入,尤其是对云盘的原理也有了更多的了解,开拓了视野,而且发现了乐趣。而且借此机会学习了用C++调用cmd的方法,也意识到string类的便捷性。虽然还存在很多bug,但是也尽力去完成了。
云盘测试指南
一、在D盘目录下新建文件夹并命名为“云盘”。
二、运行“云盘.exe”,看到页面:
三、在光标处输入功能的编号
若需要上传则输入1;
若需要查看云盘内容则输入2;
若需要下载文件则输入3;
若需要退出云盘则输入4;
若需要删除文件则输入5;
四、各功能指引:
1.上传文件
在光标处输入1,在“请输入需要上传的文件中”输入文件名称,格式为“D:\1.txt”,输入完毕之后回车。
可见输出:
普通上传则说明在云端不存在该文件,秒传则说明云端已存在该文件。
2.查看云盘内容
在“请输入功能的编号”下光标处输入2,可见输出。
该云盘是空的则输出:
若非空,则输出:
3.下载文件
在光标处输入3,在“请输入需要上传的文件中”输入文件名,格式为:1.txt,输入完毕之后回车。再在“请输入下载目录”处输入下载目录,格式为“D:\新建文件夹”,输入完毕之后回车。
可见输出:
4.删除文件
在光标处输入4,在“请输入需要上传的文件中”输入文件名,格式为:1.txt,输入完毕之后回车。
可见输出:
5.退出
在光标处输入5,回车。输出“退出”并点击任意键即可退出。
注:上传、下载和删除文件操作时可以输入0退出该操作并重新输入操作指令。