当前位置: 首页 > news >正文

大模型手搓文件对比工具(7)乱码解决了,换行也不乱

上一期把差异块操作做完后,开发计划进入 P1:编码与换行保真。

这个功能看上去没有差异算法那么直观。文件能打开,文字能修改,点击保存也没有报错,很容易让人觉得编辑器已经够用了。

但旧版一直有个隐患:所有文本都按 UTF-8 读取,再按 UTF-8 写回。

遇到普通 UTF-8 文件当然没问题。遇到 GBK、GB18030 或 UTF-16,轻一点是打开后一片乱码,重一点是用户没看出来编码已经变了,保存后把原文件直接改坏。换行也一样,Windows 项目里的 CRLF 可能被统一成 LF,文件内容没变,Git 却显示整份文件都被修改。

所以这一期不加新按钮,先把文件读写这条链路补完整:打开时识别编码,判断不准就让人确认;保存时保持左右两侧各自的编码、BOM 和换行,不做任何静默转换。

先拿几个真实文件试一下

正式改代码前,我准备了几类测试文件:

  • UTF-8 和 UTF-8 BOM。
  • UTF-16 LE、UTF-16 BE,包含带 BOM 和无 BOM 两种情况。
  • GB18030、GBK、GB2312 中文文件。
  • 左侧 CRLF、右侧 LF。
  • 同一个文件里同时存在 CRLF、LF 和 CR。
  • 最后一行有换行和没有换行。

旧版的问题马上就暴露出来了。

UTF-16 文件里经常带有0x00字节,而旧版二进制判断只要看到 NUL 就认为这不是文本。结果一个正常的 UTF-16 配置文件,还没有进入编码识别,就先被挡在编辑器外面。

另外,GB18030 和 GBK 并没有像 UTF-8 BOM 那样明确的文件头。短文本可能同时满足多种编码的解码规则,程序即使猜出一个结果,也不能把“猜测”包装成“确定”。

这两个问题决定了编码功能不能只加一句:

Charset.forName(detectedName)

识别顺序、可信程度、用户确认和保存策略都要一起设计。

能确定的自动打开,不能确定的先看内容

这次把识别结果分成了几种情况。

1. BOM 可以直接确认

文件开头如果出现下面这些字节,编码基本没有歧义:

字节头编码
EF BB BFUTF-8 BOM
FF FEUTF-16 LE BOM
FE FFUTF-16 BE BOM

BOM 不交给正文和差异算法,但会单独保存下来。以后写文件时再按原样加回去。

2. UTF-8 必须严格校验

Java 默认解码遇到非法字节时,可能使用替换字符继续执行。编辑器里看到的黑色菱形或问号,很多时候就是这样来的。

这次改成CharsetDecoder的严格模式:

decoder.onMalformedInput(CodingErrorAction.REPORT);decoder.onUnmappableCharacter(CodingErrorAction.REPORT);

整份文件可以严格解码,并且内容看起来像正常文本,才把它作为可靠 UTF-8 自动打开。

3. UTF-16 要排在二进制判断前面

无 BOM 的 UTF-16 不能直接确认,但英文和数字文本通常会在奇数位或偶数位出现规律性的0x00。程序先检查这种字节分布,再使用 UTF-16 LE 或 BE 严格解码验证。

只有 BOM、UTF-16 候选和文本编码候选都失败,并且控制字节比例明显异常时,才提示文件可能是二进制。

这样正常 UTF-16 文件不会再因为 NUL 字节被提前拒绝。

4. 传统中文编码只给建议

GB18030、GBK、GB2312 和 Big5 使用juniversalchardet 2.4.0辅助判断。这个库负责提供候选,程序再逐个执行严格解码和文本合理性检查。

统计检测的结果仍然只是建议。特别是文件很短时,GBK 和 GB18030 都可能解出看似正常的中文。对这类结果,编辑器不会直接放行,而是打开预览窗口。

