去重行动

一、项目功能:通过比对两个文件的数字签名,进行判断两个文件是否完全一致,若一致则删除重复文件

数字签名:只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明
MD5:一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。

在这里插入图片描述
由上图可知文件内容不同,所产生的MD5值是不同的

二、MD5值是如何产生的

  • 添加冗余信息
  • 添加bit长度
  • 初始化MD buffer(A,B,C,D)
  • 按512位数据逐块处理输入信息
  • 摘要输出

文件位长度(原始长度):填充在最后的64bit


文件的最终长度满足 % 512 =0


(文件的原始长度+冗余信息)%512=448+位长度的填充信息64bit位

一、添加冗余信息:使文件最终的bit长度 % 512 =448,在任何情况下都要进行填充操作
填充内容--> 冗余信息:首位为1,其余为0
二、添加bit长度:
1.bit长度 % 512 <448
此时,8bit<=最后一个数据块<=440bit
填充:首先第一个填充位填1,其余填0,
      其次,剩余64bit位填写文件的bit长度

2.bit长度 % 512 >=448
此时,456bit<=数据块长度<=504bit
填充:首先第一个填充位填1,其余填0
      其次构建一个新的512bit的数据块,前448bit填0,剩余64bit位填文件的bit长度

不管是什么情况,冗余信息最少填充长度为8bit-->1byte
/*
文件内容:abcde             5个字符:5byte-->40bit
填充冗余信息:408bit,第一个bit位为1,其余为0-->(40+408)%512=448
第449位填1,450--512位填0,
构建一个新的512bit的数据块,前448位填0,最后的64bit位:填写文件的位长度即40bit 故填写40(long long 类型)
*/
三、初始化MD buffer(A,B,C,D)
MD5值是128位,用4-word buffer(A,B,C,D)计算得到,A,B,C,D各为一个32bit的变量,这些变量初始化为下面的十六进制值,低
字节在前:
/*
word A: 01 23 45 67
word B: 89 ab cd ef
word C: fe dc ba 98
word D: 76 54 32 10
*/
// 初始化A,B,C,D
_atemp = 0x67452301;
_btemp = 0xefcdab89;
_ctemp = 0x98badcfe;
_dtemp = 0x10325476;

四、按512位数据逐块处理输入信息
1.信息摘要的计算
①按数据块逐块处理,以512bit数据为一个处理单位
②每处理一个数据块,4个整型值A,B,C,D都会更新一次
③重复第2步,处理完所有数据块,最终A,B,C,D的值就文件的MD5值
2.涉及操作
①F,G,H,I四个变换函数
/*
F(B,C,D) = (B & C) | ((~B) & D)
G(B,C,D) = (B & D) | ( C & (~D))
H(B,C,D) = B ^ C ^ D
I(B,C,D) = C ^ (B | (~D))
*/
②加法
③循环左移  
     ege: 1001 1101
左移之后:0011 1011 
/*shift((a + F + k[i] + chunk[g]), s[i])
shift((a + G + k[i] + chunk[g]), s[i])
shift((a + H + k[i] + chunk[g]), s[i])
shift((a + I + k[i] + chunk[g]), s[i])
*/
五、摘要输出
拼接4个buffer(A,B,C,D)中的摘要信息,以A中的低位字节开始,D的高位字节结束。最终的输出是128bit
摘要信息的16进制表示,故最后输出一个32长度的摘要信息。
ege:一个数十六进制表示为:0x12345678,它对应的4个摘要信息
从低字节78开始,以高字节12结束,
即:78563412
按512位数据逐块处理输入信息的详细过程

在这里插入图片描述

代码如下:

//填充操作
void MD5::fill_md5()
{
	//填充冗余信息:第一位填1,其余填0
	//任何情况下,都至少填充一个字节的冗余信息
	//获取第一个要填充的位置
	char *p = chunk + Last_Byte;
	*p++ = 0x80;
	//剩余字节的位置
	int Surplus_Byte = Chunk_Byte - Last_Byte;
	//如果剩余字节不够8字节,不能填充长度信息
	//应该先处理这块数据,然后再去构建一个新的数据块,前440位填0,最后的64位填长度xinx
	if (Surplus_Byte < 8)
	{
		//剩余位全部填0
		memset(p, 0, Surplus_Byte);
		md5((uint*)chunk);
		//构建一个新的chunk
		memset(chunk, 0, Chunk_Byte);
	}
	else
	{
		//剩余位全部补0
		memset(p, 0, Surplus_Byte);
	}
	//最终在最后一块数据的最后64位填充原始信息
	unsigned long long Sum_Bits = Sum_Byte;
	Sum_Bits *= 8;
	((unsigned long long *)chunk)[7] = Sum_Bits;
	md5((uint*)chunk);
}

