Java SE 5 引入泛型與 java.util.concurrent 奠定現代集合框架基石;Java SE 6 進一步增強雙向訪問。本文以構建 Web 爬蟲為線索,串聯泛型類型安全、線程池並發、Swing HTML 解析等核心技術,展示如何用標準庫高效完成「從種子 URL 遍歷全站鏈接」任務。
List list = new LinkedList();
list.add(new JButton("A"));
list.add(new JLabel("B")); // 編譯通過,運行時 ClassCastException
JButton btn = (JButton) list.get(1); // 崩潰
List buttons = new LinkedList<>();
buttons.add(new JButton("A"));
buttons.add(new JLabel("B")); // 編譯報錯:cannot find symbol method add(JLabel)
JButton btn = buttons.get(0); // 無需強轉,類型安全
優勢:提前發現類型錯誤、消除強轉、API 自文檔化。對遺留代碼可用 @SuppressWarnings("unchecked") 壓制警告,或用 -Xlint:unchecked 定位隱患。
無需手寫解析器,利用 javax.swing.text.html.HTMLEditorKit 解析 HTML 流,遍歷 ElementIterator 取出 <a href>:
Set uriList = new TreeSet<>(); // 去重+有序
HttpURLConnection.setFollowRedirects(false);
EditorKit kit = new HTMLEditorKit();
Document doc = kit.createDefaultDocument();
doc.putProperty("IgnoreCharsetDirective", Boolean.TRUE); // 規避 charset bug
try (Reader reader = new InputStreamReader(new URL(uri).openStream())) {
kit.read(reader, doc, 0);
ElementIterator it = new ElementIterator(doc);
Element elem;
while ((elem = it.next()) != null) {
AttributeSet attrs = (AttributeSet) elem.getAttributes().getAttribute(HTML.Tag.A);
if (attrs != null) {
String href = (String) attrs.getAttribute(HTML.Attribute.HREF);
if (href == null || href.startsWith("javascript:")) continue;
if (!href.startsWith("http")) href = uri + href; // 相對轉絕對
uriList.add(href);
}
}
}
ExecutorService service = Executors.newFixedThreadPool(5); // 固定 5 線程
service.execute(new CrawlerTask(service, baseUri, startUri));
service.shutdown();
service.awaitTermination(5, TimeUnit.MINUTES); // 等待完成或超時
class CrawlerTask implements Runnable {
private final ExecutorService pool;
private final String baseUri, currentUri;
private final Set visited; // 共享集合需線程安全
public void run() {
if (visited.add(currentUri)) { // 原子檢查+添加
Set links = extractLinks(currentUri);
for (String link : links) {
if (link.startsWith(baseUri)) // 同域才遞歸
pool.execute(new CrawlerTask(pool, baseUri, link, visited));
}
}
// 隊列空且活動線程為0時,pool 自然終止
}
}
關鍵點:visited 用 Collections.synchronizedSet 或 ConcurrentHashMap.newKeySet() 保證線程安全;任務自我複製提交,無需外部調度器。
| 工廠方法 | 核心/最大 | 隊列 | 適用場景 |
|---|---|---|---|
newFixedThreadPool(n) | n / n | 無界 LinkedBlockingQueue | 長期穩定負載 |
newCachedThreadPool() | 0 / Integer.MAX_VALUE | SynchronousQueue | 大量短任務,閒置自動回收 |
newSingleThreadExecutor() | 1 / 1 | 無界 | 串行化保證順序 |
newScheduledThreadPool(n) | n / n | DelayedWorkQueue | 定時/周期任務 |
實現 Delayed 接口的任務放入 DelayQueue,take() 會阻塞至延遲到期,天然適合定時重試、緩存過期清理等場景,比 Timer 更健壯(異常不會殺死唯一線程)。