左边选择编码,右边实时显示内容。看起来正常再点确认;某个候选无法完整解码时,直接显示错误,不拿替换字符凑出一份“差不多能看”的预览。

“按另一种编码打开”和“转换编码”必须分开

做 UI 方案时,我特意把两个容易混淆的操作拆开了。

重新按其他编码打开,处理的是“刚才读错了”。程序重新读取磁盘上的原始字节,按用户选择的编码解释,不写文件。如果当前有未保存修改,先提示放弃修改。

转换并保存编码,处理的是“我就是要改文件格式”。程序拿当前已经正确显示的文字,使用目标编码重新写入文件。操作前显示当前编码、目标编码、换行状态和文件路径,还要再确认一次。

这两个操作如果只做成一个编码下拉框,使用者很难知道点完以后只是重新读取,还是已经把磁盘文件改了。对文件工具来说,这种含糊比多一个菜单更危险。

最终编码名称放在左右文件标题里。点击左侧编码,只操作左侧;点击右侧编码,只操作右侧。左右文件可以一个是 GB18030,另一个是 UTF-8 BOM,保存后仍然各用各的格式。

换行不能只记一个全局值

编码解决以后,还有换行。

最简单的做法是读取文本时判断它主要使用 LF 还是 CRLF,然后保存时把所有行统一成这个格式。普通文件看起来没问题,但遇到混合换行就会改变大量字节。

这次继续改造原来的LineDocument,让每一行都记录自己的结尾:

第 1 行:"name=compare" + CRLF 第 2 行:"status=running" + LF 第 3 行:"message=完成" + NONE

NONE表示最后一行没有换行。它不是 LF,也不是一个可以随手补上的空字符。

修改一行文字时,只替换文字,不动该行原来的结尾。新插入的行使用这个文件最常见的换行方式。保存时把“行内容 + 原结尾”逐行拼回去,因此 CRLF、LF、单独 CR、混合换行和末尾无换行都能保留。

标题旁边会显示CRLFLFCR或“混合换行”。鼠标放上去还能看到每种换行的数量,以及文件末尾是否有换行。

差异块复制时,到底听谁的格式

这一期和上一期的差异块操作有直接关系。

假设左侧文件是 GB18030 + CRLF,右侧文件是 UTF-8 BOM + LF。现在把左侧一处差异复制到右侧,右侧应该变成什么格式?

最终规则是:

  • 复制的是文字内容,不复制已有目标文件的编码。
  • 目标文件已经存在时,继续使用目标侧编码、BOM 和首选换行。
  • 目标侧原有行能复用的换行继续保留。
  • 新增到目标侧的行使用目标侧首选换行。
  • 目标文件原本不存在时,才继承来源文件的编码、BOM 和换行。

也就是说,把 GB18030 左侧的一段中文复制到 UTF-8 BOM 右侧,右侧仍然是 UTF-8 BOM。用户想把右侧也转成 GB18030,需要单独执行“转换并保存编码”。

这条边界必须明确,否则每次点击差异箭头都有可能顺便改变整个文件格式。

保存时不能用问号糊弄过去

读取需要严格解码,保存也一样。

例如在 UTF-8 文件里输入一个 Emoji,再选择转换为 GBK。GBK 无法表示这个字符。如果直接调用常规编码方法,某些写法会用?替代,然后照常保存。用户直到下次打开文件才发现内容已经丢了。

现在所有保存都先在内存中使用CharsetEncoder完整编码,并把不可映射字符设置为REPORT。只要有一个字符无法表示,整次保存就停止,磁盘文件不动。

“全部保存”还会先检查左右两侧。任意一侧编码失败,两侧都不写,避免左边保存成功、右边失败后留下半套状态。

普通保存的顺序变成了:

  1. 按每行记录的结尾重建完整文本。
  2. 使用当前侧原编码严格编码。
  3. 编码全部成功后加回原 BOM。
  4. 写入磁盘。
  5. 更新已保存快照和界面状态。

