<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      ArrayDeque雙端隊(duì)列--底層原理可視化

      主要學(xué)習(xí)雙端隊(duì)列 ArrayDeque ,通過(guò)對(duì)其棧功能的使用,掌握循環(huán)數(shù)組底層原理

      覺(jué)得文章枯燥的可以結(jié)合ArrayDeque 底層原理可視化視頻:https://www.bilibili.com/video/BV1zChGz8EVL/

      有環(huán)形的數(shù)組?同時(shí)具備棧功能隊(duì)列功能

      1. Java 中的棧實(shí)現(xiàn)

      在Java中,棧類你可以直接找到的是Stack類。Stack類實(shí)在JDK 1.0 的時(shí)候就有了,但你會(huì)發(fā)現(xiàn)在Stack類頭注釋寫著:建議優(yōu)先使用Deque接口及其實(shí)現(xiàn)類,例如:ArrayDeque

      1.1. Stack

      Stack 繼承自 Vector<E>,線程安全,但因每次操作都要加鎖,性能較差。Vector 集合基本也不會(huì)用到的。

      示例:

      Stack<Integer> stack = new Stack<>();
      stack.push(1);
      int top = stack.peek();
      int popped = stack.pop();
      

      1.2. LinkedList

      LinkedList實(shí)現(xiàn)了Deque雙端隊(duì)列接口,具備了隊(duì)列功能和棧功能,也就是說(shuō)LinkedList 可以當(dāng)做普通List集合來(lái)用,同時(shí)還可以當(dāng)做棧或隊(duì)列來(lái)使用。

      以下是通過(guò)LinkedList 來(lái)實(shí)現(xiàn)入棧、出棧操作:

      LinkedList<String> linkedList = new LinkedList<>();
      // 入棧
      linkedList.push("淵");
      linkedList.push("渟");
      linkedList.push("岳");
      linkedList.push("Why?");
      System.out.println(linkedList); // [Why?, 岳, 渟, 淵]
      // 獲取棧頂元素
      String peek = linkedList.peek();//不會(huì)出棧
      System.out.println(peek); // Why?
      // 出棧
      String pop = linkedList.pop();//出棧一個(gè)
      System.out.println(pop);// Why?
      linkedList.pop();   //再出棧一個(gè),不獲取結(jié)果
      System.out.println(linkedList);// 剩下的元素:[渟, 淵]
      

      使用雙端隊(duì)列功能時(shí),如果你想將引用改為接口名
      ?這樣寫是錯(cuò)的:List<String> linkedList = new LinkedList<>();
      ?得這樣寫才行:Deque<String> linkedList = new LinkedList<>();

      1.3. ArrayDeque

      Deque接口為雙端隊(duì)列接口,ArrayDeque實(shí)現(xiàn)了該接口。

      ArrayDeque 看命名就知道是雙端隊(duì)列,并且底層數(shù)據(jù)結(jié)構(gòu)為數(shù)組。ArrayDeque除了具有隊(duì)列(FIFO)功能,同時(shí)它還具備棧(LIFO)功能,所以它可以當(dāng)做棧來(lái)使用。

      棧功能示例:

      Deque<Integer> deque = new ArrayDeque<>();
      deque.push(1);
      deque.push(2);
      deque.push(3);
      System.out.println(deque); // [3, 2, 1]
      int top = deque.peek();
      System.out.println(top); // 3
      int popped = deque.pop();
      System.out.println(popped); // 3
      System.out.println(deque); // [2, 1]
      

      LinkedList在集合學(xué)習(xí)時(shí)已經(jīng)學(xué)過(guò)了,它的雙端隊(duì)列功能并不會(huì)影響底層數(shù)據(jù)結(jié)構(gòu),僅僅是操作邏輯不同而已。

      在雙端隊(duì)列功能上,LinkedList沒(méi)有ArrayDeque性能高,通常使用ArrayDeque更多,所以我們?cè)敿?xì)來(lái)學(xué)學(xué)ArrayDeque有什么獨(dú)特之處?

      2. ArrayDeque底層原理

      在Java中,可以通過(guò)雙端隊(duì)列Deque的實(shí)現(xiàn)類來(lái)實(shí)現(xiàn)棧功能,常用的有兩個(gè):ArrayDequeLinkedList

      兩個(gè)類繼承與實(shí)現(xiàn):

      // ArrayDeque
      public class ArrayDeque<E> extends AbstractCollection<E>
                                 implements Deque<E>, Cloneable, Serializable
      // LinkedList
      public class LinkedList<E>
          extends AbstractSequentialList<E>
          implements List<E>, Deque<E>, Cloneable, java.io.Serializable
      

      LinkedList 實(shí)現(xiàn)類既是List集合、同時(shí)又是Deque雙端隊(duì)列,也就是說(shuō),這個(gè)類具備多種功能:鏈?zhǔn)郊稀㈡準(zhǔn)疥?duì)列和鏈?zhǔn)綏HN功能。

      ArrayDeque 實(shí)現(xiàn)類具備兩種功能:隊(duì)列和棧。

      上一篇棧的文章也講述過(guò)使用單鏈表實(shí)現(xiàn)自定義棧,并使用自定義棧完成了有效括號(hào)匹配實(shí)戰(zhàn),在此,主要完成ArrayDeque棧功能的學(xué)習(xí)。

      2.1. ArrayDeque的數(shù)據(jù)結(jié)構(gòu)

      看命名就知道是雙端隊(duì)列,并且底層數(shù)據(jù)結(jié)構(gòu)為數(shù)組ArrayDeque類主要字段如下:

      public class ArrayDeque<E> extends AbstractCollection<E>
                                 implements Deque<E>, Cloneable, Serializable{
          // 數(shù)據(jù)就保存在這個(gè)數(shù)組,大小總是2的冪
          transient Object[] elements;
      	// 頭索引
          transient int head;
      	// 尾索引
          transient int tail;
          // 允許最小容量
          private static final int MIN_INITIAL_CAPACITY = 8;
      
      	...
      }
      

      特別說(shuō)明下MIN_INITIAL_CAPACITY=8,這個(gè)最小容量是在你指定ArrayDeque大小時(shí)才會(huì)用到。比如你指定大小為7,那它創(chuàng)建出來(lái)的大小為8,它的計(jì)算邏輯和HashMap的一模一樣。ArrayDeque的默認(rèn)大小為16。相關(guān)代碼如下:

      // 默認(rèn)構(gòu)造方法
      public ArrayDeque() {
      	elements = new Object[16];
      }
      
      // 指定大小構(gòu)造方法
      public ArrayDeque(int numElements) {
      	allocateElements(numElements);
      }
      
      private void allocateElements(int numElements) {
      	elements = new Object[calculateSize(numElements)];
      }
      
      // 計(jì)算結(jié)果為2的冪次方,跟HashMap的計(jì)算邏輯一樣
      private static int calculateSize(int numElements) {
      	int initialCapacity = MIN_INITIAL_CAPACITY;
      	
      	if (numElements >= initialCapacity) {
      		initialCapacity = numElements;
      		initialCapacity |= (initialCapacity >>>  1);
      		initialCapacity |= (initialCapacity >>>  2);
      		initialCapacity |= (initialCapacity >>>  4);
      		initialCapacity |= (initialCapacity >>>  8);
      		initialCapacity |= (initialCapacity >>> 16);
      		initialCapacity++;
      
      		if (initialCapacity < 0)
      			initialCapacity >>>= 1;
      	}
      	return initialCapacity;
      }
      

      2.2. 為什么大小設(shè)置為2的冪次方?

      如果學(xué)過(guò)HashMap底層實(shí)現(xiàn)邏輯,那就非常容易理解,之前的HashMap文章還專門講了這個(gè)。

      它是HashMap的哈希值映射數(shù)組下標(biāo)和ArrayDeque循環(huán)數(shù)組得以實(shí)現(xiàn)的基石。

      使得通過(guò)與(&)運(yùn)算高效完成數(shù)組下標(biāo)映射,非常方便哈希值映射計(jì)算和循環(huán)索引計(jì)算。為得就是方便計(jì)算元素索引位置、提高計(jì)算效率,特別是擴(kuò)容后需要做的調(diào)整,也變得簡(jiǎn)單高效。

      通過(guò)添加元素(入棧)、動(dòng)態(tài)擴(kuò)容和移除元素(出棧)這些操作感受與(&)運(yùn)算的巧妙之處。

      2.3. 添加元素--入棧

      從添加元素開(kāi)始,直到元素達(dá)到閾值后觸發(fā)動(dòng)態(tài)擴(kuò)容,再接著學(xué)習(xí)動(dòng)態(tài)擴(kuò)容。

      元素入棧操作:

      Deque<Integer> stack = new ArrayDeque<>();
      stack.push(1);  // 相當(dāng)于 addFirst
      stack.push(2);
      stack.push(3);
      stack.push(4);
      stack.push(5);
      stack.push(6);
      stack.push(7);
      stack.push(8);
      System.out.println(stack);
      

      執(zhí)行結(jié)果:

      [8, 7, 6, 5, 4, 3, 2, 1]

      結(jié)果怎么反過(guò)來(lái)的??

      我們看看源碼怎么寫的

      public void push(E e) {
      	addFirst(e);
      }
      
      public void addFirst(E e) {
      	// 不允許存放 null 元素
      	if (e == null)
      		throw new NullPointerException();
      	// 入棧元素位置計(jì)算
      	elements[head = (head - 1) & (elements.length - 1)] = e;
      	if (head == tail)
      		doubleCapacity();
      }
      

      計(jì)算新的 head 索引并插入元素

      elements[ head = (head - 1) & (elements.length - 1) ] = e;
      

      headint數(shù)據(jù)類型,成員變量默認(rèn)為:0;

      head - 1:在當(dāng)前 head 之前的那個(gè)槽位,也就是“往左移一格”,第一次插入時(shí)為:-1;

      & (elements.length - 1)取模運(yùn)算,但因?yàn)?elements.length 總是 2 的冪,這里用位運(yùn)算更高效;

      head = …:先更新 head 成新位置,再把 e 存入 elements[head];

      這樣無(wú)論 head 從 0 跑到 -1,按位與都會(huì)自動(dòng)“回繞”到數(shù)組末尾,實(shí)現(xiàn)循環(huán)緩沖

      說(shuō)那么多也沒(méi)啥印象,來(lái)個(gè)計(jì)算過(guò)程圖示:

      ArrayDeque循環(huán)數(shù)組計(jì)算過(guò)程

      所以,push入棧是從數(shù)組的末端開(kāi)始,往回入棧的,ArrayDeque的數(shù)據(jù)結(jié)構(gòu)為循環(huán)數(shù)組

      循環(huán)數(shù)組數(shù)據(jù)結(jié)構(gòu)如圖:

      ArrayDeque循環(huán)數(shù)組

      2.4. 動(dòng)態(tài)擴(kuò)容

      棧和隊(duì)列就像List集合那樣,使用時(shí)可能并不會(huì)知道集合大小為多少,所以ArrayDeque需要像ArrayList一樣需要?jiǎng)討B(tài)擴(kuò)容。

      ArrayDeque的動(dòng)態(tài)擴(kuò)容像HashMap一樣,擴(kuò)容時(shí)候?yàn)?的倍數(shù),確保大小一直為2的冪次方。

      動(dòng)態(tài)擴(kuò)容只會(huì)在元素入棧的時(shí)候才會(huì)觸發(fā)addFirst觸發(fā)擴(kuò)容條件的源碼

      if (head == tail)
      	doubleCapacity();
      

      動(dòng)態(tài)擴(kuò)容關(guān)鍵源碼為

      private void doubleCapacity() {
      	assert head == tail;
      	int p = head;
      	// 數(shù)組大小
      	int n = elements.length;
      	// p右邊元素的個(gè)數(shù)
      	int r = n - p;
      	// 容量翻倍
      	int newCapacity = n << 1;
      	if (newCapacity < 0)
      		throw new IllegalStateException("Sorry, deque too big");
      	// 創(chuàng)建新數(shù)組
      	Object[] a = new Object[newCapacity];
      	// 元素第一次拷貝
      	System.arraycopy(elements, p, a, 0, r);
      	// 元素第二次拷貝
      	System.arraycopy(elements, 0, a, r, p);
      	// 更新數(shù)組為新對(duì)象
      	elements = a;
      	// 重置頭尾索引下標(biāo)
      	head = 0;
      	tail = n;
      }
      

      重點(diǎn)在于,為什么需要拷貝兩次?

      /*
       * src     源數(shù)組
       * srcPos  源數(shù)組的起始位置
       * dest    目標(biāo)數(shù)組
       * destPos 目標(biāo)數(shù)據(jù)的起始位置
       * length  需要復(fù)制數(shù)組的長(zhǎng)度
       */
      // 元素第一次拷貝
      System.arraycopy(elements, p, a, 0, r);
      // 元素第二次拷貝
      System.arraycopy(elements, 0, a, r, p);
      

      ArrayDeque使用的是雙端隊(duì)列,是一種循環(huán)數(shù)組,頭尾看做是相連的,做兩次拷貝的目的是:確保新數(shù)組中的元素保持原來(lái)入棧的順序。具體怎么個(gè)情況,繼續(xù)往下看,可視化一步步的講個(gè)明白。

      2.5. 徹底搞懂循環(huán)數(shù)組

      舉個(gè)例子:指定ArrayDeque的大小為8。先入棧1、2、3、4、5、6、7、8元素;再入棧A、B、C、D、E、F、G、H 元素。

      源碼如下:

      public class ArrayDequeStudy {
          public static void main(String[] args) {
              Deque<String> stack = new ArrayDeque<>(6);// 不能等于8,等于8初始大小會(huì)變?yōu)?6
              stack.push("1");
              stack.push("2");
              stack.push("3");
              stack.push("4");
              stack.push("5");
              stack.push("6");
              stack.push("7");
              stack.push("8");
              stack.push("A");
              stack.push("B");
              stack.push("C");
              stack.push("D");
              stack.push("E");
              stack.push("F");
              stack.push("G");
              stack.push("H");
              System.out.println(stack); // [H, G, F, E, D, C, B, A, 8, 7, 6, 5, 4, 3, 2, 1]
              String top = stack.peek();
              System.out.println(top);   // H
              String pop = stack.pop();
              System.out.println(pop);   // H
              System.out.println(stack.pop());// G
          }
      }
      

      通過(guò)圖形顯示處理過(guò)程就很好理解了。

      1. 第一次擴(kuò)容的可視化

      第一次擴(kuò)容很好理解,只需執(zhí)行一次元素拷貝,第二次的拷貝是空拷貝System.arraycopy(elements, 0, a, 8, 0)

      ArrayDeque第一次擴(kuò)容

      2. 擴(kuò)容完成后,繼續(xù)插入元素(重點(diǎn)):

      在這里開(kāi)始會(huì)出現(xiàn)環(huán)繞的插入,就是數(shù)組中的元素拆分成了兩段

      ArrayDeque環(huán)繞插入

      也許這樣更好理解點(diǎn),在邏輯處理上ArrayDeque的數(shù)據(jù)結(jié)構(gòu)是長(zhǎng)這樣的↓。

      push 一個(gè)元素head逆時(shí)針走動(dòng)一格,寫入元素即可。

      ArrayDeque環(huán)形數(shù)組插入

      3. 第二次擴(kuò)容的可視化

      出現(xiàn)兩端環(huán)繞的情況時(shí),兩次拷貝是必不可少的,第一次拷貝的是head索引位置的后半段,第二次拷貝的是0至head的前半段,也就是剩下的那部分。不管是ArrayDeque的棧功能操作,還是雙端隊(duì)列操作,它們都會(huì)形成環(huán)繞形態(tài)的數(shù)組,需要進(jìn)行兩次拷貝,才能確保棧LIFO和隊(duì)列FIFO元素的順序正確

      ArrayDeque第二次擴(kuò)容

      2.6. 移除元素--出棧

      出棧操作pop就是將head索引下的元素取出,將head右移一位。

      主要源碼如下:

      public E pollFirst() {
      	int h = head;
      	// 取出頭元素
      	E result = (E) elements[h];
      	if (result == null)
      		return null;
      	// 清空對(duì)應(yīng)數(shù)組
      	elements[h] = null;
      	// 將head右移一位
      	head = (h + 1) & (elements.length - 1);
      	return result;
      }
      

      整個(gè)操作過(guò)程非常高效,關(guān)鍵源碼還是head 右移一位。

      比如:出棧元素D的過(guò)程

      ArrayDeque循環(huán)數(shù)組--移除元素1

      ArrayDeque是循環(huán)數(shù)組,在常規(guī)橫向的數(shù)組結(jié)構(gòu)上面展示并不直觀。

      在首尾相連的圓形數(shù)組上,右移一位就像是在圓形的數(shù)組上順時(shí)針走動(dòng)一格,沒(méi)有首尾隔斷的感覺(jué)。

      這樣看起來(lái)可能更符合循環(huán)數(shù)組的處理邏輯,出棧操作

      ArrayDeque循環(huán)數(shù)組--移除元素2

      3. 總結(jié)

      ArrayDeque 是基于循環(huán)數(shù)組的雙端隊(duì)列實(shí)現(xiàn),既可用作隊(duì)列(FIFO)也可用作棧(LIFO)。通過(guò)兩個(gè)索引 headtail 和位運(yùn)算自動(dòng)在固定大小(2 的冪)的底層數(shù)組中“回繞”操作,當(dāng)空間用盡時(shí)再將數(shù)組容量翻倍并平鋪原有元素,所有入、刪、取操作均為攤銷 O(1),不支持存放 null 且非線程安全,但因無(wú)額外節(jié)點(diǎn)指針、緩存友好,通常比鏈表結(jié)構(gòu)性能更優(yōu)。

      通過(guò)這篇文章,從棧功能使用到底層原理基本掌握,對(duì)ArrayDeque隊(duì)列操作功能感興趣的可以自行學(xué)習(xí),底層和棧功能是共用的,相信你很快便可掌握雙端隊(duì)列。

      文章配套的視頻也同步更新:https://www.bilibili.com/video/BV1zChGz8EVL/

      想要可視化對(duì)比ArrayDeque的棧功能和隊(duì)列功能,可到視頻最后部分查看。

      往期推薦

      分類 往期文章
      Java集合底層原理可視化 “子彈彈夾”裝彈和出彈的抽象原理實(shí)戰(zhàn):掌握棧的原理與實(shí)戰(zhàn)
      TreeMap集合--底層原理、源碼閱讀及它在Java集合框架中扮演什么角色?
      LinkedHashMap集合--原理可視化
      HashMap集合--基本操作流程的源碼可視化
      Java集合--HashMap底層原理可視化,秒懂?dāng)U容、鏈化、樹化
      Java集合--從本質(zhì)出發(fā)理解HashMap
      Java集合--LinkedList源碼可視化
      Java集合源碼--ArrayList的可視化操作過(guò)程
      設(shè)計(jì)模式秘籍
      (已全部開(kāi)源)
      掌握設(shè)計(jì)模式的兩個(gè)秘籍
      往期設(shè)計(jì)模式文章的:設(shè)計(jì)模式
      軟件設(shè)計(jì)師 軟考中級(jí)--軟件設(shè)計(jì)師毫無(wú)保留的備考分享
      通過(guò)軟考后卻領(lǐng)取不到實(shí)體證書?
      2023年下半年軟考考試重磅消息
      Java學(xué)習(xí)路線
      和相應(yīng)資源
      Java全棧學(xué)習(xí)路線、學(xué)習(xí)資源和面試題一條龍

      原創(chuàng)不易,覺(jué)得還不錯(cuò)的,三連支持:點(diǎn)贊、分享、推薦↓

      posted @ 2025-08-04 08:34  淵渟岳  閱讀(290)  評(píng)論(0)    收藏  舉報(bào)
      主站蜘蛛池模板: 九色国产精品一区二区久久| 女人被狂躁c到高潮| 国内精品无码一区二区三区| 国产毛片三区二区一区| 中文字幕人妻不卡精品| 2019香蕉在线观看直播视频| 亚洲欧洲日产国无高清码图片| 最新亚洲春色av无码专区| 视频一区二区不中文字幕| 亚洲欧洲一区二区精品| 一区二区三区放荡人妻| 中文字幕久久国产精品| 国产麻豆成人传媒免费观看| 亚洲综合久久精品国产高清| 99国产欧美久久久精品蜜芽| 色综合视频一区二区三区| 真人在线射美女视频在线观看| 99久久国产精品无码| 2021亚洲国产精品无码| 日本午夜精品一区二区三区电影| 中文字幕精品亚洲字幕成| 精品一区二区三人妻视频| 国产精品自在自线视频| 国产在线乱子伦一区二区| 日本肉体xxxx裸交| 精品久久久久国产免费| 在线观看人成视频免费| 少妇高潮水多太爽了动态图| 亚洲精品一区二区制服| 亚洲一区二区三区蜜桃臀| 日本一卡2卡3卡四卡精品网站| 日韩一区二区三区精品区| 国产欧美日韩亚洲一区二区三区| 久久天天躁狠狠躁夜夜躁| 国产欧美综合在线观看第十页| 久久月本道色综合久久| 亚洲欧美高清在线精品一区二区 | 偷拍专区一区二区三区| 亚洲国产成人综合自在线| 南木林县| 激情综合网激情国产av|