64位Python处理8MB字节流执行replace为何抛出MemoryError?
问题背景
处理大小仅8MB的mitmproxy流量(内容为bytes类型),需求为:
- 导出流量中内嵌的所有JPG图像到本地
- 将流量内的原有JPG替换为单张大小不足100KB的自定义图像
代码运行时在替换步骤触发MemoryError,理论上内存资源完全充足,无法定位报错原因。
问题复现代码
startList = list(re.finditer(b'\xff\xd8',flowContent)) x = 1 for a in startList: end = flowContent.find(b'\xff\xd9',a.start()) fileContent = flowContent[a.start():end] fileName = 'image'+str(x)+".jpg" dumpfile = open('dump/'+fileName,'wb') dumpfile.write(fileContent) dumpfile.close() replace = open('replace/replace'+str(x)+'.jpg','rb') myImage = Image(replace) replace.close() nowTime = datetime.now() myImage.datetime = nowTime.strftime(DATETIME_STR_FORMAT) myImage.datetime_digitized = nowTime.strftime(DATETIME_STR_FORMAT) myImage.datetime_original = nowTime.strftime(DATETIME_STR_FORMAT) newImage = open('replace/replace'+str(x)+'U.jpg',"wb") newImage.write(myImage.get_file()) newImage.close() replaceF = open('replace/replace'+str(x)+'U.jpg','rb') replaceContent = replaceF.read() replaceF.close() flowContent = flowContent.replace(fileContent,replaceContent) #flowContent = re.sub(fileContent,myImage.get_file(),flowContent) x = x+1
报错信息
Traceback (most recent call last): File "E:\SpecialK\flow.py", line 41, in <module> flowContent = flowContent.replace(fileContent,replaceContent) MemoryError
根因分析
- JPG提取逻辑错误,导致匹配片段过短
JPG的结束标记b'\xff\xd9'占2个字节,代码中切片只切到end(即\xff的位置),漏了后面的\xd9字节,提取出的fileContent是残缺的JPG片段,甚至可能是长度极短的常见字节序列。 - 全局replace引发爆炸式内存占用
bytes.replace()会对整个bytes对象做全量扫描,把所有匹配到目标片段的位置全部替换。如果提取的残缺片段是高频短字节串,8MB的流量中可能存在数十万甚至上百万个匹配点,替换过程中生成的中间bytes对象体积会指数级膨胀,直接撑爆内存。 - 偏移量提前缓存导致逻辑失效
代码一开始就把所有JPG起始位置缓存到startList中,但循环过程中一直在修改flowContent的长度和内容,提前缓存的偏移量在第一次替换后就已经和实际内容位置不匹配,后续处理全是无效逻辑。
修复方案
- 修正JPG切片逻辑,找到结束标记位置后偏移2个字节,保证提取的是完整JPG文件,避免短片段误匹配。
- 放弃全局replace方法,按照图片的实际起止位置直接拼接内容:图片前的流量片段 + 替换图片内容 + 图片后的流量片段,从根源上避免全局匹配带来的内存膨胀问题。
- 不要提前缓存所有JPG位置,处理完一张图片后,从当前图片结束位置之后继续查找下一个JPG起始标记,保证偏移量始终有效。
修复后的核心逻辑参考:
x = 1 current_pos = 0 flow_len = len(flowContent) new_flow = b'' while current_pos < flow_len: # 从当前位置找下一个JPG起始标记 start = flowContent.find(b'\xff\xd8', current_pos) if start == -1: # 没有更多图片,拼接剩余内容后退出 new_flow += flowContent[current_pos:] break # 先拼接图片前的非图片内容 new_flow += flowContent[current_pos:start] # 找当前JPG的结束标记,偏移2字节包含完整结束符 end = flowContent.find(b'\xff\xd9', start) if end == -1: # 找不到结束标记说明是异常片段,直接拼接剩余内容退出 new_flow += flowContent[start:] break end += 2 # 提取完整原图片 fileContent = flowContent[start:end] # 导出原图片、修改自定义图片EXIF的逻辑和原有逻辑一致 replace_path = f'replace/replace{x}U.jpg' with open(replace_path, 'rb') as f: replaceContent = f.read() # 拼接替换后的图片内容 new_flow += replaceContent # 更新当前位置到当前图片结束之后 current_pos = end x += 1 flowContent = new_flow
内容的提问来源于stack exchange,提问作者Martijn Deleij
相关产品推荐
相关产品推荐

