-
Notifications
You must be signed in to change notification settings - Fork 0
/
compress.py
258 lines (226 loc) · 9.75 KB
/
compress.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
sys.setrecursionlimit(1000000) #python默认的递归深度有限,约900多,压缩文件时会超过,故引用sys修改最大递归深度
# from app import data #这里的data是全局变量,为了提交到正确的位置
# 代码中出现的列表及字典的详细解释
# bytes_list: 存放读入的字节
# count_dict: key 不重复的字节 val 出现的次数
# node_dict: key 不重复的字节 val 对应的结点
# bytes_dict: key 不重复的字节 val 对应的编码
# nodes : 保存结点并构建树,构建完后只剩下根结点
class datatmp:
decodeFile="" #解压之后的文件名
datatemp=datatmp()
#哈夫曼树结点的类定义
class node(object):
def __init__(self,value=None,left=None,right=None,father=None):
self.value=value
self.left=left
self.right=right
self.father=father #分别定义左右结点,父结点,和结点的权重
#构建哈夫曼树
def creat_tree(nodes_list):
nodes_list.sort(key=lambda x: x.value) #将结点列表进行升序排序
if len(nodes_list)==1:
return nodes_list[0] #只有一个结点时,返回根结点
father_node=node(nodes_list[0].value+nodes_list[1].value,nodes_list[0],nodes_list[1]) #创建最小的两个权重结点的父节点
nodes_list[0].father=nodes_list[1].father=father_node
nodes_list.pop(0)
nodes_list.pop(0)
nodes_list.insert(0,father_node) #删除最小的两个结点并加入父结点
return creat_tree(nodes_list)
def node_encode(node1): #对叶子结点进行编码
if node1.father==None:
return b''
if node1.father.left==node1:
return node_encode(node1.father)+b'0'
else:
return node_encode(node1.father)+b'1'
def file_encode(input_file):
print('打开文件并读取中...\n')
with open(input_file,'rb') as f:
f.seek(0, 2) #读取文件的总长度,seek(0,2)移到文件末尾,tell()指出当前位置,并且用seek(0)重新回到起点
size=f.tell()
f.seek(0)
bytes_list=[0]*size #创建一个长度为size的列表,存放读入的字节
i=0
while i<size:
bytes_list[i]=f.read(1) #每次读取一个符号
i+=1
print('统计各字节出现频率中...\n')
count_dict = {} # 使用一个字典统计一下出现次数
for x in bytes_list:
if x not in count_dict.keys():
count_dict[x] = 0
count_dict[x] += 1
node_dict={} #使用一个字典将count_list中的值node化,其中key为对应的字符,值为字符对应的结点
for x in count_dict.keys():
node_dict[x]=node(count_dict[x]) #结点的权重即count_dict[x]
print('生成哈夫曼编码中...\n')
nodes=[] #使用一个列表来保存结点并由此构建哈夫曼树
for x in node_dict.keys():
nodes.append(node_dict[x])
root=creat_tree(nodes) #构建哈夫曼树
#对叶子结点编码,输出字节与哈夫曼码的字典
bytes_dict={}
for x in node_dict.keys():
bytes_dict[x]=node_encode(node_dict[x])
# print(bytes_dict)
#开始压缩文件
print('开始压缩文件...\n')
path_list=input_file.split('.')
name=input_file.split('/')[-1]
print (path_list)
with open(path_list[0]+'.ys','wb') as object:
#首先将文件的原名写入
object.write((name+'\n').encode(encoding='UTF-8'))
#写入结点数量,占位2个字节
n=len(count_dict.keys())
object.write(int.to_bytes(n ,2 ,byteorder = 'big'))
#先计算最大频率所占的字节数
times=0
for x in count_dict.keys():
if times<count_dict[x]:
times=count_dict[x]
width=1
if times>255:
width=2
if times>65535:
width=3
if times>16777215:
width=4
# 写入width
object.write(int.to_bytes(width,1,byteorder='big'))
# 写入结点以及对应频率
for x in count_dict.keys():
object.write(x)
object.write(int.to_bytes(count_dict[x], width, byteorder='big'))
#写入数据,注意每次要凑一个字节
code=b'' #用来存放编译出来的代码
for x in bytes_list:
code+=bytes_dict[x]
out=0
while len(code)>=8:
for s in range(8):
out = out << 1
if code[s] == 49: #ASCII码中1为49
out = out | 1
object.write(int.to_bytes(out,1,byteorder='big'))
out=0
code=code[8:]
#处理可能不足一个字节的数据
object.write(int.to_bytes(len(code), 1, byteorder='big')) #写入最后一节数据长度
print(len(code))
print(code)
out=0
for i in range(len(code)):
out = out << 1
if code[i] == 49:
out = out | 1
object.write(int.to_bytes(out,1,byteorder='big'))
print('压缩完成!')
#文件的解压缩
#解压缩前,重温下压缩文件.cc的内容:第一行为原文件名
#第二行的开始两个字节纪录结点数量n,然后一个字节纪录频率的位宽width,后面纪录每个字节与其频率
#之后全部是数据内容
def file_decode(input_file):
print('开始解压缩文件')
with open(input_file,'rb') as f_in:
f_in.seek(0,2)
length=f_in.tell() #读出文件的总长度
f_in.seek(0)
path_list = input_file.split('.')
name = f_in.readline().decode(encoding="UTF-8").split('/')[-1].replace('\n','')
name = name.split('.')[-1] #读出文件名
datatemp.decodeFile=name #得到文件名
with open(path_list[0]+'.'+name,'wb') as f_out:
n=int.from_bytes(f_in.read(2), byteorder = 'big') #读出结点数量
width=int.from_bytes(f_in.read(1), byteorder = 'big') #读出位宽
count_dict={}
i=0
while i<n:
dict_key=f_in.read(1)
dict_value=int.from_bytes(f_in.read(width),byteorder='big')
count_dict[dict_key]=dict_value
i+=1
print('生成反向字典中...')
#以下过程与编码时的构建过程相同
node_dict = {} # 使用一个字典将count_list中的值node化,其中key为对应的字符,值为字符对应的结点
for x in count_dict.keys():
node_dict[x] = node(count_dict[x]) # 结点的权重即count_dict[x]
nodes = [] # 使用一个列表来保存结点并由此构建哈夫曼树
for x in node_dict.keys():
nodes.append(node_dict[x])
root = creat_tree(nodes) # 构建哈夫曼树
# 对叶子结点编码,输出字节与哈夫曼码的字典
bytes_dict = {}
for x in node_dict.keys():
bytes_dict[x] = node_encode(node_dict[x])
# print(bytes_dict)
# 生成反向字典,key为编码,value为对应的字节
diff_dict={}
for x in bytes_dict.keys():
diff_dict[bytes_dict[x]]=x
print('解码中...')
# 解码时不停读取单个数字,遍历二叉树,直到找到叶子结点
out=b''
i=f_in.tell()
node_now = root
result = b''
while i < length-2:
i+=1
temp=int.from_bytes(f_in.read(1),byteorder='big')
for mm in range(8): #将数据转换成b'01'形式
if temp&1 == 1:
out=b'1'+out
else:
out=b'0'+out
temp=temp>>1
while out: #遍历哈夫曼树
if out[0]==49:
node_now=node_now.right
result = result+b'1'
if out[0]==48:
node_now=node_now.left
result = result+b'0'
out=out[1:]
if node_now.left==None and node_now.right==None:
f_out.write(diff_dict[result])
result=b''
node_now=root
# 处理最后可能不满8位的数据
last_length = int.from_bytes(f_in.read(1), byteorder='big')
print(last_length)
temp= int.from_bytes(f_in.read(1), byteorder='big')
print(temp)
for mm in range(last_length): # 将数据转换成b'01'形式
if temp & 1 == 1:
out = b'1' + out
else:
out = b'0' + out
temp = temp >> 1
print(out)
while out: # 遍历哈夫曼树
if out[0] == 49:
node_now = node_now.right
result = result + b'1'
if out[0] == 48:
node_now = node_now.left
result = result + b'0'
out = out[1:]
if node_now.left == None and node_now.right == None:
f_out.write(diff_dict[result])
print(result)
result = b''
node_now = root
print('解压成功!')
# 本体调用本函数时运行的内容
if __name__ == '__main__':
de=int(input('请输入您需要进行的操作(1为压缩,2为解压):'))
if de==1:
in_file=input('请输入您需要压缩的文件路径:')
file_encode(in_file)
if de==2:
in_file=input('请输入您需要解压的文件路径:')
file_decode(in_file)