【從今天開始好好學數據結構02】棧與隊列

目錄

我們今天主要來談談“棧”以及隊列這兩種數據結構。

回顧一下上一章中中,在數組中只要知道數據的下標,便可通過順序搜索很快查詢到數據,可以根據下標不同自由查找,然而今天要講的“棧”以及隊列這兩種數據結構訪問是受限制的,只允許在一端讀取、插入和刪除數據,這時候對它存在的意義產生了很大的疑惑。因為會覺得,相比數組和鏈表,棧帶給我的只有限制,並沒有任何優勢。那我直接使用數組或者鏈表不就好了嗎?為什麼還要用這個“操作受限”的“棧”呢?事實上,從功能上來說,數組或鏈表確實可以替代棧,但你要知道,特定的數據結構是對特定場景的抽象,而且,數組或鏈表暴露了太多的操作接口,操作上的確靈活自由,但使用時就比較不可控,自然也就更容易出錯。

@

1、理解棧與隊列

首先,如何理解“棧”?用現實一個通俗貼切的例子,我們平時放盤子的時候,都是從下往上一個一個放;取的時候,我們是從上往下一個一個地依次取,不能從中間任意抽出,先進后出,這就是典型的“棧”結構,當某個數據集合只涉及在一端插入和刪除數據,並且滿足後進先出、先進后出的特性,我們就應該首選“棧”這種數據結構。

其次如何理解隊列?同樣用現實一個通俗貼切的例子,平時在校的時候飯堂吃飯都是排隊,而且不能插隊,先進先出,這就是典型的隊列結構

2、用代碼談談棧

實際上,棧既可以用數組來實現,也可以用鏈表來實現。用數組實現的棧,我們叫作順序棧,用鏈表實現的棧,我們叫作鏈式棧。不管是順序棧還是鏈式棧,我們存儲數據只需要一個大小為n的數組就夠了。在入棧和出棧過程中,只需要一兩個臨時變量存儲空間,所以空間複雜度是O(1)。

注意,這裏存儲數據需要一個大小為n的數組,並不是說空間複雜度就是O(n)。因為,這n個空間是必須的,無法省掉。所以我們說空間複雜度的時候,是指除了原本的數據存儲空間外,算法運行還需要額外的存儲空間。

空間複雜度分析是不是很簡單?時間複雜度也不難。不管是順序棧還是鏈式棧,入棧、出棧只涉及棧頂個別數據的操作,所以時間複雜度都是O(1)。

還有一點,JVM內存管理中有個“堆棧”的概念。棧內存用來存儲局部變量和方法調用,堆內存用來存儲Java中的對象。那JVM裏面的“棧”跟我們這裏說的“棧”是不是一回事呢?如果不是,那它為什麼又叫作“棧”呢?知道的大牛請自覺評論區見面~

2.1、用數組實現的棧:順序棧

public class MyStack {
    
    //棧的底層我們使用數組來存儲數據
    int[] elements;

    public MyStack() {
        elements = new int[0];
    }
    
    //壓入元素
    public void push(int element) {
        // 創建一個新的數組
        int[] newArr = new int[elements.length + 1];
        // 把原數組中的元素複製到新數組中
        for (int i = 0; i < elements.length; i++) {
            newArr[i] = elements[i];
        }
        // 把添加的元素放入新數組中
        newArr[elements.length] = element;
        // 使用新數組替換舊數組
        elements = newArr;
    }
    
    //取出棧頂元素
    public int pop() {
        //棧中沒有元素
        if(elements.length==0) {
            throw new RuntimeException("stack is empty");
        }
        //取出數組的最後一個元素
        int element = elements[elements.length-1];
        //創建一個新的數組
        int[] newArr = new int[elements.length-1];
        //原數組中除了最後一個元素的其它元素都放入新的數組中
        for(int i=0;i<elements.length-1;i++) {
            newArr[i]=elements[i];
        }
        //替換數組
        elements=newArr;
        //返回棧頂元素
        return element;
    }
    
    //查看棧頂元素
    public int peek() {
        //棧中沒有元素
        if(elements.length==0) {
            throw new RuntimeException("stack is empty");
        }
        return elements[elements.length-1];
    }
    
    //判斷棧是否為空
    public boolean isEmpty() {
        return elements.length==0;
    }
    
}

2.2、測試數組實現的棧

import demo2.MyStack;

public class TestMyStack {

    public static void main(String[] args) {
        //創建一個棧
        MyStack ms = new MyStack();
        //壓入數組
        ms.push(9);
        ms.push(8);
        ms.push(7);
        //最出棧頂元素
        System.out.println(ms.pop());
        System.out.println(ms.pop());
        System.out.println(ms.pop());
        //查看棧頂元素
//      System.out.println(ms.peek());
        System.out.println(ms.isEmpty());
    }

}

2.3、基於鏈表實現的棧:鏈式棧

package stack;
/**
 * 基於鏈表實現的棧。
 */
public class StackBasedOnLinkedList {
  private Node top = null;

  public void push(int value) {
    Node newNode = new Node(value, null);
    // 判斷是否棧空
    if (top == null) {
      top = newNode;
    } else {
      newNode.next = top;
      top = newNode;
    }
  }

  /**
   * 我用-1表示棧中沒有數據。
   */
  public int pop() {
    if (top == null) return -1;
    int value = top.data;
    top = top.next;
    return value;
  }

  public void printAll() {
    Node p = top;
    while (p != null) {
      System.out.print(p.data + " ");
      p = p.next;
    }
    System.out.println();
  }