//计算一个chunk的MD5值
void MD5::md5(uint* chunk)
{
	int a = A;
	int b = B;
	int c = C;
	int d = D;
	int f, g;
	for (int i = 0;i < 63;++i)
	{
		//位运算,F,G,H,I
		/*
			if (0 <= i < 16) g = i;
			if (16 <= i < 32) g = (5 * i + 1) % 16;
			if (32 <= i < 48) g = (3 * i + 5) % 16;
			if(48 <= i < 63) g = (7 * i) % 16;
		*/
		//0~15:F 
		if (0 <= i && i <= 15)
		{
			f = F(b, c, d);
			g = i;
		}
		//16~31:G
		else if (16 <= i && i <= 31)
		{
			f = G(b, c, d);
			g = (5 * i + 1) % 16;
		}
		//32~47:H
		else if (32 <= i && i <= 47)
		{
			f = H(b, c, d);
			g = (3 * i + 5) % 16;
		}
		//48~63:I
		else
		{
			f = I(b, c, d);
			g = (7 * i) % 16;
		}
		//更新a,b,c,d
		int ta = a;
		int tb = b;
		int tc = c;
		int td = d;
		a = tc;
		b = b + LeftShift(a + f + chunk[g] + k[i], Left_Shift[i]);
		c = tb;
		d = tc;
	}
	//更新A,B,C,D
	A += a;
	B += b;
	C += c;
	D += d;
}
三、扫描目录
  • 输入目录
  • 扫描目录
  • 重复文件的结果
  • 管理重复文件
扫描目录

Cpp:读取文件可以采用几个函数分别为,_findfirst、_findnext、_findclose。其中还要借助结构体 struct _finddata_t,_finddata_t主要用来存储各种文件的信息


struct _finddata64i32_t {
        unsigned    attrib;//文件属性
        __time64_t  time_create; //创建时间,_time64_t类型本质是int型
        __time64_t  time_access;   //访问时间
        __time64_t  time_write;//修改时间
        _fsize_t    size;//文件大小,_fsize_t是unsigned long类型,表示文件的字节数。 
        char        name[260];//文件名
}

 unsigned    attrib文件属性有一下几种
_A_ARCH(存档)
_A_HIDDEN(隐藏)
_A_NORMAL(正常)
_A_RDONLY(只读)
_A_SUBDIR(文件夹)
_A_SYSTEM(系统)

long _findfirst( char *filespec, struct _finddata_t *fileinfo );
返回值:
如果查找成功的话,将返回一个long型的唯一的查找用的句柄(就是一个唯一编号)。这个句柄将在_findnext函数中被使用。若失败,则返回-1。
参数:
filespec:标明文件的字符串,可支持通配符。比如:*.c,则表示当前文件夹下的所有后缀为C的文件。
fileinfo :这里就是用来存放文件信息的结构体的指针。这个结构体必须在调用此函数前声明,不过不用初始化,只要分配了内存空间就可以了。
函数成功后,函数会把找到的文件的信息放入这个结构体中。

int _findnext( long handle, struct _finddata_t *fileinfo );
返回值:
若成功返回0,否则返回-1。
参数:
handle:即由_findfirst函数返回回来的句柄。
fileinfo:文件信息结构体的指针。找到文件后,函数将该文件信息放入此结构体中。

int _findclose( long handle );
返回值:成功返回0,失败返回-1。
参数: handle :_findfirst函数返回回来的句柄。

linux: findfirst,findnext,findclose

代码如下:

void search_dir(const std::string& path, std::unordered_set<std::string>& sum_file)
{
	std::string match = path + "\\" + "*.*";
	_finddata_t file_attr;
	long handle = _findfirst(match.c_str(), &file_attr);
	if (handle == -1)
	{
		perror("search failed!!!");
		std::cout << match << std::endl;
		return;
	}
	do
	{
		if (file_attr.attrib & _A_SUBDIR)
		{
			if (strcmp(file_attr.name, ".") != 0 && strcmp(file_attr.name, "..") != 0)
			{
				//当前为目录,继续搜索
				search_dir(path + "\\" + file_attr.name, sum_file);
			}
		}
		else
		{
			//当前为文件,则保存
			sum_file.insert(path + "\\" + file_attr.name);
		}
	} while (_findnext(handle, &file_attr) == 0);
	_findclose(handle);
}


