1. 程式人生 > >java api文件離線查詢器

java api文件離線查詢器

寫java程式的時候,經常要到官網(http://docs.oracle.com/javase/8/docs/api/index.html)查文件,用著用著感覺有時有些不方便:
1.java官網有時登不上去
2.沒有搜尋查詢功能
所以針對這兩個,就想到做一個文件離線查詢器,把文件離線下來,然後可以進行搜尋查詢。(程式碼依然寫得亂。。。)

專案介紹:

專案名稱:java文件離線下載器(JDOR)

exe檔案下載:

本地電腦具有jdk 1.8版本的(需要配置好java系統環境):

本地環境沒有配好的,可以下載jdk1.8的jre包,放到該exe所在資料夾便可使用(jdk1.8的jre包有180多兆,太大,傳不上來)

使用環境:windows

程式分析:

下載元件、流程分析:

total
offline_class

Record:

Record作為資料的集合,用於在各元件之間傳遞

/**
 * 
 * 儲存一條資料記錄(一個包或一個類)的類
 * 
 * url:string 網址(離線時是線上網址,查詢時是本地網址) 
 * filePath:string 本地檔案儲存地址 
 * papge:string 網頁資料(html原始碼) 
 * fullname:string 包名或類名 
 * isclass:boolean 是否為類
 * 
 * @author zwh
 * @version 1.0
 */
public class Record { public String url = ""; public String filePath = ""; public String page = ""; public String fullName = ""; public boolean isClass = false; }

Offliner下載器:

流程圖:

offliner

解析:

功能:
Offliner下載器是下載總元件,主要任務是為外部提供呼叫介面,內部啟動、呼叫其它的元件完成下載任務。

//開始下載
public void offline
(String version); //獲取總共需要下載的Record的數量 public int getTotalRecords(); //設定下載速度 public void setOfflineSpeed(SPEED speed);

RecordAllocater任務分配器:

流程圖:

RecordAllocater

解析:

功能:
將傳遞進來的未下載的Record分派給RecordOffliner執行任務下載(多執行緒),以及啟動異常Record重分派器。
分析:
由於採用迴圈列表建立執行緒執行任務,因此採用執行緒池可以更方便的進行管理:

public class RecordAllocater extends Thread
......
//該執行緒池會動態的增加、刪除執行緒
private ExecutorService threadPool = Executors.newCachedThreadPool();
......
public void allocate()
    {
        for (Record record : this.nonOfflineRecords)
        {
            //offliner繼承了Thread
            RecordOffliner offliner = new RecordOffliner(record);
            //直接呼叫執行緒池的execute方法啟動新執行緒
            this.threadPool.execute(offliner);
            //登記資訊:建立了一個新的下載執行緒
            RecordOfflinerMonitor.createARecordOffliner();
            /*
            *用於控制下載速度,如果time==0,就會在短時間內建立了大量執行緒,對目標網站進行訪問,
            *容易造成目標網站封禁IP不能登入
            */
            try
            {
                sleep(time);
            } catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
        //在UI處顯示資訊
        OfflineUI.appendOfflineExceptionMessage("RecordAllocater分派任務完成");
    }
......

ExceptionRecordAllocater任務分配器:

流程圖:

ExceptionRecordAllocater

解析:

功能:
ExceptionRecordAllocater實際上是RecordAllocater的內部私有類,由RecordAllocater負責啟動,當RecordOffliner下載過程中出現異常(由於短時間內大量執行緒對目標網站的訪問導致的IP暫時被封禁)的時候,Record將被髮送到RecordAllocater的靜態變數exceptionRecords佇列中,這時將喚醒ExceptionRecordAllocater對這些下載異常的Record重新分派。

//由於是併發訪問,因此要使用執行緒安全的ConcurrentLinkedQueue<Record> 
private static ConcurrentLinkedQueue<Record> exceptionRecords = new ConcurrentLinkedQueue<Record>();
......
    /**
     * 
     * RecordOffliner呼叫該方法傳送異常Record
     * 
     * @param record:下載異常的Record
     */
    public static void exceptionRecord(Record record)
    {
        RecordAllocater.exceptionRecords.add(record);
        synchronized (lockNewRecord)
        {
            //喚醒沉睡的ExceptionRecordAllocater
            lockNewRecord.notifyAll();
        }
    }
......
private class ExceptionRecordsAllocater extends Thread
    {
        @Override
        public void run()
        {
            while (true)
            {
                //當沒有異常Record加入時,會一直陷入沉睡狀態
                synchronized (lockNewRecord)
                {
                    try
                    {
                        lockNewRecord.wait();
                    } catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                }
//UI處顯示資訊
                        OfflineUI.appendOfflineExceptionMessage("ExceptionRecordsAllocater喚醒,開始將發生下載錯誤的Record重新分派");
                /*
                 *這時執行緒已被喚醒(一定存在異常Record),而且可能存在多個異常Record(其它執行緒在執行的時候也出現了異常)
                 *因此需要遍歷exceptionRecords
                 */
                while (!exceptionRecords.isEmpty())
                {
                    Record record = exceptionRecords.poll();
                    if (record != null)
                    {
                        //啟動執行緒執行,依然使用RecordAllocater的執行緒池
                        RecordOffliner offliner = new RecordOffliner(record);
                        threadPool.execute(offliner);
                        RecordOfflinerMonitor.createARecordOffliner();
                        //由於這些是異常Record,增大分派時間間隔,確保這次成功的機率
                        try
                        {
                            sleep(500);
                        } catch (InterruptedException e)
                        {
                            e.printStackTrace();
                        }
                    }
                }
                OfflineUI.appendOfflineExceptionMessage("發生下載錯誤的Record分派完成,ExceptionRecordsAllocater陷入沉睡");
            }
        }
    }

RecordOffliner任務下載器:

流程圖:

RecordOffliner

解析:

作為下載任務的最終執行者,每個RecordOffliner負責下載一個Record指定的頁面。

/**
     * 下載過程出現異常(爬取過程出現的異常),
     * 將在OfflineUI中顯示異常資訊,並將Record重新交給RecordAllocater重新分配,最後將在
     * RecordOfflinerMonitor登記資訊;如果正常下載完,將傳送給FinishedRecordRepertory,並
     * 在RecordOfflinerMonitor登記資訊
     */
    @Override
    public void run()
    {
        try {
            //爬取指定url的頁面
            this.record.page = Crawler.crawl(this.record.url);
        } catch (Exception e) {
            OfflineUI.appendOfflineExceptionMessage(record.fullName + ":離線過程中出現網路異常,被送入ExceptionRecordsAllocater");
            //出現異常,將Record傳送給RecordAllocater(ExceptionRecordAllocater)重新分派
            RecordAllocater.exceptionRecord(record);
            //在RecordOfflinerMonitor處登記資訊
            RecordOfflinerMonitor.ARecordOfflinerException();
        }
        //成功下載完,因此將Record傳送給FinishedRecordRepertory完成最後儲存到本地的任務
        FinishedRecordRepertory.add(this.record);
        //登記資訊
        RecordOfflinerMonitor.finishARecordOffliner();
    }

FinishedRecordRepertory成品倉庫:

流程圖:

FinishedRecordRepertory

解析:

負責將成功下載的Record儲存到本地。
是一個“靜態類”,只需要用到它的靜態方法,它運作與ExceptionRecordAllocater的運作相似,先是等待,被喚醒後,執行把所有Record儲存到相應的地址。

/**
     * 當沒有Record需要儲存時,陷入睡眠狀態,每當有Record加入,將被喚醒
     * 然後執行儲存工作,若儲存工作出現異常,將Record重新加入到儲存列表中
     */
    @Override
    public void run()
    {
        while (true)
        {
            //等待新Record加入
            if (FinishedRecordRepertory.records.isEmpty())
            {
                try
                {
                    synchronized (FinishedRecordRepertory.lockNewRecord)
                    {
                        FinishedRecordRepertory.lockNewRecord.wait();
                    }
                } catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
            //被喚醒,開始執行任務
            while (!FinishedRecordRepertory.records.isEmpty())
            {
                Record record = FinishedRecordRepertory.records.poll();
                //啟動新執行緒執行
                FinishedRecordRepertory.threadPool.execute(new Thread(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        //實際的執行方法
                        writeToFile(record);
                    }
                }));
            }
        }
    }

在儲存過程中依然可能會出現異常:
1.建立檔案失敗
2.寫入資料失敗
為此依然要做好準備工作

private static void writeToFile(Record record)
    {
        synchronized (lock)
        {
            FinishedRecordRepertory.nonFinishedRecordNumber--;
        }
        //檔案建立失敗,將Record重新加入佇列中等待
        if (!FileManager.isExit(record.filePath))
            if (!FileManager.createFile(record.filePath))
            {
                records.add(record);
                OfflineUI.appendOfflineExceptionMessage(record.fullName + "檔案建立失敗,加入佇列等待");
            }
        //檔案寫入失敗
        if (FileManager.cover(record.filePath, record.page))
        {
            OfflineUI.appendOfflineSuccessMessage(record.fullName + ":離線成功");
            return;
        }
        //將Record重新加入佇列
        records.add(record);
        OfflineUI.appendOfflineExceptionMessage(record.fullName + "檔案寫入失敗,加入佇列等待");
    }

查詢元件分析:

前面說過了下載元件,而我們的程式下載完後就是查詢、顯示。
現在來說的查詢元件比較簡單,因為下載的時候,我們已經把目錄檔案也下載了下來,因此直接讀取目錄檔案使用RecordReader(下面資料分析中介紹)轉化為Record格式儲存,便可以很方便進行查詢。
然後為了可以進行模糊查詢,因此沒有使用二分搜尋,而是直接迴圈查詢,查詢也是很快,因為資料最大也就是4、5千,而耗時較多的是讀取目錄並進行解析轉化的過程

    //在類目錄中查詢
    public ArrayList<Record> searchClass(String target)
    {
        return this.search(target, true);
    }
    //在包目錄中查詢
    public ArrayList<Record> searchPackage(String target)
    {
        return this.search(target, false);
    }

    private ArrayList<Record> search(String target, boolean isClass)
    {
        //確定後需要查詢的目錄
        ArrayList<Record> temp = this.packageRecords;
        if (isClass)
            temp = this.classRecords;
        ArrayList<Record> result = new ArrayList<Record>();
        //因為不是精準查詢,所以需要迴圈所有目錄項
        for (Record record : temp)
        {
            //模糊查詢,只要包含目的字串,變加入到結果中返回
            if (record.fullName.toLowerCase().contains(target.toLowerCase()))
                result.add(record);
        }
        return result;
    }

瀏覽元件分析:

網頁內容在程式內的顯示:

JEditorPane是swing元件,使用方便,但是效果不好(最後面有程式使用的截圖),因此程式中特地提供了在瀏覽器開啟網頁的按鈕,方便觀看

    private JEditorPane edpDisplayHtml = new JEditorPane();
    private JScrollPane scrollHtmlDisplay = new JScrollPane(this.edpDisplayHtml);
    ......
    private void setHtmlDisplay()
    {
        this.edpDisplayHtml.setContentType("text/html");
        this.edpDisplayHtml.setEditable(false);
        //讓網頁中的超連結可使用
        this.edpDisplayHtml.addHyperlinkListener(this);
    }
    ......
    @Override
    public void hyperlinkUpdate(HyperlinkEvent e)
    {
        if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
        {
            this.displayHtmlPage(e.getURL().toString());
            //historyManager是用來管理瀏覽歷史,用於前進後退,下面會介紹
            this.historyManager.add(e.getURL().toString());
        }
    }

ExploreHistoryManager瀏覽記錄的前進、後退:

本程式支援在持續中顯示內容,因此就會存在一個瀏覽記錄的問題,而本程式也提供了一個類似於瀏覽器的前進、後退功能。
歷史記錄的資料結構為:

    //ExploreHistoryManager私有類
    private class HistoryRecord
    {
        public String record = "";
        public int no = 0;

        public HistoryRecord(String record,int no)
        {
            this.no = no;
            this.record = record;
        }
    }

演算法解析:

瀏覽的網頁按照先後順序遞增編號
當後退時,從連結串列的最後(也就是當前網頁)開始往前走,當遇到的第一個編號小於當前網頁的,即為前一個網頁,這時把該網頁(編號不變,且原來位置的而依然保留)直接加入到連結串列尾端成為新的當前網頁
當前進時,和後退差不多,唯一不同就是判斷的標準,這時不是小於,而是應該第一個大於當前網頁編號的為前一個網頁,這時把該網頁**原
封不動地複製**到連結串列尾端成為新的當前網頁。
最後,為了防止儲存的網頁過多,設定最多儲存20個網頁,當超過20個網頁時,把連結串列頂端的網頁刪除掉。
下面為示例:
history-1
上圖為瀏覽歷史記錄1,解析為按先後順序分別瀏覽了網頁a,b,c,d,其中它們的編碼為遞增。

這時使用者按下返回鍵,
history-2
使用者按下返回鍵的時候的當前網頁為d,編號4,開始往前搜尋,遇到的第一個小於其編號的網頁c即為d的前一個網頁,這時把c加到尾端成為新的當前網頁。

這時使用者又按下返回鍵,
history-3
使用者按下返回鍵的時候的當前網頁為c,編號3,開始往前搜尋,遇到的第一個小於其編號的網頁b即為c的前一個網頁,這時把b加到尾端成為新的當前網頁。

這時用於按下前進鍵,
history-4
使用者按下返回鍵的時候的當前網頁為b,編號2,開始往前搜尋,遇到的第一個大於其編號的網頁c即為b的前一個網頁,這時把c加到尾端成為新的當前網頁。


    //傳入的Comparator<HistoryRecord>用於前進或後退的判斷依據
    private String move(Comparator<HistoryRecord> comparator)
    {
        int len = this.history.size();
        if(len == 0)
            return "";
        HistoryRecord currentRecord = this.history.get(len - 1); 
        for(int i = len - 1;i > 0; i --)
        {
            //當前進時,當前網頁編號需要小於對比的網頁編號
            //當後退時,當前網頁編號需要大於對比的網頁編號
            if(comparator.compare(currentRecord, this.history.get(i - 1)) == 1)
            {
                HistoryRecord temp = history.get(i - 1);
                //將得到的結果加入到連結串列尾端作為新的當前網頁
                this.add(temp);
                return temp.record;
            }
        }
        //如果沒有找到結果,表明該當前網頁是第一個訪問的網頁(後退時)
        //或者當前網頁是最後一個訪問的網頁(前進時)
        //因此直接返回當前網頁。
        return currentRecord.record;
    }

上面演算法中的網頁儲存的是什麼:

儲存的肯定不會是整個網頁內容,這樣佔用很大記憶體,因此,裡面儲存的是網址,這時又有一個疑問,那麼搜尋結果怎麼儲存?這時也不是直接儲存整個搜尋結果,而是儲存搜尋的目標字串,這樣,下次顯示時,只需重新把字串拿去搜索獲取結果便可以了。

SearchResultDisplayFormator顯示的內容格式轉化:

從上面的Searcher元件可知,搜查結果返回的是Record格式的資料,但是顯示的是以網頁格式顯示,不能僅僅以簡單的txt文字格式顯示,其中有個很重要的原因是要利用網頁的超連結,不然會更麻煩。
對的,因此我們需要將Record格式轉變為網頁格式,因此我們這裡將其轉變成html中的列表形式:

<html>
<body>
<ul>
......
<li><a href=...>...</li>
......
</ul>
</body>
</html>

這樣就簡單的完成了顯示

    public static String format(Collection<Record> records)
    {
        String result = "<html><body><ul>";
        String formator = "<li><a href=\"URL\">NAME</a></li>";
        for(Record record:records)
        {
            String temp = formator.replace("URL", record.url);
            temp = temp.replace("NAME", record.fullName);
            result += temp;
        }
        result += "</ul></body></html>";
        return result;
    }

工具元件分析:

StringSpliter字串分割元件:

聽說用java自帶的String.split效率不好。。。哈哈,這個就寫寫吧。。。不用管它就好。。。

FileManager檔案操作:

把java原來的檔案操作封裝下,讓它在建立、讀寫檔案/資料夾的時候更方便。

//建立資料夾,當中間缺失一層或多層檔案的時候,會補全缺失的資料夾,直至目標資料夾建立,返回建立結果
//例子:目標:c:\\a\\b\\c,但是中間的a、b都不存在,該函式會自動建立資料夾a、b,使可以建立到目標資料夾
public static boolean createDir(String path);
//建立檔案,與上面的建立資料夾類似,會自動補全中間缺失的資料夾,返回建立結果
public static boolean createFile(String path);
//讀取字串
public static String readString(String path);
//覆蓋資料到目標檔案,如果檔案不存在,不會建立檔案,並且返回false
public static boolean cover(String path, String content);
//新增資料到目標檔案,如果檔案不存在,不會建立檔案,並且返回false
public static boolean append(String path, String content);
//判斷資料夾或檔案是否存在
public static boolean isExit(String path);
//獲取檔案或資料夾長度
public static long getLength(String path);

資料元件分析:

配置項資料:

解析:

程式中的部分引數可能有改動,例如java版本提高,就需要加入新的java api網址,這時可以通過修改配置檔案來加入新網址之類等等。

配置檔案儲存地址:C:\jdor\configure.txt

configure.txt

配置項介紹及修改注意:

1.java api網址:

<name=javaVersionName-versionNumber><data=url>;
例子:
<name=javase-8><data=http://docs.oracle.com/javase/8/docs/api>;

javaVersionName:javase
versionNumber:8
url:http://docs.oracle.com/javase/8/docs/api

其中url和名字的關係必須滿足:*javaVersionName/versionNumber*
                         *javase/8*

2.下載速度:

<name=*speed><data=分配一個任務以及分配下一個任務之間的間隔時間>;

可根據情況進行調整:
我試驗是在超高速情況下(間隔時間為0),會出現大量的下載異常(連線超時),雖說程式會把異常Record重分派,但到最後發現會**少了一個到兩個左右的檔案**
高速情況下,正常完成任務,所需時間也就是5、6分鐘左右。

3.資料儲存的資料夾:

<name=data-filldir><data=資料夾路徑>;

配置項相關類:

Configure:

/**
 * 配置項類
 * name:配置項名
 * data:配置項資料
 * 
 * @author zwh
 * @version 1.0
 */
public class Configure
{
    public String name = "";
    public String data = "";

    public Configure()
    {

    }

    public Configure(String name, String data)
    {
        this.name = name;
        this.data = data;
    }
}

DefaultConfigureKeeper預設配置項資料儲存器:

由於程式第一次開啟的時候,沒有配置檔案,而程式很多地方都需要配置的資料,因此該類使用硬編碼方式儲存了基本的配置項資料。

//該類不能被修改
public final class DefaultConfigureKeeper
{
//由於僅使用該類的靜態方法,其所有方法、變數均為靜態
private static ArrayList<Configure> configures = new ArrayList<Configure>();
static
    {
        configures.add(new Configure("javase-8", "http://docs.oracle.com/javase/8/docs/api"));
        configures.add(new Configure("javase-7", "http://docs.oracle.com/javase/7/docs/api"));
        configures.add(new Configure("javase-6", "http://docs.oracle.com/javase/6/docs/api"));
        configures.add(new Configure("javase-5", "http://docs.oracle.com/javase/1.5.0/docs/api"));
        configures.add(new Configure("javaee-7", "https://docs.oracle.com/javaee/7/api"));
        configures.add(new Configure("javaee-6", "https://docs.oracle.com/javaee/6/api"));
        configures.add(new Configure("javaee-5", "https://docs.oracle.com/javaee/5/api"));
        configures.add(new Configure("superspeed", "0"));
        configures.add(new Configure("highspeed", "70"));
        configures.add(new Configure("normalspeed", "200"));
        configures.add(new Configure("lowspeed", "400"));
        configures.add(new Configure("data-filldir", "c:\\jdor"));
    }
    //獲取所有配置項
    public static ArrayList<Configure> getConfigures()
    {......;}
    //根據名字返回資料項
    public static String getData(String name)
    {......;}
}

ConfigureWriter配置項資料檔案寫入器:

由於在元件之間互動配置項資料的形式都是以Configure的形式互動,而儲存到本地檔案是以上面介紹的形式儲存,因此該類的作用是將Configure的資料重新組裝成所需形式並寫入檔案

    //必須傳入一個或多個Configure給建構函式作為引數
    public ConfigureWriter(Collection<Configure> coll)
    {
        this.configureData = coll;
    }

    public ConfigureWriter(Configure con)
    {
        this.configureData.add(con);
    }

資料組裝並寫入:

//isCover:以覆蓋還是新增的方法
//該函式不是直接供外部使用
private boolean write(String path, boolean isCover)
    {
        if (!FileManager.isExit(path))
            if (!FileManager.createFile(path))
                return false;
        String data = "";
        //資料的重新組裝
        for (Configure configure : this.configureData)
        {
            String temp = String.format("<name=%s><data=%s>;\r\n", configure.name, configure.data);
            data += temp;
        }
        //資料的寫入
        if (isCover && FileManager.cover(path, data))
            return true;
        if (!isCover && FileManager.append(path, data))
            return true;
        return false;
    }
    //為外部呼叫,提供更方便方法
    public boolean cover(String path)
    {
        return this.write(path, true);
    }

    public boolean append(String path)
    {
        return this.write(path, false);
    }

ConfigureReader配置項資料閱讀器:

該類與上面的寫入器功能可以說正好相反,負責從配置檔案讀出配置項資料,但由於讀出的資料為字元創格式,為了方便資料傳遞,需要將其重組裝為Configure格式
而且其它元件、類獲取配置項資料均是通過呼叫該類獲取,因此在第一次啟動程式的時候(沒有配置檔案),需要該類初始化配置檔案,該類也是僅使用其中的靜態方法、變數

    public static ArrayList<Configure> configureData = new ArrayList<Configure>();
    ......
    static
    {
        ConfigureReader.initialize();
    }
    ......
    //初始化該類的資料或者配置項檔案
    private static void initialize()
    {
        //由於該方法程式碼繁雜。。。因此用虛擬碼代替
        if(配置檔案不存在)
        {
            建立配置檔案;
            if(配置檔案建立失敗)
            {
                彈出提示框;
                return ;
            }
            寫入配置檔案;
            //從DefaultConfigureKeeper獲取配置項資料
            if(配置檔案寫入失敗)
            {
                彈出提示框;
                return ;
            }
        }
        從配置檔案讀取配置項資料;
        if(資料出現異常為空)
        {
            彈出提示框;
            return;
        }
        重組配置資料;
    }

目錄項資料相關類:

RecordReader:

與ConfigureReader相似,不過也有很多不同,該類需要建立物件使用,而且必須傳入字串作為引數,該類將字串的陣列重組裝為Record格式,方便資料傳遞使用。
而實際上呼叫該類傳入的資料通常為網頁資料:

<ul title="Packages">
<li><a href="java/applet/package-frame.html" target="packageFrame">java.applet</a></li>
<li><a href="java/awt/package-frame.html" target="packageFrame">java.awt</a></li>
<li><a href="java/awt/color/package-frame.html" target="packageFrame">java.awt.color</a></li>

從上可以看出其url不是完整的URL,因此還需加上URL的前部分,所以還需要傳遞另外兩個引數來完整Record儲存的資料:url(字首),fillDir(資料儲存的資料夾)

    public RecordReader(String str, String url, String fileDir)
    {
        this.analize(str, url, fileDir);
    }
    //資料重組
    private void analize(String str, String url, String fileDir)
    {
        //先把部分擷取下來
        //<a href="java/awt/color/package-frame.html" target="packageFrame">java.awt.color</a>

        Pattern pa = Pattern.compile("(<a.*?</a>)");
        //再逐個提取資料
        while (ma.find())
        {
            String temp = ma.group(1);
            Record re = new Record();
            re.isClass = temp.contains("title");
            re.url = this.getStr(Pattern.compile("href=\"(.*?)\""), temp);
            re.fullName = this.getStr(Pattern.compile(">(.*?)<"), temp);
            re.filePath = re.url.replaceAll("/", "\\\\");
            re.url = url + "/" + re.url;
            re.filePath = fileDir + "\\" + re.filePath;
            this.records.add(re);
        }

    }

資料互動分析:

解析:

這次資料互動,也就是類之間資料的傳遞,感覺難處在於涉及到多執行緒,而且我這次元件涉及還是要求每個執行緒最後把資料傳遞到同一個元件,然後再在這個元件裡面處理資訊。
想來想去,感覺就是用static靜態方法來實現最好,所以這次我把所有負責處理多執行緒產生的資料資訊的類,都弄成了不需要建立物件使用,而是直接呼叫它們的靜態方法。

負責處理執行緒產生資訊的類元件:

RecordAllocater(ExceptionRecordAllocater)任務分派器:

//儲存異常Record
private static ConcurrentLinkedQueue<Record> exceptionRecords = new ConcurrentLinkedQueue<Record>();
......
//RecordOffliner呼叫該方法,將異常Record新增到exceptionRecords
public static void exceptionRecord(Record record)
{......}

FinishedRecordRepertory成品倉庫:

//儲存完成了下載的Record
public static ConcurrentLinkedQueue<Record> records = new ConcurrentLinkedQueue<Record>();
//管理多執行緒
private static ExecutorService threadPool = Executors.newCachedThreadPool();
//用於喚醒執行緒執行任務
private static Lock lockNewRecord = new ReentrantLock();
//新增Reocrd到records 
public static void add(Record record)
{......}
//獲取已完成儲存的Record數量
public static synchronized int getNonFinishedRecordNumber()
{......}

RecordOfflinerMonitor下載執行緒監控器:

    private static Lock lockAliveNumber = new ReentrantLock();
    private static Lock lockFinishedNumber = new ReentrantLock();
    private static Lock lockExceptionNumber = new ReentrantLock();
    //存活的下載執行緒數量
    private static int aliveNumber = 0;
    //完成離線(下載+儲存到本地)的執行緒數量
    private static int finishedNumber = 0;
    //下載出現異常的執行緒數量
    private static int exceptionNumber = 0;

    public static  int getAliveNumber()
    {......}
    public static int getFinishedNumber()
    {......}
    public static int getExceptionNumber() 
    {......}
    //建立了新執行緒
    public static void createARecordOffliner()
    {......}
    //完成了一個新執行緒的離線(下載+儲存到本地)
    public static void finishARecordOffliner()
    {......}
    //一個Record下載過程中出現了異常
    public static void ARecordOfflinerException()
    {......}

OfflineUI下載UI介面:

//防止資料競爭
private static Lock lockNewOfflineExceptionMessage = new ReentrantLock();
//防止資料競爭
private static Lock lockNewOfflineSuccessMessage = new ReentrantLock();
......
//顯示組建啟動及錯誤資訊記錄,因此基本上需要和所有下載元件進行資料互動
private static JTextArea txtOfflineExceptionMessage = new JTextArea();
//顯示成功下載記錄,因此需要和FinishedRecordRepertory進行資料互動
private static JTextArea txtOfflineSuccessMessage = new JTextArea();
......
//新增組建啟動及錯誤資訊
public static void appendOfflineExceptionMessage(String message)
{......}
//新增成功下載的記錄資訊
public static void appendOfflineSuccessMessage(String message)
{......}

程式專案總結:

用到的東西:

多執行緒:

1.同步synchronized的使用
2.喚醒機制
3.執行緒之間使用靜態方法、變數進行資訊互動(雖然不知道好不好)
4.執行緒內部的異常在自己內部捕抓、解決掉

資料流:

這次那個RecordReader和ConfigureReader,是模仿了java的io包的那種資料流方法(我自己覺得是。。。),就是獲取資料流->資料流過濾器(將原始資料流組裝、過濾成新的格式)

swing:

1.JMenuBar,JMenu,JMenuItem,ActionListener
2.JComboxBox,ItemListener
3.JEditorPane,HyperlinkListener
4.JProgressBar

總結:

算是一個較為完整的程式,可用性還是有的。。。只是程式碼感覺還是一團糟,特別非UI元件和UI元件之間的互動,有待改善。。。

程式使用介紹:

第一次開啟和沒有離線文件的情況下:

first-open

離線資料:

開始進行離線資料:

click-offlinedata

下載介面:

offlineUI

選擇版本:

choose-version

選擇下載速度:

choose-speed

開始下載:

start-1

當下載出現異常時:

P.S.圖中紅框部分就是異常資訊以及異常導致元件啟動的資訊
start-2

下載完成:

P.S.下載結束的唯一確定標誌<成功下載記錄>不再更新
start-3
start-4

查詢資料:

當下載完成後,關閉下載介面,返回主介面按一下重新整理按鈕,便可顯示最新下載的java文件:

search-version

選擇完版本後,便會顯示類目錄:

display-main

主目錄點選Package後,顯示包目錄:

display-main

輸入目標字元,點選search後,預設是在類中搜索:

display-result

點選Package後,在包中搜索:

display-result

結果顯示:

display-result