  private static class Node {
    private int data;
    private Node next;

    public Node(int data, Node next) {
      this.data = data;
      this.next = next;
    }

    public int getData() {
      return data;
    }
  }
}

3、用代碼談談隊列

棧只支持兩個基本操作:入棧push()和出棧pop()。隊列跟棧非常相似,支持的操作也很有限,最基本的操作也是兩個:入隊enqueue(),放一個數據到隊列尾部;出隊dequeue(),從隊列頭部取一個元素。所以,隊列跟棧一樣,也是一種操作受限的線性表數據結構。隊列的概念很好理解,基本操作也很容易掌握。作為一種非常基礎的數據結構,隊列的應用也非常廣泛,特別是一些具有某些額外特性的隊列,比如循環隊列、阻塞隊列、併發隊列。它們在很多偏底層系統、框架、中間件的開發中,起着關鍵性的作用。比如高性能隊列Disruptor、Linux環形緩存,都用到了循環併發隊列;Java concurrent併發包利用ArrayBlockingQueue來實現公平鎖等。

跟棧一樣,隊列可以用數組來實現,也可以用鏈表來實現。用數組實現的棧叫作順序棧,用鏈表實現的棧叫作鏈式棧。同樣,用數組實現的隊列叫作順序隊列,用鏈表實現的隊列叫作鏈式隊列

3.1、數組實現隊列:順序隊列

package queue;

// 用數組實現的隊列
public class ArrayQueue {
  // 數組:items,數組大小:n
  private String[] items;
  private int n = 0;
  // head表示隊頭下標,tail表示隊尾下標
  private int head = 0;
  private int tail = 0;

  // 申請一個大小為capacity的數組
  public ArrayQueue(int capacity) {
    items = new String[capacity];
    n = capacity;
  }

  // 入隊
  public boolean enqueue(String item) {
    // 如果tail == n 表示隊列已經滿了
    if (tail == n) return false;
    items[tail] = item;
    ++tail;
    return true;
  }

  // 出隊
  public String dequeue() {
    // 如果head == tail 表示隊列為空
    if (head == tail) return null;
    // 為了讓其他語言的同學看的更加明確,把--操作放到單獨一行來寫了
    String ret = items[head];
    ++head;
    return ret;
  }

  public void printAll() {
    for (int i = head; i < tail; ++i) {
      System.out.print(items[i] + " ");
    }
    System.out.println();
  }
}

3.2、鏈表實現的隊列:鏈式隊列

package queue;

/**
 * 基於鏈表實現的隊列
 */
public class QueueBasedOnLinkedList {

  // 隊列的隊首和隊尾
  private Node head = null;
  private Node tail = null;

  // 入隊
  public void enqueue(String value) {
    if (tail == null) {
      Node newNode = new Node(value, null);
      head = newNode;
      tail = newNode;
    } else {
      tail.next = new Node(value, null);
      tail = tail.next;
    }
  }

  // 出隊
  public String dequeue() {
    if (head == null) return null;

    String value = head.data;
    head = head.next;
    if (head == null) {
      tail = null;
    }
    return value;
  }

  public void printAll() {
    Node p = head;
    while (p != null) {
      System.out.print(p.data + " ");
      p = p.next;
    }
    System.out.println();
  }

  private static class Node {
    private String data;
    private Node next;

    public Node(String data, Node next) {
      this.data = data;
      this.next = next;
    }

    public String getData() {
      return data;
    }
  }

}

3.2、循環隊列

用數組來實現隊列的時候會有數據搬移操作,這樣入隊操作性能就會受到影響。那有沒有辦法能夠避免數據搬移呢?我們來看看循環隊列的解決思路。

循環隊列,顧名思義,它長得像一個環。原本數組是有頭有尾的,是一條直線。現在我們把首尾相連,扳成了一個環。

package queue;
public class CircularQueue {
  // 數組:items,數組大小:n
  private String[] items;
  private int n = 0;
  // head表示隊頭下標,tail表示隊尾下標
  private int head = 0;
  private int tail = 0;

  // 申請一個大小為capacity的數組
  public CircularQueue(int capacity) {
    items = new String[capacity];
    n = capacity;
  }

  // 入隊
  public boolean enqueue(String item) {
    // 隊列滿了
    if ((tail + 1) % n == head) return false;
    items[tail] = item;
    tail = (tail + 1) % n;
    return true;
  }

  // 出隊
  public String dequeue() {
    // 如果head == tail 表示隊列為空
    if (head == tail) return null;
    String ret = items[head];
    head = (head + 1) % n;
    return ret;
  }

  public void printAll() {
    if (0 == n) return;
    for (int i = head; i % n != tail; ++i) {
      System.out.print(items[i] + " ");
    }
    System.out.println();
  }
}

好了,到這裏,總結一下隊列,隊列最大的特點就是先進先出,主要的兩個操作是入隊和出隊。跟棧一樣,它既可以用數組來實現,也可以用鏈表來實現。用數組實現的叫順序隊列,用鏈表實現的叫鏈式隊列。特別是長得像一個環的循環隊列。在數組實現隊列的時候,會有數據搬移操作,要想解決數據搬移的問題,我們就需要像環一樣的循環隊列。

如果本文章對你有幫助,哪怕是一點點,請點個讚唄,謝謝~

歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術…說好了來了就是盆友喔…

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※專營大陸空運台灣貨物推薦

※台灣空運大陸一條龍服務