void delete_file(const char* filename)
{
	if (remove(filename) == 0)
	{
		std::cout << "delete file:" << filename << "success!!!" << std::endl;
	}
	else
	{
		perror("delete file failed!!!");
	}
}
四、处理文件
  • 读入文件
  • 计算文件
  • 删除文件
  • 打印文件
//1.读入文件
void file_manager::scanner_dir(const std::string& path)
{
	//清理容器
	_file.clear();
	search_dir(path, _file);
	//std::cout << "all file" << std::endl;
	show_all_file();
	get_md5_to_file();
	show_copy_list();

	std::cout << "Only duplicate files" << std::endl;
	get_copy_list();
	//std::cout << "copy list" << std::endl;
	show_copy_list();
	show_all_file();
}
//2.计算文件
//2.1.根据文件的MD5值得到相应的文件
void file_manager::get_md5_to_file()
{
	_md5_to_file.clear();
	for (const auto &a : _file)
	{
		//计算新文件前重置
		_md5.reset();
		//string 转换成 char *
		//如果要将string直接转换成const char *类型。string有2个函数可以运用。
		//一个是.c_str(),一个是data成员函数。
		_md5_to_file.insert(make_pair(_md5.get_File_md5(a.c_str()), a));

	}
}
//2.2.得到重复文件的数量
void file_manager::get_copy_list()
{
	_file_to_md5.clear();
	//不要用范围for,涉及删除操作
	auto it = _md5_to_file.begin();
	while (it != _md5_to_file.end())
	{
		//查找每个MD5对应所有文件的结果
		if (_md5_to_file.count(it->first) > 1)
		{
			//equal_range 返回值:pair<beginIt,endIt>:[beginIt,endIt)--->迭代器遍历时连续
			auto pair_it = _md5_to_file.equal_range(it->first);
			auto begin = pair_it.first;
			while (begin != pair_it.second)
			{
				//只存放重复文件的映射关系
				_file_to_md5.insert(make_pair(begin->second, begin->first));
				++begin;
			}
			//下一个不同的MD5的起始位置
			it = pair_it.second;
		}
		else
		{
			//先删除,再更新
			_file.erase(it->second);
			//erase的返回值,被删除元素的下一个位置
			it = _md5_to_file.erase(it);
		}
	}
}
//3.删除文件
//3.1.根据文件名字删除
void file_manager::delete_by_name(const std::string& name)
{
	if (_file_to_md5.count(name) == 0)
	{
		std::cout << name << "\tnot exist!!!" << std::endl;
		return;
	}
	std::string cur_md5 = _file_to_md5[name];
	std::cout << name << "--->" << _md5_to_file.count(cur_md5) << std::endl;
	auto pair_it = _md5_to_file.equal_range(cur_md5);
	auto cur_it = pair_it.first;
	int count = 0;
	while (cur_it != pair_it.second)
	{
		if (cur_it->second != name)
		{
			_file.erase(cur_it->second);
			_file_to_md5.erase(cur_it->second);
			delete_file(cur_it->second.c_str());
			++count;
		}
		++cur_it;
	}
	cur_it = pair_it.first;
	while (cur_it != pair_it.second)
	{
		if (cur_it->second != name)
		{
			//key==md5
			_md5_to_file.erase(cur_it);
			pair_it = _md5_to_file.equal_range(cur_md5);
			cur_it = pair_it.first;
		}
		++cur_it;
	}
	std::cout << "delete files :" << count << std::endl;
}
//3.2.根据文件的MD5值删除
void file_manager::delete_by_md5(const std::string& md5)
{
	//md5映射file
	if (_md5_to_file.count(md5) == 0)
	{
		std::cout << md5 << "not exist!!!" << std::endl;
		return;
	}
	//删除时需要保留一份,规定保留第一个文件
	auto pair_it = _md5_to_file.equal_range(md5);
	std::cout << "md5值:" << md5 << "---have---" << _md5_to_file.count(md5) << std::endl;
	auto cur_it = pair_it.first;
	++cur_it;
	int count = 0;
	while (cur_it != pair_it.second)
	{
		_file.erase(cur_it->second);
		_file_to_md5.erase(cur_it->second);
		//文件从磁盘中删除
		delete_file(cur_it->second.c_str());
		++count;
		++cur_it;
	}
	//更新一下MD5到file的映射
	cur_it = pair_it.first;
	++cur_it;
	//erase(first,last)----> 删除区间[first,last)
	_md5_to_file.erase(cur_it, pair_it.second);
	std::cout << "delete files number:" << count << std::endl;
}
//3.3.根据文件包含的 'match' 删除
void file_manager::delete_by_match(const std::string& match)
{
	std::unordered_set<std::string> all_file;
	//遍历所有文件
	for (const auto& a : _file)
	{
		if (a.find(match) != std::string::npos)
			all_file.insert(a);
	}
	for (const auto & b : all_file)
	{
		if (_file_to_md5.count(b) != 0)
			delete_by_name(b);
	}
}
//3.4.删除所有重复文件
void file_manager::delete_all_copy()
{
	std::unordered_set<std::string> md5_set;
	//找出所有的md5值
	for (const auto& a : _md5_to_file)
	{
		md5_set.insert(a.first);
	}
	for (const auto& b : md5_set)
	{
		delete_by_md5(b);
	}
}
//4.打印文件
//4.1.打印重复文件
void file_manager::show_copy_list()
{
	std::cout << "+++++++++++++++++++++++" << std::endl;
	std::cout << "show_copy_list" << std::endl;
	auto it = _md5_to_file.begin();
	int total = _md5_to_file.size();
	int count = 0;
	while (it != _md5_to_file.end())
	{
		//md5值相同的显示在一起
		int idx = 1;
		auto pair_it = _md5_to_file.equal_range(it->first);
		auto cur_it = pair_it.first;
		std::cout << "cut md5:" << it->first << std::endl;
		while (cur_it != pair_it.second)
		{
			std::cout << "\t第" << idx << "文件" << "\t";
			std::cout << cur_it->second << std::endl;
			++count;
			++cur_it;
			++idx;
		}
		it = pair_it.second;
	}
	std::cout << "文件总数:" << total << "\t" << count << std::endl;
	std::cout << "+++++++++++++++++++++++" << std::endl;
}
//4.2.打印所有文件
void file_manager::show_all_file()
{
	std::cout << "=======================" << std::endl;
	std::cout << "show_all_file" << std::endl;
	for (const auto& a : _file)
	{
		std::cout << a << std::endl;
	}
	std::cout << "file count:" << _file.size() << std::endl;
	std::cout << "=======================" << std::endl;
}

五、测试及测试类
  • 测试类
  • 测试

代码如下:

//1.测试类及其实现
class file_manager_tool
{
public:
	void scanner();
	void delete_by_md5();
	void delete_by_name();
	void delete_all_copy();
	void delete_by_match();
	void show_all_file();
	void show_copy();
private:
	file_manager _fm;
};
void file_manager_tool::scanner()
{
	cout << "请输入要扫描的文件夹:" << endl;
	std::string path;
	getline(cin, path);
	_fm.scanner_dir(path);
}
void file_manager_tool::delete_by_md5()
{
	cout << "请输入要删除的文件夹的MD5值:" << endl;
	std::string md5;
	cin >> md5;
	_fm.delete_by_md5(md5);
}
void file_manager_tool::delete_by_name()
{
	cout << "请输入要删除的文件名:" << endl;
	std::string file_name;
	getline(cin, file_name);
	_fm.delete_by_name(file_name);
}
void file_manager_tool::delete_all_copy()
{
	_fm.delete_all_copy();
}
void file_manager_tool::delete_by_match()
{
	cout << "请输入要删除文件所包含的内容:" << endl;
	std::string md5;
	cin >> md5;
	_fm.delete_by_match(md5);
}
void file_manager_tool::show_all_file()
{
	_fm.show_all_file();
}
void file_manager_tool::show_copy()
{
	_fm.show_copy_list();
}

//2.测试
void menu()
{
	cout << "***********************************************" << endl;
	cout << "1:scanner			2:show all		3:show copy" << endl;
	cout << "4:delete name		5:delete md5	6:delete all" << endl;
	cout << "7:delete matched	0:exit" << endl;
	cout << "***********************************************" << endl;
}
void test_tool()
{
	file_manager_tool fmt;
	int input;
	do
	{
		menu();
		std::string rubbish;
		cout << "请输入选项:";
		cin >> input;
		getline(cin, rubbish);
		switch (input)
		{
		case 1:
			fmt.scanner();
			break;
		case 2:
			fmt.show_all_file();
			break;
		case 3:
			fmt.show_copy();
			break;
		case 4:
			fmt.delete_by_name();
			break;
		case 5:
			fmt.delete_by_md5();
			break;
		case 6:
			fmt.delete_all_copy();
			break;
		case 7:
			fmt.delete_by_match();
			break;
		case 0:
			break;
		default:
			break;
		}
	} while (input != 0);
}

具体代码详见

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值