写文件之前多做一次完整检查,成本不高,但能避免无法恢复的字符丢失。

实现时没有照着方案机械加类

方案阶段列过TextFileDocumentLineEndingParserEncodingConversionService等对象。真正开始改代码后,没有把每个名字都变成一个新文件。

当前项目规模不大,原来的LineDocument已经负责行内容和差异块修改。直接把逐行换行信息扩展进去,比再加一层包装更清楚。

最终主要职责是:

  • TextEncodingDetector:BOM、UTF-8、UTF-16 和传统中文编码候选判断。
  • FileEncoding:记录字符集、BOM、可信级别和识别来源。
  • TextFileCodec:严格解码、预览、编码和写入。
  • TextFileSnapshot:保存某一侧的原始字节、编码和文档状态。
  • LineDocument:保存每行文字、每行换行和首选换行。
  • DiffEditorFrame:负责确认窗口、编码菜单、转换确认和保存交互。

方案的作用是先划清职责,不是强迫代码最后一定有多少个类。能在现有模型里讲清楚的逻辑,没有必要为了结构图再套一层。

最终运行效果

下面这张真实截图里,左侧是 UTF-16 LE BOM + CRLF,右侧是 UTF-8 BOM + 混合换行。

两边编码和换行状态分别显示,差异块按钮、占位行、保存和重新加载仍然正常。普通编辑不会把左侧转成 UTF-8,也不会把右侧的混合换行统一掉。

传统中文编码也做了单独验证。下面是左侧 GB18030、右侧 UTF-8 的真实运行结果:

GB18030 文件第一次打开时先经过“推测编码、内容预览、用户确认”,确认后中文能够正常进入编辑器。保存后重新读取,文字、编码和 CRLF 都保持不变。

真实窗口检查还发现了一个字体问题。代码区使用等宽字体时,中文在部分 Windows 环境中会显示成方框。最后把代码字体改为 Java 的逻辑等宽字体,让系统负责回退中文字库;占位提示继续使用微软雅黑。这个问题单元测试检查不到,只能启动 Swing 看实际效果。

这次实际修改了哪些内容

1. 编码识别

  • 支持 UTF-8、UTF-8 BOM。
  • 支持 UTF-16 LE/BE,包含 BOM 和明显的无 BOM 文件。
  • 支持 GB18030、GBK、GB2312 候选。
  • Big5 可作为检测候选和手动选择。
  • UTF-16 判断提前到普通二进制判断之前。
  • 传统编码低可信时必须先预览确认。

2. 文件格式保真

  • 左右文件分别记录编码和 BOM。
  • 每一行单独保存 LF、CRLF、CR 或无换行。
  • 保留混合换行和文件末尾换行状态。
  • 差异块复制保留已有目标文件格式。
  • 目标文件不存在时继承来源格式。

3. 编辑器交互

  • 左右标题分别显示编码和换行状态。
  • 支持重新按其他编码读取原始字节。
  • 支持明确转换编码并保存。
  • 未保存状态下重新读取会先确认。
  • 目标编码无法表示当前字符时阻止保存。

4. 构建和依赖

  • 增加juniversalchardet 2.4.0
  • 更新运行类路径和第三方依赖说明。
  • 继续兼容 Java 8。

5. 测试

  • BOM 和无 BOM 编码识别。
  • UTF-16 NUL 字节判断。
  • GB18030 中文预览与保存。
  • UTF-8 BOM、UTF-16、GB18030 字节往返。
  • GBK 无法表示字符时拒绝保存。
  • LF、CRLF、CR、混合换行和末尾无换行。
  • 差异块复制后的目标侧换行规则。
  • 原有差异算法、差异块应用、行对齐、过滤规则全部回归。
  • 真实 Swing 窗口和中文字体检查。

最终一共跑了 7 组回归测试,全部通过。测试不只比较 Java 字符串,还会重新读取写出的字节,确认 BOM、中文内容和换行没有在保存后变化。