※評比南投搬家公司費用收費行情懶人包大公開

※南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

特斯拉4月將推出無線充電裝置 10小時充滿Model S

月下旬,特斯拉汽車發佈聲明稱,從今年4月開始,其無線充電裝置“免插充電系統”(Plugless Systems)開始發售。

據特斯拉官方說法,無線充電裝置可用於所有特斯拉品牌車型。該系統包含一個充電板,對接50安培線路,另外一個主要部件則是適配器。該系統需要專業技術人員進行安裝。充電時車輛停在充電板上,無論室內室外都可操作,適用溫度為0至122華氏度(-18至50攝氏度)。

從充電效果看,該系統相當於7.2千瓦二級線圈式充電樁,每充入一小時可以支援電動車續航20英里(32公里)。對特斯拉Model S60而言,充滿電量需要8小時,Model S70需要10小時,Model S85需要12小時。此外,用於四驅D車型的無線充電裝置也很快將上市。

特斯拉尚未公佈該系統的造價,不過參考其他電動車無線充電裝置,日產聆風為1,540美元,雪佛蘭沃藍達為1,260美元,凱迪拉克ELR為1,940美元,因此特斯拉裝置價格可能接近2,000美元。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※專營大陸空運台灣貨物推薦

※台灣空運大陸一條龍服務

※評比南投搬家公司費用收費行情懶人包大公開

※南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

Class文件結構全面解析(上)

什麼是Class文件?

在Java剛剛誕生的時候就提出了一個非常著名的口號:“一次編寫,到處運行。(Write Once,Run Anywhere)”。為了實現平台無關性,各種不同平台的虛擬機都統一使用一種程序儲存格式,就是字節碼(ByteCode)。它就以二進制字節流的方式被存放在Class文件中,其中包含了Java虛擬機指令集和符號表以及其他輔助信息。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

為什麼需要了解Class文件結構?

一般對於數據結構的分享難免比較枯燥,但是了解Class文件結構是了解Java虛擬機的重要基礎之一。如果想比較深入地了解Java虛擬機,那麼Class文件結構是不能不接觸的。我會力求在保證邏輯準確的基礎上,盡量通俗易懂地分享,並結合實際案例。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

Class文件結構簡介

Class文件是一組以8位字節為基礎單位的二進制流,各個數據項目嚴格按照順序準確地排列在Class文件中,中間沒有任何分隔符。當遇到8位字節以上的數據時,就按照高位在前的方式(最高位字節在地址最低位、最低位字節在地址最高位的順序儲存)分割成多個8位字節儲存。

Class文件格式採用一種類似於C語言結構體的偽結構來儲存數據的,這種偽結構有兩種數據類型:無符號數和表。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

無符號數用u1、u2、u4、u8分別代表1個字節、2個字節、4個字節和8個字節的無符號數,可以用來描述数字、索引引用、數量值或者UTF-8編碼構成的字符串值。

表是由多個無符號數或其他表作為數據項構成的複合數據類型,所有的表都習慣地以“_info”結尾。表的數據結構和樹很類似,無符號數相當於它的恭弘=叶 恭弘子節點,其他的表相當於它的子節點。整個Class文件就本質上也是一個表,具體結構如下:

類型 名稱 數量 描述
u4 magic 1 魔數
u2 minor_version 1 次版本號
u2 major_version 1 主版本號
u2 constant_pool_count 1 常量池容量計數值
cp_info constant_pool constant_pool_count – 1 常量池
u2 access_flags 1 訪問標誌
u2 this_class 1 類索引
u2 super_class 1 父類索引
u2 interfaces_count 1 接口索引計數值
u2 interfaces interface_count 接口索引
u2 fields_count 1 字段計數值
field_info fields fields_count 字段
u2 methods_count 1 方法計數值
method_info fields methods_count 方法
u2 attributes_count 1 屬性計數值
attribute_info attributes attributes_count 屬性

可以發現,無論是無符號數還是表,當需要描述同一種類型又數量不定的多條數據時,就會用一個前置的計數器加幾個連續的數據項的方式,這個時候我們就把這種一系列連續的某種類型的數據叫做這個類型的集合。

在Class文件中,無論是順序還是數量,甚至是數據存儲的字節序,都必須嚴格按照上面表格進行設定,哪個字節代表什麼含義,長度是多少,先後順序怎麼樣,都不允許改變。接下來看一下各個數據項的具體含義。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

魔數

魔數(Magic Number)是每個Class文件的前4個字節,它用來確定當前文件是否是一個被Java虛擬機所接受的Class文件。很多文件存儲標準中都使用了魔數進行身份識別,比如gif、jpeg等圖片文件中都有魔數。使用魔數而不使用擴展名是出於安全考慮,因為擴展名更容易被修改。文件格式制定者可以自主選擇魔數,只要這個魔數沒有被廣泛使用又不和其他文件混淆就可以。

Class文件的魔數是:0xCAFEBABE(咖啡寶貝?),這個魔數在Java還被稱為“Oak”語言的時候(大概是1991年)就確定下來了,據Java開發小組最初的關鍵成員Patrick Naughton說:“我們一直在尋找一些好玩的、容易記憶的東西,選擇0xCAFEBABE是因為它象徵著著名咖啡品牌Peet’s Coffee中深受歡迎的Baristas咖啡”,他們是真的很喜歡喝咖啡啊,可能也預示着日後“Java”這個名字的出現。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

為了更快的理解,我準備了一個實際案例,一段非常簡單的Java代碼:

