首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

泛型與並發集合實戰:構建高性能 Web 爬蟲

     編輯:Java 相關知識

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 定位隱患。

單頁鏈接提取:Swing HTMLEditorKit

無需手寫解析器,利用 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);
        }
    }
}

全站遍歷:線程池並發化

為什麼需要線程池

  • 網絡 I/O 延遲大,單線程串行吞吐極低
  • 無限創建線程導致上下文切換風暴、OOM

ExecutorService 模式

ExecutorService service = Executors.newFixedThreadPool(5);  // 固定 5 線程
service.execute(new CrawlerTask(service, baseUri, startUri));
service.shutdown();
service.awaitTermination(5, TimeUnit.MINUTES);  // 等待完成或超時

Runnable 任務設計

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_VALUESynchronousQueue大量短任務,閒置自動回收
newSingleThreadExecutor()1 / 1無界串行化保證順序
newScheduledThreadPool(n)n / nDelayedWorkQueue定時/周期任務

延伸集合:DelayQueue 實現調度

實現 Delayed 接口的任務放入 DelayQueue,take() 會阻塞至延遲到期,天然適合定時重試、緩存過期清理等場景,比 Timer 更健壯(異常不會殺死唯一線程)。

總結與擴展

  • 泛型讓集合成為「類型安全容器」,編譯期攔截 90% 類型錯誤
  • 線程池 + 任務自我複製 = 簡潔高效的並發遞歸
  • 可擴展:收集圖片/文本/鏈接圖譜、加入深度/廣度控制、整合資料庫持久化
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved