一種基於狀態機的 DOM 樹生成技術(1)

本文首發於微信公衆號:"算法與編程之美",歡迎關注,及時瞭解更多此係列博客。html

DOM(Document Object Model)即文檔對象模型,是一種很是重要的數據結構,用途很是普遍。git

對於瀏覽器的渲染引擎來講,須要將html 字符串轉換成 DOM 樹,再轉換成渲染樹,最後才進行渲染。正則表達式

對於數據採集來講,常常須要作的是解析已經下載的 html 文檔,而這種解析工做的前提是要生成 html 文檔的 DOM 樹,而後才能解析。算法

本系列博客將爲你們介紹一種基於狀態機的 DOM樹生成技術,從最初的 html 文檔如何一步步生成最終的 DOM 樹。編程

 本講咱們將爲你們介紹狀態機的基本知識。瀏覽器

1 狀態機介紹

 

圖1- 1 狀態機示例圖微信

咱們從上述一個簡單的示例來介紹什麼是狀態機,圖中四個圓圈分別表示四個狀態即狀態0,狀態1,狀態2,狀態3。其中狀態0表示起始狀態,狀態二、狀態3表示終止狀態。起始狀態表示任務開始的地方,終止狀態將再也不接受任何輸入。數據結構

狀態0:當用戶輸入字符'a'的時候,將進入狀態1,其餘字符將進入狀態3。ide

狀態1:用戶能夠不停的輸入字符'b',可是會一直處於狀態1,當用戶輸入字符'a'的時候,將進入狀態2,其餘字符進入狀態3。測試

狀態2:終止狀態,再也不接受任何輸入。

狀態3:終止狀態,再也不接受任何輸入。

上面給你們介紹的就是狀態機,在狀態機中有多個狀態,通常狀況下,有一個起始狀態,多箇中間狀態和終止狀態。對於每個狀態能夠接受不一樣的輸入,或維持在當前狀態,或進入下一個狀態。

上面給你們介紹的這個狀態機,其實功能很簡單,這個狀態機能夠接受任何abbbb*a模式的字符串如:aba, abba, abbba等。

狀態機的用途很是的普遍,不只在正則表達式,編譯器等衆多領域發揮重要做用。

2 狀態機實現

有了上述的狀態機基本知識後,本節將爲你們介紹如何用 Java 語言實現狀態機。

要想實現上述狀態機,咱們有如下三個任務須要完成:

(1)對字符串輸入的控制。

咱們須要定義一個類可以實現很是方便的控制整個輸入的字符串,每次移動一個字符。

這個類很是的簡單,以下所示:

public class CharacterReader {
    private String content;
    private int pos;

    public CharacterReader(String content) {
        this.content = content;
        pos = 0;
    }

    public char consume() {

        return content.charAt(pos++);
    }
}

對於給定的任意一個字符串如"abbba",咱們保存在 content 屬性中,再定義一個 pos 屬性,標記當前正在處理的是哪個字符位置,處理完一個字符後,位置後移一位,初始位置爲0。

CharacterReader reader = new CharacterReader("abbba");

 

(2)各類狀態的定義。

在本例中,咱們有四個狀態,因此須要定義四個狀態類StateZero, StateOne, StateTwo, StateThree。

每個狀態類的處理邏輯都是相似的,基本的業務處理邏輯爲:

接受一個字符,而後判斷是否進行狀態轉移。

因此咱們能夠抽象一個狀態基類 State,在基類中能夠定義一個共同的抽象方法,以下:

public enum State {
    abstract void read(StateController controller, CharacterReader reader);
}

 

這裏面咱們使用是枚舉類型來定義基類 State,而且定義了一個抽象方法 read,該方法有兩個參數:

第一個是狀態控制邏輯controller,經過 controller咱們能夠快速的轉移狀態;

第二個是輸入字符串的一個封裝,經過consume方法能夠快速的獲得當前字符。

接下來咱們介紹 StateZero 的實現:

StateZero {
    @Override
    void read(StateController controller, CharacterReader reader) {

        char ch = reader.consume();

        switch (ch) {

            case 'a':
                controller.transition(StateOne);
                break;

            default:
                controller.transition(StateThree);
                break;

        }
    }
}

上述代碼很是容易理解,根據第一節的狀態轉換圖,咱們知道,在狀態0的時候,當輸入字符'a'就轉移到狀態1,其餘字符則轉移到狀態3。因此你們看到上述代碼很是的簡單易懂。

StateOne和 StateZero 相似,再也不贅述。接下來介紹終止狀態的實現。

StateTwo {
    @Override
    void read(StateController controller, CharacterReader reader) {

        controller.setTerminated(true);
        System.out.println("Match!");
    }
},
StateThree {
    @Override
    void read(StateController controller, CharacterReader reader) {

        controller.setTerminated(true);
        System.out.println("UnMatch!");
    }
};

 

狀態2和3都是終止狀態,再也不接受任何的輸入,所以直接將 controller 的 terminated 設置爲true 便可。

(3)狀態控制邏輯。

這個任務是咱們狀態機實現的核心部分,也是最精彩的部分。咱們經過定義一個狀態控制邏輯類 StateController 來控制全部的流程。

public class StateController {

    private CharacterReader reader;
    private State state = State.StateZero;
    private boolean isTerminated = false;

    public StateController(CharacterReader reader) {
        this.reader = reader;
    }

    // 將當前狀態轉移到新的狀態
    public void transition(State newState){

        this.state = newState;
    }

    public void run() {

        while (!isTerminated) {
            state.read(this, reader);
        }
    }

    public void setTerminated(boolean terminated) {
        isTerminated = terminated;
    }
}

state屬性持有當前狀態機的狀態,初始狀態爲 StateZero,isTerminated 屬性表示當前狀態機是否已經進入終止狀態。

核心代碼是:

while (!isTerminated) {
      state.read(this, reader);

}

不停的檢測狀態機當前的狀態是否處於終止狀態,若是不是則不停的從 reader 接受輸入字符,進入狀態機。

最後是測試代碼了:

public class StateTest {
    public static void main(String[] args) {

        CharacterReader reader = new CharacterReader("abbba");

        StateController controller = new StateController(reader);
        controller.run();
    }
}

綜上,已經完成了一個狀態機的設計,簡單總結就是三個:輸入控制,各類狀態類定義,狀態控制邏輯。

3 總結

本文經過一個簡單的案例向你們介紹了狀態機的基本知識,並給出了設計狀態機的基本思路和相關代碼,爲後續講解基於狀態機的DOM 樹的生成打好基礎。

本文全部代碼可在如下 git 庫中 day01模塊中找到,git 地址爲:

https://gitee.com/gschen/sctu-treebuilder.git

歡迎持續關注「算法與編程之美」微信公衆號,瞭解更多。

相關文章
相關標籤/搜索