本文目录导读:

- 网络或 I/O 流阻塞(最常见)
- 不完整或格式错误的 HTML
- 使用了错误的
Document类型或EditorKit - 线程竞争或 Swing 单线程模型违规
- 解析器 Bug 或过于复杂/非标准的 CSS/HTML
- 调试技巧
- 总结排查清单
这是一个非常专业且具体的问题,通常出现在使用 Java Swing 的 JEditorPane 或 HTMLEditorKit 进行 HTML 解析或渲染时。
“读取挂起”或“read pending”错误通常与 javax.swing.text.html.parser.Parser 或 DocumentParser 的内部状态有关,这个错误的核心原因是 解析器正在等待读取更多的数据来完成当前的结构(如标签、属性或实体),但数据流被中断、提前结束或阻塞了。
以下是导致该问题的常见原因及对应的解决方案:
网络或 I/O 流阻塞(最常见)
当 JEditorPane 正在加载远程 URL(HTTP/HTTPS)的 HTML 页面时,如果网络连接断开、超时或服务器响应不完整,解析器会卡在 read() 方法上,状态被设置为“等待读取”,但永远不会得到后续数据。
解决方案:
-
避免直接加载未知 URL:不要使用
pane.setPage("http://...")来加载不可控的远程页面。 -
使用自定义的
InputStream或Reader:先下载完整的 HTML 内容到本地字符串或字节数组,然后再设置给JEditorPane。// 错误方式:直接加载(容易挂起) // editorPane.setPage("http://example.com/slow_page.html"); // 正确方式:先下载完整内容 String htmlContent = downloadCompleteHtml(url); // 自己实现下载逻辑,增加超时控制 editorPane.setContentType("text/html"); editorPane.setText(htmlContent);
不完整或格式错误的 HTML
解析器遇到一个开始标签(如 <div),开始等待 > 或属性,但文本流突然结束(EOF),此时解析器会认为“我正在读取一个标签,还没读完,请继续给我数据”,从而导致状态挂起。
解决方案:
-
确保 HTML 是完整的:特别是
<table>、<div>、<script>、<style>等标签必须闭合。 -
清理 HTML 中的特殊字符:检查是否存在孤立的
<、>、&符号。5 < 10应写为5 < 10。A&B应写为A&B。
-
使用 HTML 清理库:在设置文本前,用 Jsoup 或类似库清理并补全 HTML。
String dirtyHtml = "..."; // 可能不完整的 HTML // 使用 Jsoup 清理和补全 org.jsoup.nodes.Document doc = Jsoup.parse(dirtyHtml); doc.outputSettings().syntax(org.jsoup.nodes.Document.OutputSettings.Syntax.html); String cleanHtml = doc.html(); // 现在标签是闭合的,结构是完整的 editorPane.setText(cleanHtml);
使用了错误的 Document 类型或 EditorKit
HTMLEditorKit 默认与 HTMLDocument 配合,如果你错误地设置了一个 PlainDocument 或自定义的 Document,解析器在试图将解析的内容插入文档时会因类型不匹配而卡住。
解决方案:
- 确保
EditorKit和Document匹配:JEditorPane editorPane = new JEditorPane(); editorPane.setContentType("text/html"); // 不要手动 setDocument(new PlainDocument()); // 让 HTMLEditorKit 自动创建 HTMLDocument
线程竞争或 Swing 单线程模型违规
在后台线程中调用 setText() 或 setPage(),同时主线程或另一个线程正在处理同一个 JEditorPane 的输入事件或重绘,导致解析器的内部状态(如字符缓冲区)被并发修改。
解决方案:
- 始终在 EDT (Event Dispatch Thread) 上设置 HTML 内容:
SwingUtilities.invokeLater(() -> { editorPane.setText(htmlContent); }); - 如果必须进行耗时解析(如大文件),考虑彻底隔离:在一个不可见的
JEditorPane中完成解析,然后将生成的HTMLDocument复制到可见组件。
解析器 Bug 或过于复杂/非标准的 CSS/HTML
Swing 自带的 HTMLEditorKit(基于 javax.swing.text.html)是一个非常老旧且有限的解析器(仅支持 HTML 3.2 及部分 CSS 1.0/2.0),遇到 HTML5、现代 CSS 或 JavaScript 动态内容时,极易触发内部错误,导致解析器进入非法状态。
解决方案:
- 如果可能,放弃 Swing HTML 渲染,改用 JavaFX WebView(
javafx.scene.web.WebView),它基于 WebKit,支持现代 HTML/CSS/JavaScript。 - 如果必须用
JEditorPane,简化 HTML:移除所有<script>、<style>、<form>以及复杂的<table>嵌套。 - 考虑使用 Flyingsaucer (XHTML Renderer) 作为替代渲染引擎。
调试技巧
如果你无法定位具体是哪段 HTML 导致的挂起,可以尝试以下步骤:
- 二分法定位:每次只设置 HTML 的一半内容,确定触发问题的部分。
- 增加超时(Hack 方案):继承
HTMLEditorKit并重写createDocument方法,为Parser设置一个有限制的Reader,但这样做比较复杂。 - 捕获异常:
JEditorPane通常不会抛出此异常(它发生在内部 EDT 上),尝试在PropertyChangeListener或自定义ErrorListener中监听。
总结排查清单
| 原因 | 检查点 | 快速解决 |
|---|---|---|
| I/O 阻塞 | 正在加载远程 URL? | 预下载 HTML 到字符串 |
| HTML 不完整 | 有未闭合的 <div> 或 <table>? |
用 Jsoup 清理补全 |
| 特殊字符 | 存在裸露的 < 或 &? |
转义为 < & |
| 线程问题 | setText() 是否不在 EDT? |
包裹在 invokeLater() 中 |
| 解析器限制 | 使用了 HTML5 或 CSS3? | 改用 JavaFX WebView |
最推荐的终极方案:
将你的 HTML 先用 Jsoup 解析并输出为标准 HTML 4.01,然后在 EDT 线程上调用
setText(),这能解决 95% 的“读取挂起”问题,如果仍然不行,请考虑迁移到 JavaFX WebView。