public class OneMoreStudy {
    private int number;

    private int plusOne() {
        return number + 1;
    }
}

使用JDK 1.7把這段代碼編譯成Class文件,用打開,就可以到魔數了,如下圖:

在接下來的分享中,也會經常使用這個Class文件。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

次版本號和主版本號

緊跟着魔數的第5和第6個字節是次版本號(Minor Version),第7和第8個字節是主版本號(Major Version)。Java的主版本號是從45開始的,從JDK 1.1以後每個JDK大版本發布主版本號都加1,高版本的JDK向下兼容低版本的Class文件,但不能運行更高版本的Class文件,即使Class文件的格式沒有發生任何變化,Java虛擬機也會拒絕運行超過其版本號的Class文件。

再來看一下之前的Class文件例子:

表示次版本號的第5和第6個字節值為0x0000,表示主版本號的第7和第8個字節值為0x0033,也就是十進制的51,說明這個Class文件可以被JDK 1.7及其以上版本的Java虛擬機運行。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

常量池

緊跟着主版本號的就是常量池,它可以理解為Class文件的資源倉庫,也是Class文件結構中與其他數據項關聯最多的數據類型。因為在常量池中的常量數量是不固定的,所以首先有一個u2類型的數據,表示常量池容量大小(constant_pool_count)。

常量池的容量計數不是從0開始的,而是從1開始的,這是因為0有它的特殊用用途,那就是為了表達在特殊情況下需要表達“不引用任何一個常量池項目”的含義。在Class文件結構中只有常量池的容量計數是從1開始的,對於其他集合,包括接口索引集合、字段集合、方法集合等的容量計數都是從0開始的。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

再來看一下之前的Class文件例子:

常量池容器計數值為0x0013,也就是十進制的19,它表示常量池中有18個常量,索引值範圍從1到18。

常量池中主要存儲兩種常量:字面量(Literal)和符號引用(Symbolic References)。字面量比較接近Java語言層面的常量,比如文本字符串、聲明為final的常量值。符號引用則是編譯原理層次的概念,它包括以下三種:

  • 類和接口的全限定名
  • 字段的名稱和描述符
  • 方法的名稱和描述符

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

常量池中每一個常量都是一個表,共有14種不同的常量類型(JDK1.7及之前版本),每一種類型的表在第一位都有一個u1類型的標誌位,具體如下錶:

類型 標誌位 描述
CONSTANT_Utf8_info 1 UTF-8編碼的字符串
CONSTANT_Integer_info 3 整形字面量
CONSTANT_Float_info 4 浮點型字面量
CONSTANT_Long_info 5 長整型字面量
CONSTANT_Double_info 6 雙精度浮點型字面量
CONSTANT_Class_info 7 類或接口的符號引用
CONSTANT_String_info 8 字符串類型字面量
CONSTANT_Fieldref_info 9 字段的符號引用
CONSTANT_Methodref_info 10 類中方法的符號引用
CONSTANT_InterfaceMethodref_info 11 接口中方法的符號引用
CONSTANT_NameAndType_info 12 字段或方法的部分符號引用
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_MethodType_info 16 標識方法類型
CONSTANT_InvokeDynamic_info 18 表示一個動態方法調用點

有個一個專門分析Class文件字節碼的工具javap,我們用它直接看一下之前的Class文件例子里的18個常量(常量池以外的信息已省略):

E:\>javap -verbose OneMoreStudy
  Compiled from "OneMoreStudy.java"
  minor version: 0
  major version: 51
Constant pool:
   #1 = Methodref     #4.#15     // java/lang/Object."<init>":()V
   #2 = Fieldref      #3.#16     // OneMoreStudy.number:I
   #3 = Class         #17        // OneMoreStudy
   #4 = Class         #18        // java/lang/Object
   #5 = Utf8          number
   #6 = Utf8          I
   #7 = Utf8          <init>
   #8 = Utf8          ()V
   #9 = Utf8          Code
  #10 = Utf8          LineNumberTable
  #11 = Utf8          plusOne
  #12 = Utf8          ()I
  #13 = Utf8          SourceFile
  #14 = Utf8          OneMoreStudy.java
  #15 = NameAndType   #7:#8       // "<init>":()V
  #16 = NameAndType   #5:#6       // number:I
  #17 = Utf8          OneMoreStudy
  #18 = Utf8          java/lang/Object

其中,有一些常量好像在代碼里沒有出現過,如“I”、“ ”、“Code”、“LineNumberTable”、“SourceFile”。它們其實自動生成的,是後面要分享的字段表、方法表、屬性表引用到的,用於描述一些不方便使用“固定字節”進行表達的內容。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

訪問標誌

緊跟着常量池的2個字節表示訪問標誌(access_flags),它用於識別一些類或接口層次的訪問信息,具體見下錶:

標誌名稱 標誌值 含義
ACC_PUBLIC 0x0001 是否為public類型
ACC_FINAL 0x0010 是否被聲明為final
ACC_SUPER 0x0020 是否允許使用invokespecial字節碼指令
ACC_INTERFACE 0x0200 是否是接口
ACC_ABSTRACT 0x0400 是否為abstract類型
ACC_SYNTHETIC 0x1000 標誌這個類並非由用戶代碼產生的
ACC_ANNOTATION 0x2000 是否是註解
ACC_ENUM 0x4000 是否是枚舉

其中,ACC_SUPER在JDK 1.0.2之後編譯出來的Class文件必須為true;ACC_ABSTRACT對於接口或抽象類來說為true,其他類為false。

