本文目录导读:

在Java Swing开发中,JEditorPane 配合 HTMLEditorKit 处理HTML时,其底层解析器(Parser)是基于流(Stream)的,这涉及到如何将HTML文档作为输入流读取,并通过自定义或默认的解析器进行解析和渲染。
以下是关于 JEditorPane、HTMLEditorKit 及其解析器流处理的详细说明和实战示例:
核心组件与流处理机制
| 组件 | 作用 |
|---|---|
JEditorPane |
用于显示HTML的文本组件 |
HTMLEditorKit |
提供HTML编辑能力,包含解析器和视图工厂 |
Parser (解析器) |
读取HTML输入流,解析成文档结构 |
Document |
解析后的文档模型 |
Reader/InputStream |
读取HTML数据的输入流 |
默认解析器链:
InputStream → InputStreamReader → HTMLEditorKit.Parser → HTMLDocument
基本流处理示例
1 从文件读取HTML并显示
import javax.swing.*;
import javax.swing.text.html.*;
import java.io.*;
public class HTMLStreamExample {
public static void main(String[] args) {
JFrame frame = new JFrame("HTML Stream Example");
JEditorPane editorPane = new JEditorPane();
editorPane.setEditable(false);
editorPane.setContentType("text/html");
// 使用HTMLEditorKit
HTMLEditorKit kit = new HTMLEditorKit();
editorPane.setEditorKit(kit);
// 创建HTML文档
HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
editorPane.setDocument(doc);
try {
// 从文件读取HTML流
File htmlFile = new File("example.html");
FileInputStream fis = new FileInputStream(htmlFile);
InputStreamReader reader = new InputStreamReader(fis, "UTF-8");
// 直接将流读入文档
kit.read(reader, doc, 0);
reader.close();
// 或者使用setPage方法(内部也会使用流)
// editorPane.setPage(htmlFile.toURI().toURL());
} catch (Exception e) {
e.printStackTrace();
}
frame.add(new JScrollPane(editorPane));
frame.setSize(600, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
自定义Parser与流处理
1 扩展HTMLEditorKit自定义解析器
import javax.swing.text.html.*;
import javax.swing.text.html.parser.*;
import java.io.*;
public class CustomHTMLEditorKit extends HTMLEditorKit {
@Override
public HTMLEditorKit.Parser getParser() {
return new CustomParser();
}
private static class CustomParser extends HTMLEditorKit.Parser {
@Override
public void parse(Reader r, ParserCallback cb, boolean ignoreCharSet)
throws IOException {
System.out.println("[CustomParser] 开始解析HTML流...");
// 包装Reader以便于监控流处理
BufferedReader br = new BufferedReader(r);
// 创建默认的解析器
ParserDelegator delegator = new ParserDelegator();
// 自定义回调包装
ParserCallback wrappedCallback = new ParserCallbackWrapper(cb);
// 委托给默认解析器
delegator.parse(br, wrappedCallback, ignoreCharSet);
System.out.println("[CustomParser] 解析完成!");
}
}
// 记录解析过程的回调包装器
private static class ParserCallbackWrapper extends ParserCallback {
private ParserCallback original;
public ParserCallbackWrapper(ParserCallback original) {
this.original = original;
}
@Override
public void handleText(char[] text, int pos) {
System.out.println("[解析] 文本: " + new String(text).trim());
original.handleText(text, pos);
}
@Override
public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
System.out.println("[解析] 开始标签: " + t + " at " + pos);
original.handleStartTag(t, a, pos);
}
@Override
public void handleEndTag(HTML.Tag t, int pos) {
System.out.println("[解析] 结束标签: " + t);
original.handleEndTag(t, pos);
}
@Override
public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) {
System.out.println("[解析] 自闭合标签: " + t);
original.handleSimpleTag(t, a, pos);
}
@Override
public void flush() throws BadLocationException {
original.flush();
}
}
}
2 使用自定义解析器
public class CustomParserDemo {
public static void main(String[] args) throws Exception {
JFrame frame = new JFrame("Custom Parser Demo");
JEditorPane editorPane = new JEditorPane();
// 使用自定义EditorKit
CustomHTMLEditorKit kit = new CustomHTMLEditorKit();
editorPane.setEditorKit(kit);
// HTML内容(作为字符串流)
String html = "<html><body><h1>Hello</h1><p>This is <b>bold</b> text.</p></body></html>";
StringReader reader = new StringReader(html);
// 解析流
HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
kit.read(reader, doc, 0);
editorPane.setDocument(doc);
frame.add(new JScrollPane(editorPane));
frame.setSize(400, 300);
frame.setVisible(true);
}
}
高级流处理技巧
1 带监控的流包装器
import java.io.*;
public class MonitoredInputStream extends FilterInputStream {
private long totalBytesRead = 0;
private long lastReportTime = System.currentTimeMillis();
public MonitoredInputStream(InputStream in) {
super(in);
}
@Override
public int read() throws IOException {
int b = super.read();
if (b != -1) {
totalBytesRead++;
reportProgress();
}
return b;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int bytesRead = super.read(b, off, len);
if (bytesRead > 0) {
totalBytesRead += bytesRead;
reportProgress();
}
return bytesRead;
}
private void reportProgress() {
long now = System.currentTimeMillis();
if (now - lastReportTime > 1000) { // 每秒报告一次
System.out.println("已读取: " + (totalBytesRead / 1024) + " KB");
lastReportTime = now;
}
}
public long getTotalBytesRead() {
return totalBytesRead;
}
}
2 流转换:从网络获取HTML
import java.net.*;
import javax.swing.text.html.*;
public class NetworkHTMLReader {
public static void loadHTMLFromURL(JEditorPane editorPane, String urlString) {
try {
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
// 获取输入流
InputStream inputStream = conn.getInputStream();
// 使用监控流
MonitoredInputStream monitoredStream = new MonitoredInputStream(inputStream);
// 转换为Reader
InputStreamReader reader = new InputStreamReader(monitoredStream, "UTF-8");
// 解析
HTMLEditorKit kit = (HTMLEditorKit) editorPane.getEditorKit();
HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
kit.read(reader, doc, 0);
reader.close();
System.out.println("总共读取: " + monitoredStream.getTotalBytesRead() + " bytes");
} catch (Exception e) {
e.printStackTrace();
}
}
}
流处理优化建议
1 使用BufferedReader提高性能
// 不推荐
InputStreamReader reader = new InputStreamReader(inputStream);
kit.read(reader, doc, 0);
// 推荐 - 使用缓冲
BufferedReader reader = new BufferedReader(
new InputStreamReader(inputStream, "UTF-8"), 8192); // 8KB缓冲区
kit.read(reader, doc, 0);
2 处理字符编码问题
public static void loadHTMLWithEncoding(JEditorPane pane, InputStream is, String encoding) {
try {
// 先检测BOM标记
PushbackInputStream pushback = new PushbackInputStream(is, 4);
byte[] bom = new byte[4];
int n = pushback.read(bom, 0, 4);
String detectedEncoding = encoding;
if (n >= 2) {
if (bom[0] == (byte)0xFE && bom[1] == (byte)0xFF) {
detectedEncoding = "UTF-16BE";
pushback.unread(bom, 2, n - 2);
} else if (bom[0] == (byte)0xFF && bom[1] == (byte)0xFE) {
detectedEncoding = "UTF-16LE";
pushback.unread(bom, 2, n - 2);
} else if (n >= 3 && bom[0] == (byte)0xEF &&
bom[1] == (byte)0xBB && bom[2] == (byte)0xBF) {
detectedEncoding = "UTF-8";
pushback.unread(bom, 3, n - 3);
} else {
pushback.unread(bom, 0, n);
}
}
Reader reader = new BufferedReader(
new InputStreamReader(pushback, detectedEncoding));
HTMLEditorKit kit = (HTMLEditorKit) pane.getEditorKit();
HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
kit.read(reader, doc, 0);
} catch (Exception e) {
e.printStackTrace();
}
}
注意事项和最佳实践
- 线程安全:解析过程应在EDT(事件分发线程)外进行,完成后更新UI
- 资源管理:始终在finally块中关闭流
- 错误处理:HTML解析错误不会抛出异常,但文档可能不完整
- 性能监控:大型HTML文件推荐使用缓冲区监控
// 在后台线程中加载HTML
SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
// 在后台线程中解析
HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
kit.read(reader, doc, 0);
return null;
}
@Override
protected void done() {
// 在EDT中更新UI
editorPane.setDocument(doc);
}
};
worker.execute();
通过合理使用流处理,你可以高效地从各种来源(文件、网络、内存)加载HTML内容到 JEditorPane 中显示,并且可以根据需要进行自定义解析和监控。