“棧”與“佇列”呢點事(一)

弒曉風發表於2019-02-23
因為棧和佇列放在一起學習更容易理解,因為本文將化繁為簡,儘量通俗易懂的解釋棧與佇列。
化腐朽為神奇???不,好像不能。

定義

棧是一種操作受限的線性結構。舉個比較典型的例子,疊一摞盤子。放盤子的時候,都是從下往上一個一個放;取的時候,也是從上往下一個一個地依次取,不能從中間任意抽出。這就是一種典型的‘棧’結構。它的思想就是先進後出,後進先出

注意:棧的插入和刪除操作只允許棧頂(也就是末尾)進行。

棧又分順序棧鏈式棧。基於陣列實現的棧叫做順序棧,基於連結串列實現的則叫鏈式棧。

棧的應用其實很廣泛。比如表示式的轉換和求值,函式呼叫,逆序輸出,語法的檢查(“()”“[]”“{}”“<>”這些成對出現的符號是否完整)回溯,遞迴,深度優先搜尋等,業務上比如瀏覽器的前進後退。

棧的基本操作:

  • 入棧(push):將資料放入棧頂,棧頂top指標加一。
  • 出棧(pop):將棧頂資料資料輸出,棧頂資料減一。

佇列

佇列也是一種操作受限的線性結構。它的思想也正好和棧相反--先進先出,後進後出。舉個例子,排隊買票,先來的先買,後來的人只能站末尾,並且不允許插隊。佇列的概念相對棧來講,更好理解。

注意:佇列它是隻允許在首端刪除,尾端插入。

佇列分為順序佇列鏈式佇列,迴圈佇列,阻塞佇列,併發佇列。同樣,用陣列實現的佇列叫作順序佇列,用連結串列實現的佇列叫作鏈式佇列。迴圈佇列則是一個環狀。至於阻塞佇列它其實就是在普通佇列的基礎上增加了阻塞的操作(生產者-消費者模型)。併發佇列是基於執行緒安全為大前提的,也就是每一次的入隊出隊操作都加了鎖(利用 CAS 原子操作,可以實現非常高效的併發佇列)。

佇列的應用也更加的廣泛。比如迴圈佇列、阻塞佇列、併發佇列。它們在很多偏底層系統、框架、中介軟體的開發中,起著關鍵性的作用。比如高效能佇列 Disruptor、Linux 環形快取,都用到了迴圈併發佇列;Java concurrent 併發包利用 ArrayBlockingQueue 來實現公平鎖;訊息佇列(activeMQ、rabbitMQ、rocketMQ、zeroMQ,kafka等等)就更不用提了,還有執行緒池等等。

佇列的基本操作

  • 入隊(enqueue):將資料放入尾端,資料大小加一。
  • 出隊(dequeue):將資料從首端取出,資料大小減一。

思考

關於堆疊

一般說棧的時候,也會提一下堆,但是要注意記憶體中的堆疊和資料結構堆疊不是一個概念,可以說記憶體中的堆疊是真實存在的物理區,資料結構中的堆疊是抽象的資料儲存結構。

記憶體空間在邏輯上分為三部分:

程式碼區:儲存方法體的二進位制程式碼。高階排程(作業排程)、中級排程(記憶體排程)、低階排程(程式排程)控制程式碼區執行程式碼的切換。

靜態資料區:儲存全域性變數、靜態變數、常量,常量包括final修飾的常量和String常量。系統自動分配和回收。

和動態資料區:

動態資料區又分為棧區和堆區。

棧區:儲存執行方法的形參、區域性變數、返回值。由系統自動分配和回收。
堆區:new一個物件的引用或地址儲存在棧區,指向該物件儲存在堆區中的真實資料。

關於佇列

使用佇列的時候要注意

  • 佇列為空
  • 佇列只有一個元素,即頭尾指標都指向空
  • 初始化佇列時,分配空間後不要忘記將頭為指標置空

佇列應用於有限的資源池的時候,用於排隊請求,比如資料庫連線池,執行緒池等。實際上,對於大部分資源有限的場景,當沒有空閒資源時,基本上都可以通過“佇列”這種資料結構來實現請求排隊。

淺談一下訊息佇列

有這麼一種場景:小王到M記點餐之後,服務員給了他一個號牌,並讓他在櫃檯桌子前方等待叫號取餐。每個人都按照自己付款拿到的號牌順序排隊等叫號。即使店裡人再多,也不會顯得沒有秩序。

在上述場景中,櫃檯其實就充當了一個訊息佇列(Message Queue)。小王等生產者把訂餐的訊息傳送到櫃檯即訊息佇列裡,又從其中取了餐即消費了訊息,可以說這就是訊息佇列的一個完整走向——訊息被髮送到佇列中,又成功被消費者消費。“訊息佇列”是在訊息的傳輸過程中儲存訊息的容器,佇列的主要目的是提供路由並保證訊息的傳遞。如果傳送訊息時接收者不可用,訊息佇列會保留訊息,直到可以成功地傳遞它。

一般來說,訊息佇列是一種非同步的服務間通訊方式,是分散式系統中重要的元件,主要解決應用耦合,非同步訊息,流量削鋒等問題,實現高效能,高可用,可伸縮和最終一致性架構。

擴充套件

下一篇則關於棧的具體實現和解析。

end

您的點贊和關注是對我最大的支援,謝謝!


相關文章