之前的例子OneMoreStudy是一個普通的類,不是接口、註解或枚舉,只被public修飾,沒有被聲明為final或abstract,而且是JDK 1.7編譯的,所以只有ACC_PUBLIC和ACC_SUPER為true,所以它的訪問標誌應該是0x0001 | 0x0020 = 0x0021,如下圖:

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

下回分解

由於篇幅限制,這次的分享先暫時到這裏,希望大家更好地消化吸收。欲知後事如何,請聽下回分解!敬請期待!

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※專營大陸空運台灣貨物推薦

※台灣空運大陸一條龍服務

※評比南投搬家公司費用收費行情懶人包大公開

※南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

從「河」而來的亞馬遜汞污染? 研究追「毒黃金」非法來源 籲國際重視

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※專營大陸空運台灣貨物推薦

※台灣空運大陸一條龍服務

※評比彰化搬家公司費用,南投搬家公司費用收費行情懶人包大公開

※彰化搬家費用,南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

Vue項目性能優化整理

 以下方式基於 @vue/cli 快速搭建的交互式項目腳手架

1. 路由懶加載

當打包構建應用時,JavaScript 包會變得非常大,影響頁面加載。如果我們能把不同路由對應的組件分割成不同的代碼塊,然後當路由被訪問的時候才加載對應組件,這樣就更加高效了。

結合 Vue 的和 Webpack 的,輕鬆實現路由組件的懶加載。

 1 import Vue from 'vue'
 2 import Router from 'vue-router'
 3 import store from './store'
 4 import Home from './views/Home.vue'
 5 
 6 Vue.use(Router)
 7 
 8 const router = new Router({
 9   routes: [
10     {
11       path: '/',
12       name: 'home',
13       component: Home,
14     },
15     {
16       path: '/make',
17       name: 'make',
18       component: () => import(/* webpackChunkName: "make" */ './views/Make.vue'),
19     }
20   ],
21 })

 

2. webpack動態導入

將statically import(靜態導入) 改為 dynamic import(動態導入)進行代碼拆分

 1 import(/* webpackChunkName: "html2canvas" */ 'html2canvas').then(
 2   ({ default: html2canvas }) => {
 3     html2canvas(document.querySelector('.container'), {
 4       scale: window.devicePixelRatio,
 5       allowTaint: false,
 6       useCORS: true,
 7     }).then(canvas => {
 8       console.log(canvas.toDataURL('image/jpeg', 0.9))
 9     })
10   }
11 )

 

3. 預取/預加載模塊(prefetch/preload module)

在聲明 import 時,使用下面這些內置指令,可以讓 webpack 輸出 “resource hint(資源提示)”,來告知瀏覽器:

  prefetch(預取):將來某些導航下可能需要的資源
  preload(預加載):當前導航下可能需要資源

import(/* webpackPrefetch: true */ 'LoginModal');

import(/* webpackPreload: true */ 'ChartingLibrary');

這會生成 <link rel="prefetch" href="login-modal-chunk.js"> 並追加到頁面頭部,指示着瀏覽器在閑置時間預取 login-modal-chunk.js 文件。

只要父 chunk 完成加載,webpack 就會添加 prefetch hint(預取提示)。

與 prefetch 指令相比,preload 指令有許多不同之處:

  • preload chunk 會在父 chunk 加載時,以并行方式開始加載。prefetch chunk 會在父 chunk 加載結束后開始加載。
  • preload chunk 具有中等優先級,並立即下載。prefetch chunk 在瀏覽器閑置時下載。
  • preload chunk 會在父 chunk 中立即請求,用於當下時刻。prefetch chunk 會用於未來的某個時刻。
  • 瀏覽器支持程度不同。

vue默認開啟,可在vue.config.js中全局禁用prefetch,再針對指定模塊開啟。

chainWebpack: config => {
  config.plugins.delete('prefetch')
},

 

4. 添加Gzip打包配置()

  yarn add compression-webpack-plugin -D

configureWebpack: config => {
  const CompressionPlugin = require('compression-webpack-plugin')
  config.plugins.push(new CompressionPlugin())
}

 

5. 添加頁面預渲染()

在單頁應用程序中預呈現靜態HTML,可以極大的提高網頁訪問速度,而且配合一些meat插件,基本可以滿足SEO需求。

預渲染基本上是在啟動無頭瀏覽器,加載應用程序的路由並將結果保存到靜態HTML文件中。然後將其與以前使用的任何靜態文件服務解決方案一起使用,是無需更改代碼或添加服務器端渲染的解決方法。

不過,它僅適用於HTML5 history,因為每個預渲染的路由都會生成一個對應的HTML,在hash模式下使用只會有一個HTML。

  yarn add prerender-spa-plugin -D

 1 configureWebpack: config => {
 2   const path = require('path')
 3   const PrerenderSPAPlugin = require('prerender-spa-plugin')
 4   config.plugins.push(
 5     new PrerenderSPAPlugin({
 6       staticDir: path.join(__dirname, 'dist'),
 7       routes: ['/'],
 8       minify: {
 9         collapseBooleanAttributes: true,
10         collapseWhitespace: true,
11         keepClosingSlash: true,
12         decodeEntities: true,
13         sortAttributes: true,
14       },
15       renderer: new PrerenderSPAPlugin.PuppeteerRenderer({
16         renderAfterDocumentEvent: 'render-event',
17         renderAfterTime: 5000,
18         // headless: false,
19       }),
20     })
21   )
22 }
23 
24 new Vue({
25   router,
26   store,
27   render: h => h(App),
28   mounted() {
29     // 預渲染 renderAfterDocumentEvent.
30     document.dispatchEvent(new Event('render-event'))
31   },
32 }).$mount('#app')

