本文首發於微信公衆號:"算法與編程之美",歡迎關注,及時瞭解更多此係列博客。html
DOM(Document Object Model)即文檔對象模型,是一種很是重要的數據結構,用途很是普遍。git
對於瀏覽器的渲染引擎來講,須要將html 字符串轉換成 DOM 樹,再轉換成渲染樹,最後才進行渲染。正則表達式
對於數據採集來講,常常須要作的是解析已經下載的 html 文檔,而這種解析工做的前提是要生成 html 文檔的 DOM 樹,而後才能解析。算法
本系列博客將爲你們介紹一種基於狀態機的 DOM樹生成技術,從最初的 html 文檔如何一步步生成最終的 DOM 樹。編程
本講咱們將爲你們介紹狀態機的基本知識。瀏覽器
圖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等。
狀態機的用途很是的普遍,不只在正則表達式,編譯器等衆多領域發揮重要做用。
有了上述的狀態機基本知識後,本節將爲你們介紹如何用 Java 語言實現狀態機。
要想實現上述狀態機,咱們有如下三個任務須要完成:
咱們須要定義一個類可以實現很是方便的控制整個輸入的字符串,每次移動一個字符。
這個類很是的簡單,以下所示:
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");
在本例中,咱們有四個狀態,因此須要定義四個狀態類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 便可。
這個任務是咱們狀態機實現的核心部分,也是最精彩的部分。咱們經過定義一個狀態控制邏輯類 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(); } }
綜上,已經完成了一個狀態機的設計,簡單總結就是三個:輸入控制,各類狀態類定義,狀態控制邏輯。
本文經過一個簡單的案例向你們介紹了狀態機的基本知識,並給出了設計狀態機的基本思路和相關代碼,爲後續講解基於狀態機的DOM 樹的生成打好基礎。
本文全部代碼可在如下 git 庫中 day01模塊中找到,git 地址爲:
https://gitee.com/gschen/sctu-treebuilder.git
歡迎持續關注「算法與編程之美」微信公衆號,瞭解更多。