最终版提示词

如果需要给一个现有 Java Swing 文本工具补上同类能力,可以直接使用下面这份提示词:

请继续开发一个现有的 Java 8 Swing 文件对比工具。工具已经支持文件和目录 SHA-256 对比、可折叠目录树、过滤规则、左右同步、F5 刷新、差异块级 双向操作、撤销、重新加载和分别保存。 本次实现 P1“编码与换行保真”。目标是让左右文件可以使用不同编码和换行, 普通编辑、差异块复制与保存都不能静默改变文件格式或丢失字符。 功能要求: 1. 支持 UTF-8、UTF-8 BOM、UTF-16 LE/BE、GB18030、GBK、GB2312, Big5 作为检测候选和手动选择。 2. 编码识别按 BOM、严格 UTF-8、UTF-16 字节分布、统计检测的顺序执行。 3. UTF-16 识别必须早于普通 NUL 二进制判断,避免把 UTF-16 文本误判为二进制。 4. 统计检测只能提供候选。传统中文编码或低可信结果必须显示内容预览, 由用户确认后才能进入可编辑状态。 5. 所有解码使用 CharsetDecoder 严格模式,非法输入和不可映射字符必须报错, 不能静默替换。 6. 左右两侧分别保存 Charset、BOM、识别来源、可信状态和原始字节, 不能使用一个全局编码覆盖两侧。 7. 每一行分别记录 LF、CRLF、CR 或无换行,支持混合换行和末尾无换行保真。 8. 修改已有行不能改变原换行;新增行使用目标文件首选换行。 9. 差异块复制默认只复制文字。目标文件存在时保留目标编码、BOM 和换行, 目标文件不存在时才继承来源文件格式。 10. 在左右文件标题中分别显示编码和换行状态,混合换行要有明显状态和统计提示。 11. 严格区分“重新按其他编码打开”和“转换并保存编码”:前者重新解释 磁盘原始字节且不写文件,后者转换当前文本并明确写盘。 12. 当前有未保存修改时,重新按编码打开必须先确认放弃修改。 13. 编码转换前显示当前编码、目标编码、BOM、换行状态、操作侧和文件路径, 并明确另一侧不会改变。 14. 所有保存使用 CharsetEncoder 严格模式并先在内存完成编码。 目标编码无法表示任何字符时阻止写入,不能替换成问号。 15. 全部保存必须先校验左右两侧,任意一侧编码失败时两侧均不写入。 16. 保留现有差异块操作、撤销、占位行、联动滚动、重新加载和未保存提示。 17. 编码检测、编解码、文件格式元数据和 Swing 交互要分开,不把检测逻辑 直接堆进窗口事件代码。 18. 引入第三方检测库前检查 Java 8 兼容性、许可证、运行时依赖和离线分发方式, 并更新启动脚本及第三方声明。 19. 增加回归测试,至少覆盖 UTF-8 BOM、UTF-16 LE/BE、GB18030、GBK、 纯 ASCII、空文件、非法 UTF-8、NUL、不可映射字符、混合换行和末尾无换行。 20. 增加字节级往返测试:文件不修改直接保存后,编码、BOM、文字和换行保持一致。 21. 完成后编译全部源码和测试,运行真实 Swing 窗口,使用中文 GB18030、 UTF-16 BOM 和混合换行文件检查字体、预览、保存和左右独立状态。 修改前先阅读现有代码和开发计划,给出执行计划、数据模型、识别顺序、 保存语义和 UI 效果图。我确认后再修改正式代码。实现完成后更新 README、 开发计划、第三方依赖说明,并生成真实运行截图。

下一步

按照开发计划,下一项是 P1“同步预览、备份和失败恢复”。

目前目录同步可以把不同文件复制到另一侧,但点击以后就直接执行。真正放到项目目录里使用,还需要在写盘前列出新增、覆盖和跳过的文件,让人确认方向和目标根目录;重要文件覆盖前可以选择备份,某一项失败后也要知道哪些已经完成、哪些没有执行。