prerender-spa-plugin 利用了 Puppeteer 的爬取頁面的功能。 Puppeteer 是一個 Chrome官方出品的 headlessChromenode 庫。它提供了一系列的 API, 可以在無 UI 的情況下調用 Chrome 的功能, 適用於爬蟲、自動化處理等各種場景。它很強大,所以很簡單就能將運行時的 HTML 打包到文件中。原理是在 Webpack 構建階段的最後,在本地啟動一個 Puppeteer 的服務,訪問配置了預渲染的路由,然後將 Puppeteer 中渲染的頁面輸出到 HTML 文件中,並建立路由對應的目錄。

 

6. 壓縮js,刪除console()

  yarn add terser-webpack-plugin -D

1 configureWebpack: config => {
2   const TerserPlugin = require('terser-webpack-plugin')
3   config.optimization.minimizer.push(
4     new TerserPlugin({
5       extractComments: false,
6       terserOptions: { compress: { drop_console: true } },
7     })
8   )
9 }

 

7. bundle 分析()

將 bundle 內容展示為便捷的、交互式、可縮放的樹狀圖形式。

  yarn add -D webpack-bundle-analyzer

1 configureWebpack: config => {
2   const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
3     .BundleAnalyzerPlugin
4   config.plugins.push(new BundleAnalyzerPlugin())
5 }

 

8. WebP

WebP(發音 weppy),是一種支持有損壓縮和無損壓縮的圖片文件格式,派生自圖像編碼格式 VP8。根據 Google 的測試,無損壓縮后的 WebP 比 PNG 文件少了 45% 的文件大小,即使這些 PNG 文件經過其他壓縮工具壓縮之後,WebP 還是可以減少 28% 的文件大小。

不過WebP目前在IOS上兼容性不好,可以使用JavaScript進行檢測,對支持 WebP 的用戶輸出 WebP 圖片。

 1 created() {
 2   const htmlClass = document.documentElement.classList
 3   this.checkWebpSupport() ? htmlClass.add('webps') : htmlClass.remove('webps')
 4 }
 5 
 6 checkWebpSupport() {
 7   try {
 8     return (
 9       document
10         .createElement('canvas')
11         .toDataURL('image/webp')
12         .indexOf('data:image/webp') === 0
13     )
14   } catch (err) {
15     return false
16   }
17 }

記一次BUG:

  在默認情況下,頁面加載完成執行 this.checkWebpSupport() && document.documentElement.classList.add(‘webps’),沒有問題。

  但使用了prerender-spa-plugin進行預渲染后,因為執行預渲染的瀏覽器是支持WebP的,所有會直接在頁面中加上’webps’類,所以即使瀏覽器不支持WebP,不執行此方法也會有該類名。

 

9. 網頁性能優化測試()

進行網頁測試,根據優化建議針對性的修改,提高網頁加載速度。

  https://www.googlespeed.cn/

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※專營大陸空運台灣貨物推薦

※台灣空運大陸一條龍服務

※評比彰化搬家公司費用,南投搬家公司費用收費行情懶人包大公開

※彰化搬家費用,南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

衛星監測曝光 義大利全境封鎖後空污大幅改善

摘錄自2020年03月15日自由時報報導

中國日前因新型冠狀病毒肺炎(武漢肺炎,COVID-19)影響,封省限制民眾外出、工業活動暫停,意外讓中國碳排放量大減,同樣的情形也出現在義大利,由於政府宣布全國封鎖,許多民眾被迫留在家中,義大利半島上空空污大幅減少,尤其是疫情重災區義大利北部地區更是明顯。

綜合外媒報導,可監測大氣中空污情形的歐洲太空總署(ESA)「哨兵五號」(Sentinel-5P)衛星發現,整個義大利上空的空氣污染,特別是二氧化氮排放量大幅減少,在半島北部地區尤為明顯,ESA「哨兵五號」團隊成員澤納(Claus Zehner)指出,受雲層覆蓋和天氣變化影響,數據可能略有出入,「但我們非常有信心,交通和工業活動大幅縮減的情況下,排放量降低與義大利封鎖有密切關聯。」

ESA特地製作義大利上空自今年1月1日起到2020年3月11日的二氧化氮排放量波動,可以明顯看出,義大利半島北部的波河流域二氧化氮含量急遽減少,波河中上游為義大利重要的人口密集工業地帶。

公害污染
空氣污染
國際新聞
義大利
武漢肺炎

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※專營大陸空運台灣貨物推薦

※台灣空運大陸一條龍服務

※評比彰化搬家公司費用,南投搬家公司費用收費行情懶人包大公開

※彰化搬家費用,南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

你不知道的JS系列【1】- 什麼是作用域

  幾乎所有的編程語言都能夠儲存變量,並且能在之後對這個變量值進行訪問或修改,正是儲存和訪問變量的能力將狀態帶給了程序,那麼,這些變量儲存在哪裡呢?程序需要時又是如何找到他們?這些問題說明需要一套設計良好的規則來儲存變量,並且之後可以方便的找到這些變量,這套規則被稱為作用域。

1、了解編譯原理

  儘管將JS歸類為“動態”或“解釋執行”腳本語言,但事實上它是一門編譯語言。但是與傳統編譯語言不同的是,它不是提前編譯的,編譯結果也不能在分佈式系統中進行移植。JS引擎進行編譯的步驟與傳統的語言非常相似,程序中一段源代碼在執行之前會經歷三個步驟,統稱為“編譯”。

  • 分詞/詞法分析

這個過程會將由字符組成的字符串分解成有意義的代碼塊,這些代碼塊被稱為詞法單元。例如,考慮程序 var a = 2;。這段程序通常會被分解成 為下面這些詞法單元:var、a、=、2 、;。

  • 解析/語法分析

這個過程是將詞法單元流(數組)轉換成一個由元素逐級嵌套所組成的代表了程序語法結構的樹。這個樹被稱為“抽象語法樹”(Abstract Syntax Tree,AST)。var a = 2;的抽象語法樹中可能會有一個叫作VariableDeclaration的頂級節點,接下來是一個叫作 Identifier(它的值是 a)的子節點,以及一個叫作 AssignmentExpression 的子節點。AssignmentExpression 節點有一個叫作 NumericLiteral(它的值是 2)的子節點。

  • 代碼生成

AST轉換為可執行代碼的過程稱被稱為代碼生成,簡單來說就是有某種方法可以將 var a = 2;的AST轉化為一組機器指 令,用來創建一個叫作a的變量(包括分配內存等),並將一個值儲存在a中。

編譯流程如下圖所示:

JS引擎比傳統的編譯語言編譯器複雜很多,在語法分析和代碼生成階段有特定的步驟來對性能進行優化,大部分情況下編譯發生在代碼之前的前幾微秒,在討論作用域背後,js引擎用了各種辦法來保證性能最佳。

Tips:我們平時在寫JS代碼的時候,一個語句結尾要加分號(;),便於JS編譯器編譯。

2、理解作用域

  我們先了解JS編譯過程中幾個名詞,JS引擎,編譯器,作用域。

2.1.名詞介紹

  • JS引擎:從頭到尾負責整個JS程序編譯過程。
  • 編譯器:負責語法分析及代碼生成等。
  • JS引擎:負責收集並維護由所有聲明的標識符(變量)組成的一系列查 詢,並實施一套非常嚴格的規則,確定當前執行的代碼對這些標識符的訪問權限。

2.2.變量賦值

對於var a=2;這段代碼,我們認為這就是申明一個為變量a且初始值為2,實際上,JS引擎認為這裡有兩個完全不同的申明,一個由編譯器在編譯時處理,另一個則由引擎在運行時處理。

處理過程分為兩步:

1.遇到var a,編譯器會詢問作用域是否已經有一個該名稱的變量存在於同一個作用域的集合中。如果是,編譯器會忽略該聲明,繼續進行編譯;否則它會要求作用域在當前作用域的集合中聲明一個新的變量,並命名為a。

2.接下來編譯器會為引擎生成運行時所需的代碼,這些代碼被用來處理a = 2這個賦值操作。引擎運行時會首先詢問作用域,在當前的作用域集合中是否存在一個叫作a的變量。如果是,引擎就會使用這個變量;如果否,引擎會繼續查找該變量。

如果引擎最終找到a變量,就會將2賦值給它。否則就拋出異常。

Tips:聲明提前(hoist)-JS引擎在創建變量時,會將該變量提升到當前作用域的最前面。

總結:變量的賦值操作會執行兩個動作,首先編譯器會在當前作用域中聲明一個變量(如果之前沒有聲明過),然後在運行時引擎會在作用域中查找該變量,如果能夠找到就會對它賦值。

2.3.LHS查詢&RHS查詢

編譯器在編譯過程中的第二步生成了代碼,引擎在執行時,會通過查找變量a來判斷它是否已經聲明過。當變量出現在賦值操作的左側時進行LHS查詢,當變量出現在右側時進行RHS查詢。

console.log(a); //對a的引用時RHS引用,這裏沒有對a賦予任何值,需要查找a的值。

a=2; //對a的引用是LHS引用,因為這裏不關心a的值等於多少,只想為 =2 這個賦值操作找到一個目標(變量a);

LHS和RHS的含義是“賦值操作的左側或右側”並不一定意味着就是“= 賦值操作符的左側或右側”。賦值操作還有其他幾種形式,因此在概念上最好將其理解為“賦值操作的目標是誰(LHS)”以及“去找到XX變量的值,誰是賦值操作的源頭(RHS)”。

3、作用域嵌套

  作用域是根據名稱查找變量的一套規則。實際情況中,通常需要同時顧及幾個作用域。 當一個塊或函數嵌套在另一個塊或函數中時,就發生了作用域的嵌套。因此,在當前作用 域中無法找到某個變量時,引擎就會在外層嵌套的作用域中繼續查找,直到找到該變量, 或抵達最外層的作用域(也就是全局作用域)為止。

參考以下代碼:

var name='peer';
function sayHello(){
  alert('hello '+ name)
}
sayHello();
// 對name的RHS引用無法在函數sayHello完成,但是可以在上一級作用域中完成。

把作用域比喻成一個建築如下圖所示:

LHS和RHS引用都會在當前樓層進行查找,如果沒有找到,就會坐電梯前往上一層樓,如果還是沒有找到就繼續向上,以此類推。一旦抵達頂層(全局作用域),可能找到了你所需的變量,也可能沒找到,但無論如何查找過程都將停止。