编码保真解决的是“保存时别把文件格式改坏”,同步安全解决的是“批量覆盖前先让我看清楚”。这两项补齐后,工具才更适合处理真实项目目录。

计划里后面还有扫描进度与取消、对比历史缓存、过滤规则预设和发布打包。顺序已经写进开发计划,不再临时看到一个小问题就改变主线。

最后

这一期没有增加一个特别显眼的大功能,但它处理的是文件工具最不能含糊的地方。

乱码还能在打开时发现,静默改编码、统一换行或用问号替换字符,往往要到提交代码、部署配置甚至下次打开文件时才会暴露。到那时,原来的字节和格式可能已经找不回来了。

现在左右两侧可以各用各的编码和换行,程序判断不准时会停下来让人确认,保存前也会先检查是否能完整编码。它还不支持所有地区编码,检测短文本也不可能百分之百准确,但至少不会装作自己一定猜对了。

目前整体使用体验仍然有不少需要优化的地方。同步缺少预览和备份,大目录没有进度与取消,常用对比任务也还不能直接从历史记录恢复。后面继续按开发计划逐项补,不急着把功能清单写得很长,先把每一次文件读写做稳。

http://www.jsqmd.com/news/1318304/

相关文章:

  • 义乌秋裤专业定制商口碑推荐,价格透明不踩坑 - 工业品牌热点
  • L2正则化,防止过拟合-历史补充
  • 单人基于在线工具闭环实现AI漫剧全流程制作
  • Unity游戏迁移微信小游戏:7大实战技巧攻克性能与适配难题
  • 位运算实现字符唯一性检测的高效算法
  • 2026 年当下,珠晖专业的DBJ连续缠绕管供应厂家哪家强,小区地下管网用它,30年不堵不漏还省一半后期维护费,这东西凭啥这么能打? - 领域鉴赏官
  • LeetCode 189 轮转数组:三次反转原地解决,图解 Java 实现
  • 知行之桥EDI系统邮件通知机制解析与应用实践
  • LeetCode 189 轮转数组|3种解法拆解,从暴力到O(1)原地最优解
  • Python 面向对象进阶——继承、多态、魔术方法
  • Claude Code实战指南:从环境搭建到Skill工具链的AI编程全流程
  • 2026年电磁兼容检测实验室怎么选?厂家推荐与口碑分析 - 优质品牌商家
  • MSFvenom免杀Shellcode生成与监听部署实战指南
  • 从技术原理到实战:揭秘高并发抢票系统的应对策略与技巧
  • 短线、波段与价值投资策略全解析
  • 微型挖机出品质哪家高?2026十大出片品牌深度测评,所见即所得 - 工业品牌热点
  • Open-Golf游戏性能优化实战:从算法到渲染的全面调优指南
  • 卡牌游戏开发的技术困境与Godot框架的模块化解法:从性能瓶颈到规则引擎的完整方案
  • 如何用Diablo Edit2轻松打造你的专属暗黑破坏神2角色?
  • Python循环结构解析与高效编程实践
  • 教室照明设计:如何通过科学用光提升学习效率与视力健康
  • 2026 岳阳往返长沙全攻略 正规车队包车避坑提醒收藏 - 资讯动态
  • 奥特曼也逃不过刷TikTok上瘾,Sora背后最抓马的一段来了
  • 企业 AI Agent 应用场景全景:十大高频落地场景与 ROI 深度分析
  • 并行草稿模型与因果修正:大语言模型推理加速实践指南
  • 电化学氧气传感器原理与Grove模块应用全解析
  • 2026年装修公司推荐:广受好评的服务商服务质量评选 - 工业推荐榜
  • Java Web环保网站开发:SpringBoot+Vue3+MySQL8技术实践
  • AI工具导航站精选与高效使用指南:13个网站深度评测
  • RHCE作业1