4、總結

  作用域是一套規則,用於確定在何處以及如何查找變量(標識符)。LHS和RHS查詢都會在當前執行作用域中開始,如果有需要就會向上級作用域繼續查找目標標識符,這樣每次上升一級作用域,最後抵達全局作用域,無論找到或沒找到都將停止。不成功的RHS 引用會導致拋出ReferenceError異常。不成功的LHS引用會導致自動隱式地創建一個全局變量(非嚴格模式下),掌握這些基本作用域知識能使我們更深入理解JS引擎的編譯過程來編寫更高性能的代碼。

參考資料:
《你不知道的JavaScript》

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※專營大陸空運台灣貨物推薦

※台灣空運大陸一條龍服務

※評比彰化搬家公司費用,南投搬家公司費用收費行情懶人包大公開

※彰化搬家費用,南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

新冠疫情打亂中國貿易 肯亞漁民收入好轉

摘錄自2020年3月24日中時報導

武漢肺炎全球大流行,造成國際物流與貿易的停滯,不過這肯亞漁民來說,卻帶來了意想不到的好處。由於現在中國大陸的冷凍魚片無法進口,因此當地轉而購買肯亞漁民在維多利亞湖捕獲的鮮魚。

路透社報導,中國大陸貿易力量的強勢,使得肯亞當地漁民處境辛苦,根據國際貿易中心(International Trade Center)的數據,以2018年為例,中國大陸銷售給肯亞價值2320萬美元的冷凍魚,佔領了肯亞的魚獲市場,幾乎所有的魚類進口都來自中國大陸。長期以來,肯亞漁民一直抱怨,廉價進口的冷凍漁,幾乎扼殺了當地漁民生計。

然而,武漢肺炎的蔓延,中止了中國大陸的進口業,很快魚肉供應緊缺,使得肯亞消費者轉向購買當地的魚類,對當地漁民而言是個好消息。但是,從較大的經濟角度來看,接下來肯亞會遇到供不應求的麻煩,因為當地漁獲量僅能提供消費量的三分之一。

農林漁牧業
環境經濟
國際新聞
肯亞
武漢肺炎
漁民

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※專營大陸空運台灣貨物推薦

※台灣空運大陸一條龍服務

※評比彰化搬家公司費用,南投搬家公司費用收費行情懶人包大公開

※彰化搬家費用,南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

始於野味的全球疫情 中國仍鼓勵熊膽入藥治武漢肺炎

環境資訊中心綜合外電;黃鈺婷 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※專營大陸空運台灣貨物推薦

※台灣空運大陸一條龍服務

※評比彰化搬家公司費用,南投搬家公司費用收費行情懶人包大公開

※彰化搬家費用,南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

阿里汽車發佈「車秒貸」2.0

4月24日,阿里在北京車展前日發佈了「車秒貸」2.0版本。阿里汽車事業部總經理王立成對該產品的描述是「拿起手機,最快20秒就可以完成最高上限為20萬人民幣(下同)的貸款額度,如果恰好你所在的品牌4S店有現車,手續辦理後就可以直接開走。」車秒貸升級2.0後,通過螞蟻金服旗下的網商銀行的大資料風控模型,車秒貸授信用戶從此前的5000萬擴大到1億。

在車秒貸體系中,網商銀行向汽車金融公司輸出大資料風控模型,説明汽車金融公司來甄別風險、篩選用戶。從車秒貸1.0版本發佈起,網商銀行聯合東風標緻雪鐵龍金融、海馬財務、創富租賃等7家汽車金融公司,全國累計申請車秒貸的消費者超過6萬名。目前,網商銀行正與廣汽匯理、郵政儲蓄銀行、通用金融、日產金融、一汽金融、奇瑞徽銀等多家金融公司洽談合作,進一步擴大業務規模。

據悉,廣汽匯理、郵政儲蓄銀行預期在今年5月正式上線車秒貸產品,其中,廣汽匯理最高貸款額度或將超50萬,屆時,享受車秒貸產品的車型價位也將會全面提升。

王立成介紹說,車秒貸用戶線上上無需任何材料,只要登錄天貓移動用戶端,選取車型和貸款方案,等待極短時間後即可得到一個確定給到的貸款額度,最快一筆授信審批通過只需要20秒,授信額度最高可達汽車指導價的80%。

目前接入「車秒貸」的品牌廠商有奧迪、東風日產、東風雪鐵龍、東風標緻、別克、通用雪佛蘭、吉利、奇瑞、力帆、DS、海馬汽車等33家,已上線的車型共75款乘用車,價格區間為10~30萬 。其中,10~15萬的汽車占比超80%。

汽車廠商之所以願意加入「車秒貸」,一方面是看中這種新型的線上放款模式更加高效,也看中電商平臺能否帶來更多線下無法觸及的用戶。在之前的運作中,阿里汽車發現,線上線下客戶有明顯的差異化,一部分按照線下標準無法成功申請貸款的客戶(如無車或無房),在車秒貸的平臺上獲得貸款的機率更大。

阿里巴巴提供的資料顯示,上海、武漢、蘇州、南京、北京為車秒貸消費密集的城市。但從趨勢上看,通過大資料預測,未來77%的新車需求會在三四五線城市。從消費年齡上看維度較廣,70、80、90後的為主流用戶群體。其中,90後大學畢業生接受度最高。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※專營大陸空運台灣貨物推薦

※台灣空運大陸一條龍服務

※評比彰化搬家公司費用,南投搬家公司費用收費行情懶人包大公開

※彰化搬家費用,南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!