【集合系列】- 紅黑樹實現分析

一、故事的起因

JDK1.8最重要的就是引入了紅黑樹的設計(當衝突的鏈表長度超過8個的時候),為什麼要這樣設計呢?好處就是避免在最極端的情況下衝突鏈表變得很長很長,在查詢的時候,效率會非常慢。

  • 紅黑樹查詢:其訪問性能近似於折半查找,時間複雜度O(logn);
  • 鏈表查詢:這種情況下,需要遍歷全部元素才行,時間複雜度O(n);

本文主要是講解紅黑樹的實現,只有充分理解了紅黑樹,對於後面的分析才會更加順利。

簡單的說,紅黑樹是一種近似平衡的二叉查找樹,其主要的優點就是“平衡“,即左右子樹高度幾乎一致,以此來防止樹退化為鏈表,通過這種方式來保障查找的時間複雜度為log(n)。

關於紅黑樹的內容,網上給出的內容非常多,主要有以下幾個特性:

  • 1、每個節點要麼是紅色,要麼是黑色,但根節點永遠是黑色的;
  • 2、每個紅色節點的兩個子節點一定都是黑色;
  • 3、紅色節點不能連續(也即是,紅色節點的孩子和父親都不能是紅色);
  • 4、從任一節點到其子樹中每個恭弘=叶 恭弘子節點的路徑都包含相同數量的黑色節點;
  • 5、所有的恭弘=叶 恭弘節點都是是黑色的(注意這裏說恭弘=叶 恭弘子節點其實是上圖中的 NIL 節點);

在樹的結構發生改變時(插入或者刪除操作),往往會破壞上述條件3或條件4,需要通過調整使得查找樹重新滿足紅黑樹的條件。

二、調整方式

上面已經說到當樹的結構發生改變時,紅黑樹的條件可能被破壞,需要通過調整使得查找樹重新滿足紅黑樹的條件。

調整可以分為兩類:一類是顏色調整,即改變某個節點的顏色,這種比較簡單,直接將節點顏色進行轉換即可;另一類是結構調整,改變檢索樹的結構關係。結構調整主要包含兩個基本操作:左旋(Rotate Left),右旋(RotateRight)。

2.1、左旋

左旋的過程是將p的右子樹繞p逆時針旋轉,使得p的右子樹成為p的父親,同時修改相關節點的引用,使左子樹的深度加1,右子樹的深度減1,通過這種做法來調整樹的穩定性。過程如下:

以jdk1.8為例,打開HashMap的源碼部分,紅黑樹內部類TreeNode屬性分析:

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        //指向父節點的指針
        TreeNode<K,V> parent;
        //指向左孩子的指針
        TreeNode<K,V> left;
        //指向右孩子的指針
        TreeNode<K,V> right;
        //前驅指針,跟next屬性相反的指向
        TreeNode<K,V> prev;
        //是否為紅色節點
        boolean red;
        ......
}

左旋方法rotateLeft如下:

/*
 * 左旋邏輯
 */
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
                                              TreeNode<K,V> p) {
            //root:表示根節點
            //p:表示要調整的節點
            //r:表示p的右節點
            //pp:表示p的parent節點
            //rl:表示p的右孩子的左孩子節點
            TreeNode<K,V> r, pp, rl;
            //r判斷,如果r為空則旋轉沒有意義
            if (p != null && (r = p.right) != null) {
                //多個等號的連接操作從右往左看,設置rl的父親為p
                if ((rl = p.right = r.left) != null)
                    rl.parent = p;
                //判斷p的父親,為空,為根節點,根節點的話就設置為黑色
                if ((pp = r.parent = p.parent) == null)
                    (root = r).red = false;
                //判斷p節點是左兒子還是右兒子
                else if (pp.left == p)
                    pp.left = r;
                else
                    pp.right = r;
                r.left = p;
                p.parent = r;
            }
            return root;
}

2.2、右旋

了解了左旋轉之後,相應的就會有右旋,邏輯基本也是一樣,只是方向變了。右旋的過程是將p的左子樹繞p順時針旋轉,使得p的左子樹成為p的父親,同時修改相關節點的引用,使右子樹的深度加1,左子樹的深度減1,通過這種做法來調整樹的穩定性。實現過程如下:

同樣的,右旋方法rotateRight如下:

/*
 * 右旋邏輯
 */
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
                                               TreeNode<K,V> p) {
            //root:表示根節點
            //p:表示要調整的節點
            //l:表示p的左節點
            //pp:表示p的parent節點
            //lr:表示p的左孩子的右孩子節點
            TreeNode<K,V> l, pp, lr;
            //l判斷,如果l為空則旋轉沒有意義
            if (p != null && (l = p.left) != null) {
                //多個等號的連接操作從右往左看,設置lr的父親為p
                if ((lr = p.left = l.right) != null)
                    lr.parent = p;
                //判斷p的父親,為空,為根節點,根節點的話就設置為黑色
                if ((pp = l.parent = p.parent) == null)
                    (root = l).red = false;
                //判斷p節點是右兒子還是左兒子
                else if (pp.right == p)
                    pp.right = l;
                else
                    pp.left = l;
                l.right = p;
                p.parent = l;
            }
            return root;
}

三、操作示例介紹

3.1、插入調整過程圖解

3.2、刪除調整過程圖解

3.3、查詢過程圖解

四、總結

至此,紅黑樹的實現就基本完成了,關於紅黑樹的結構,有很多種情況,情況也比較複雜,但是整體調整流程,基本都是先調整結構然後調整顏色,直到最後滿足紅黑樹特性要求為止。整篇文章,如果有理解不當之處,歡迎指正!

五、參考

1、
2、

作者:炸雞可樂
出處:

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

【其他文章推薦】

※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

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

Webpack 4 Tree Shaking 終極優化指南

幾個月前,我的任務是將我們組的 Vue.js 項目構建配置升級到 Webpack 4。我們的主要目標之一是利用 tree-shaking 的優勢,即 Webpack 去掉了實際上並沒有使用的代碼來減少包的大小。現在,tree-shaking 的好處將根據你的代碼庫而有所不同。由於我們的幾個架構決策,我們從公司內部的其他庫中提取了大量代碼,而我們只使用了其中的一小部分。

我寫這篇文章是因為恰當地優化 Webpack 並不簡單。一開始我以為這是一種簡單的魔法,但後來我花了一個月的時間在網上搜索我遇到的一系列問題的答案。我希望通過這篇文章,其他人會更容易地處理類似問題。

先說好處

在討論技術細節之前,讓我先總結一下好處。不同的應用程序將看到不同程度的好處。主要的決定因素是應用程序中死代碼的數量。如果你沒有多少死代碼,那麼你就看不到 tree-shaking 的多少好處。我們項目里有很多死代碼。

在我們部門,最大的問題是共享庫的數量。從簡單的自定義組件庫,到企業標準組件庫,再到莫名其妙地塞到一個庫中的大量代碼。很多都是技術債務,但一個大問題是我們所有的應用程序都在導入所有這些庫,而實際上每個應用程序都只需要其中的一小部分

總的來說,一旦實現了 tree-shaking,我們的應用程序就會根據應用程序的不同,縮減率從25%到75%。平均縮減率為52%,主要是由這些龐大的共享庫驅動的,它們是小型應用程序中的主要代碼。

同樣,具體情況會有所不同,但是如果你覺得你打的包中可能有很多不需要的代碼,這就是如何消除它們的方法。

沒有示例代碼倉庫

對不住了各位老鐵,我做的項目是公司的財產,所以我不能分享代碼到 GitHub 倉庫了。但是,我將在本文中提供簡化的代碼示例來說明我的觀點。

因此,廢話少說,讓我們來看看如何編寫可實現 tree-shaking 的最佳 webpack 4 配置。

什麼是死代碼

很簡單:就是 Webpack 沒看到你使用的代碼。Webpack 跟蹤整個應用程序的 import/export 語句,因此,如果它看到導入的東西最終沒有被使用,它會認為那是“死代碼”,並會對其進行 tree-shaking 。

死代碼並不總是那麼明確的。下面是一些死代碼和“活”代碼的例子,希望能讓你更明白。請記住,在某些情況下,Webpack 會將某些東西視為死代碼,儘管它實際上並不是。請參閱《副作用》一節,了解如何處理。

// 導入並賦值給 JavaScript 對象,然後在下面的代碼中被用到
// 這會被看作“活”代碼,不會做 tree-shaking
import Stuff from './stuff';
doSomething(Stuff);
// 導入並賦值給 JavaScript 對象,但在接下來的代碼里沒有用到
// 這就會被當做“死”代碼,會被 tree-shaking
import Stuff from './stuff';
doSomething();
// 導入但沒有賦值給 JavaScript 對象,也沒有在代碼里用到
// 這會被當做“死”代碼,會被 tree-shaking
import './stuff';
doSomething();
// 導入整個庫,但是沒有賦值給 JavaScript 對象,也沒有在代碼里用到
// 非常奇怪,這竟然被當做“活”代碼,因為 Webpack 對庫的導入和本地代碼導入的處理方式不同。
import 'my-lib';
doSomething();

用支持 tree-shaking 的方式寫 import

在編寫支持 tree-shaking 的代碼時,導入方式非常重要。你應該避免將整個庫導入到單個 JavaScript 對象中。當你這樣做時,你是在告訴 Webpack 你需要整個庫, Webpack 就不會搖它。

以流行的庫 Lodash 為例。一次導入整個庫是一個很大的錯誤,但是導入單個的模塊要好得多。當然,Lodash 還需要其他的步驟來做 tree-shaking,但這是個很好的起點。

// 全部導入 (不支持 tree-shaking)
import _ from 'lodash';
// 具名導入(支持 tree-shaking)
import { debounce } from 'lodash';
// 直接導入具體的模塊 (支持 tree-shaking)
import debounce from 'lodash/lib/debounce';

基本的 Webpack 配置

使用 Webpack 進行 tree-shaking 的第一步是編寫 Webpack 配置文件。你可以對你的 webpack 做很多自定義配置,但是如果你想要對代碼進行 tree-shaking,就需要以下幾項。

首先,你必須處於生產模式。Webpack 只有在壓縮代碼的時候會 tree-shaking,而這隻會發生在生產模式中。

其次,必須將優化選項 “usedExports” 設置為true。這意味着 Webpack 將識別出它認為沒有被使用的代碼,並在最初的打包步驟中給它做標記。

最後,你需要使用一個支持刪除死代碼的壓縮器。這種壓縮器將識別出 Webpack 是如何標記它認為沒有被使用的代碼,並將其剝離。TerserPlugin 支持這個功能,推薦使用。

下面是 Webpack 開啟 tree-shaking 的基本配置:

// Base Webpack Config for Tree Shaking
const config = {
 mode: 'production',
 optimization: {
  usedExports: true,
  minimizer: [
   new TerserPlugin({...})
  ]
 }
};

有什麼副作用

僅僅因為 Webpack 看不到一段正在使用的代碼,並不意味着它可以安全地進行 tree-shaking。有些模塊導入,只要被引入,就會對應用程序產生重要的影響。一個很好的例子就是全局樣式表,或者設置全局配置的JavaScript 文件。

Webpack 認為這樣的文件有“副作用”。具有副作用的文件不應該做 tree-shaking,因為這將破壞整個應用程序。Webpack 的設計者清楚地認識到不知道哪些文件有副作用的情況下打包代碼的風險,因此默認地將所有代碼視為有副作用。這可以保護你免於刪除必要的文件,但這意味着 Webpack 的默認行為實際上是不進行 tree-shaking。

幸運的是,我們可以配置我們的項目,告訴 Webpack 它是沒有副作用的,可以進行 tree-shaking。

如何告訴 Webpack 你的代碼無副作用

package.json 有一個特殊的屬性 sideEffects,就是為此而存在的。它有三個可能的值:

true 是默認值,如果不指定其他值的話。這意味着所有的文件都有副作用,也就是沒有一個文件可以 tree-shaking。

false 告訴 Webpack 沒有文件有副作用,所有文件都可以 tree-shaking。

第三個值 […] 是文件路徑數組。它告訴 webpack,除了數組中包含的文件外,你的任何文件都沒有副作用。因此,除了指定的文件之外,其他文件都可以安全地進行 tree-shaking。

每個項目都必須將 sideEffects 屬性設置為 false 或文件路徑數組。在我公司的工作中,我們的基本應用程序和我提到的所有共享庫都需要正確配置 sideEffects 標誌。

下面是 sideEffects 標誌的一些代碼示例。儘管有 JavaScript 註釋,但這是 JSON 代碼:

// 所有文件都有副作用,全都不可 tree-shaking
{
 "sideEffects": true
}
// 沒有文件有副作用,全都可以 tree-shaking
{
 "sideEffects": false
}
// 只有這些文件有副作用,所有其他文件都可以 tree-shaking,但會保留這些文件
{
 "sideEffects": [
  "./src/file1.js",
  "./src/file2.js"
 ]
}

全局 CSS 與副作用

首先,讓我們在這個上下文中定義全局 CSS。全局 CSS 是直接導入到 JavaScript 文件中的樣式表(可以是CSS、SCSS等)。它沒有被轉換成 CSS 模塊或任何類似的東西。基本上,import 語句是這樣的:

// 導入全局 CSS
import './MyStylesheet.css';

因此,如果你做了上面提到的副作用更改,那麼在運行 webpack 構建時,你將立即注意到一個棘手的問題。以上述方式導入的任何樣式表現在都將從輸出中刪除。這是因為這樣的導入被 webpack 視為死代碼,並被刪除。

幸運的是,有一個簡單的解決方案可以解決這個問題。Webpack 使用它的模塊規則系統來控制各種類型文件的加載。每種文件類型的每個規則都有自己的 sideEffects 標誌。這會覆蓋之前為匹配規則的文件設置的所有 sideEffects 標誌。

所以,為了保留全局 CSS 文件,我們只需要設置這個特殊的 sideEffects 標誌為 true,就像這樣:

// 全局 CSS 副作用規則相關的 Webpack 配置
const config = {
 module: {
  rules: [
   {
    test: /regex/,
    use: [loaders],
    sideEffects: true
   }
  ]
 } 
};

Webpack 的所有模塊規則上都有這個屬性。處理全局樣式表的規則必須用上它,包括但不限於 CSS/SCSS/LESS/等等。

什麼是模塊,模塊為什麼重要

現在我們開始進入秘境。表面上看,編譯出正確的模塊類型似乎是一個簡單的步驟,但是正如下面幾節將要解釋的,這是一個會導致許多複雜問題的領域。這是我花了很長時間才弄明白的部分。

首先,我們需要了解一下模塊。多年來,JavaScript 已經發展出了在文件之間以“模塊”的形式有效導入/導出代碼的能力。有許多不同的 JavaScript 模塊標準已經存在了多年,但是為了本文的目的,我們將重點關注兩個標準。一個是 “commonjs”,另一個是 “es2015”。下面是它們的代碼形式:

// Commonjs
const stuff = require('./stuff');
module.exports = stuff;

// es2015 
import stuff from './stuff';
export default stuff;

默認情況下,Babel 假定我們使用 es2015 模塊編寫代碼,並轉換 JavaScript 代碼以使用 commonjs 模塊。這樣做是為了與服務器端 JavaScript 庫的廣泛兼容性,這些 JavaScript 庫通常構建在 NodeJS 之上(NodeJS 只支持 commonjs 模塊)。但是,Webpack 不支持使用 commonjs 模塊來完成 tree-shaking。

現在,有一些插件(如 common-shake-plugin)聲稱可以讓 Webpack 有能力對 commonjs 模塊進行 tree-shaking,但根據我的經驗,這些插件要麼不起作用,要麼在 es2015 模塊上運行時,對 tree-shaking 的影響微乎其微。我不推薦這些插件。

因此,為了進行 tree-shaking,我們需要將代碼編譯到 es2015 模塊。

es2015 模塊 Babel 配置

據我所知,Babel 不支持將其他模塊系統編譯成 es2015 模塊。但是,如果你是前端開發人員,那麼你可能已經在使用 es2015 模塊編寫代碼了,因為這是全面推薦的方法。

因此,為了讓我們編譯的代碼使用 es2015 模塊,我們需要做的就是告訴 babel 不要管它們。為了實現這一點,我們只需將以下內容添加到我們的 babel.config.js 中(在本文中,你會看到我更喜歡JavaScript 配置而不是 JSON 配置):

// es2015 模塊的基本 Babel 配置
const config = {
 presets: [
  [
   '[@babel/preset-env](http://twitter.com/babel/preset-env)',
   {
    modules: false
   }
  ]
 ]
};

把 modules 設置為 false,就是告訴 babel 不要編譯模塊代碼。這會讓 Babel 保留我們現有的 es2015 import/export 語句。

划重點:所有可需要 tree-shaking 的代碼必須以這種方式編譯。因此,如果你有要導入的庫,則必須將這些庫編譯為 es2015 模塊以便進行 tree-shaking 。如果它們被編譯為 commonjs,那麼它們就不能做 tree-shaking ,並且將會被打包進你的應用程序中。許多庫支持部分導入,lodash 就是一個很好的例子,它本身是 commonjs 模塊,但是它有一個 lodash-es 版本,用的是 es2015模塊。

此外,如果你在應用程序中使用內部庫,也必須使用 es2015 模塊編譯。為了減少應用程序包的大小,必須將所有這些內部庫修改為以這種方式編譯。

不好意思, Jest 罷工了

其他測試框架情況類似,我們用的是 Jest。

不管怎麼樣,如果你走到了這一步,你會發現 Jest 測試開始失敗了。你會像我當時一樣,看到日誌里出現各種奇怪的錯誤,慌的一批。別慌,我會帶你一步一步解決。

出現這個結果的原因很簡單:NodeJS。Jest 是基於 NodeJS 開發的,而 NodeJS 不支持 es2015 模塊。為此有一些方法可以配置 Node,但是在 jest 上行不通。因此,我們卡在這裏了:Webpack 需要 es2015 進行 tree shaking,但是 Jest 無法在這些模塊上執行測試。

就是為什麼我說進入了模塊系統的“秘境”。這是整個過程中耗費我最多時間來搞清楚的部分。建議你仔細閱讀這一節和後面幾節,因為我會給出解決方案。

解決方案有兩個主要部分。第一部分針對項目本身的代碼,也就是跑測試的代碼。這部分比較容易。第二部分針對庫代碼,也就是來自其他項目,被編譯成 es2015 模塊並引入到當前項目的代碼。這部分比較複雜。

解決項目本地 Jest 代碼

針對我們的問題,babel 有一個很有用的特性:環境選項。通過配置可以運行在不同環境。在這裏,開發和生產環境我們需要 es2015 模塊,而測試環境需要 commonjs 模塊。還好,Babel 配置起來非常容易:

// 分環境配置Babel 
const config = {
 env: {
  development: {
   presets: [
    [
     '[@babel/preset-env](http://twitter.com/babel/preset-env)',
     {
      modules: false
     }
    ]
   ]
  },
  production: {
   presets: [
    [
     '[@babel/preset-env](http://twitter.com/babel/preset-env)',
     {
      modules: false
     }
    ]
   ]
  },
  test: {
   presets: [
    [
     '[@babel/preset-env](http://twitter.com/babel/preset-env)',
     {
      modules: 'commonjs'
     }
    ]
   ],
   plugins: [
    'transform-es2015-modules-commonjs' // Not sure this is required, but I had added it anyway
   ]
  }
 }
};

設置好之後,所有的項目本地代碼能夠正常編譯,Jest 測試能運行了。但是,使用 es2015 模塊的第三方庫代碼依然不能運行。

解決 Jest 中的庫代碼

庫代碼運行出錯的原因非常明顯,看一眼node_modules 目錄就明白了。這裏的庫代碼用的是 es2015 模塊語法,為了進行 tree-shaking。這些庫已經採用這種方式編譯過了,因此當 Jest 在單元測試中試圖讀取這些代碼時,就炸了。注意到沒有,我們已經讓 Babel 在測試環境中啟用 commonjs 模塊了呀,為什麼對這些庫不起作用呢?這是因為,Jest (尤其是 babel-jest) 在跑測試之前編譯代碼的時候,默認忽略任何來自node_modules 的代碼。

這實際上是件好事。如果 Jest 需要重新編譯所有庫的話,將會大大增加測試處理時間。然而,雖然我們不想讓它重新編譯所有代碼,但我們希望它重新編譯使用 es2015 模塊的庫,這樣才能在單元測試里使用。

幸好,Jest 在它的配置中為我們提供了解決方案。我想說,這部分確實讓我想了很久,並且我感覺沒必要搞得這麼複雜,但這是我能想到的唯一解決方案。

配置 Jest 重新編譯庫代碼

// 重新編譯庫代碼的 Jest 配置 
const path = require('path');
const librariesToRecompile = [
 'Library1',
 'Library2'
].join('|');
const config = {
 transformIgnorePatterns: [
  `[\\\/]node_modules[\\\/](?!(${librariesToRecompile})).*$`
 ],
 transform: {
  '^.+\.jsx?$': path.resolve(__dirname, 'transformer.js')
 }
};

以上配置是 Jest 重新編譯你的庫所需要的。有兩個主要部分,我會一一解釋。

transformIgnorePatterns 是 Jest 配置的一個功能,它是一個正則字符串數組。任何匹配這些正則表達式的代碼,都不會被 babel-jest 重新編譯。默認是一個字符串“node_modules”。這就是為什麼Jest 不會重新編譯任何庫代碼。

當我們提供了自定義配置,就是告訴 Jest 重新編譯的時候如何忽略代碼。也就是為什麼你剛才看到的變態的正則表達式有一個負向先行斷言在裏面,目的是為了匹配除了庫以外的所有代碼。換句話說,我們告訴 Jest 忽略 node_modules 中除了指定庫之外的所有代碼。

這又一次證明了 JavaScript 配置比 JSON 配置要好,因為我可以輕鬆地通過字符串操作,往正則表達式里插入庫名字的數組拼接。

第二個是 transform 配置,他指向一個自定義的 babel-jest 轉換器。我不敢100%確定這個是必須的,但我還是加上了。設置它用於在重新編譯所有代碼時加載我們的 Babel 配置。

// Babel-Jest 轉換器
const babelJest = require('babel-jest');
const path = require('path');
const cwd = process.cwd();
const babelConfig = require(path.resolve(cwd, 'babel.config'));
module.exports = babelJest.createTransformer(babelConfig);

這些都配置好后,你在測試代碼應該又能跑了。記住了,任何使用庫的 es2015 模塊都需要這樣配置,不然測試代碼跑不動。

Npm/Yarn Link 就是魔鬼

接下來輪到另一個痛點了:鏈接庫。使用 npm/yarn 鏈接的過程就是創建一個指向本地項目目錄的符號鏈接。結果表明,Babel 在重新編譯通過這種方式鏈接的庫時,會拋出很多錯誤。我之所以花了這麼長時間才弄清楚 Jest 這檔子事兒,原因之一就是我一直通過這種方式鏈接我的庫,出現了一堆錯誤。

解決辦法就是:不要使用 npm/yarn link。用類似 “yalc” 這樣的工具,它可以連接本地項目,同時能模擬正常的 npm 安裝過程。它不但沒有 Babel 重編譯的問題,還能更好地處理傳遞性依賴。

針對特定庫的優化。

如果完成了以上所有步驟,你的應用基本上實現了比較健壯的 tree shaking。不過,為了進一步減少文件包大小,你還可以做一些事情。我會列舉一些特定庫的優化方法,但這絕對不是全部。它尤其能為我們提供靈感,做出一些更酷的事情。

MomentJS 是出了名的大體積庫。幸好它可以剔除多語言包來減少體積。在下面的代碼示例中,我排除了 momentjs 所有的多語言包,只保留了基本部分,體積明顯小了很多。

// 用 IgnorePlugin 移除多語言包
const { IgnorePlugin } from 'webpack';
const config = {
 plugins: [
  new IgnorePlugin(/^\.\/locale$/, /moment/)
 ]
};

Moment-Timezone 是 MomentJS 的老表,也是個大塊頭。它的體積基本上是一個帶有時區信息的超大 JSON 文件導致的。我發現只要保留本世紀的年份數據,就可以將體積縮小90%。這種情況需要用到一個特殊的 Webpack 插件。

// MomentTimezone Webpack Plugin
const MomentTimezoneDataPlugin = require('moment-timezone-data-webpack-plugin');
const config = {
 plugins: [
  new MomentTimezoneDataPlugin({
   startYear: 2018,
   endYear: 2100
  })
 ]
};

Lodash 是另一個導致文件包膨脹的大塊頭。幸好有一個替代包 Lodash-es,它被編譯成 es2015 模塊,並帶有 sideEffects 標誌。用它替換 Lodash 可以進一步縮減包的大小。

另外,Lodash-es,react-bootstrap 以及其他庫可以在 Babel transform imports 插件的幫助下實現瘦身。該插件從庫的 index.js 文件讀取 import 語句,並使其指向庫中特定文件。這樣就使 webpack 在解析模塊樹時更容易對庫做 tree shaking。下面的例子演示了它是如何工作的。

// Babel Transform Imports
// Babel config
const config = {
 plugins: [
  [
   'transform-imports',
   {
    'lodash-es': {
     transform: 'lodash/${member}',
     preventFullImport: true
    },
    'react-bootstrap': {
     transform: 'react-bootstrap/es/${member}', // The es folder contains es2015 module versions of the files
     preventFullImport: true
    }
   }
  ]
 ]
};
// 這些庫不再支持全量導入,否則會報錯
import _ from 'lodash-es';
// 具名導入依然支持
import { debounce } from 'loash-es';
// 不過這些具名導入會被babel編譯成這樣子
// import debounce from 'lodash-es/debounce';

總結

全文到此結束。這樣的優化可以極大地縮減打包后的大小。隨着前端架構開始有了新的方向(比如微前端),保持包大小最優化變得比以往更加重要。希望本文能給那些正在給應用程序做tree shaking的同學帶來一些幫助。

交流

歡迎掃碼關注微信公眾號“1024譯站”,為你奉上更多技術乾貨。

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

【其他文章推薦】

※想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

※不管是台北網頁設計公司、台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置,台北網頁設計,各種案例分享

Springboot 自動配置淺析

Introduction

我們知道,SpringBoot之所以強大,就是因為他提供了各種默認的配置,可以讓我們在集成各個組件的時候從各種各樣的配置文件中解放出來。

拿一個最普通的 web 項目舉例。我們需要使用 servlet 容器,SpringBoot 就提供了嵌入式的 Tomcat 作為默認容器,不需要一行配置就能直接以最普通的 Java 程序的方式啟動:java -jar;接收請求需要一個網絡端口,默認配置好8080;處理請求需要 servlet 的多線程特性,默認配置好了最大線程數為200;處理好的請求以Restful 風格返回給調用方,SpringBoot 默認配置好了jackson進行 json 序列化,業務代碼需要做的只是返回一個 POJO 對象;連接池直接就默認配置了性能最好的的 Hikari,以及連接池的默認尺寸為10……在一個簡單的web應用中,這些配置我們甚至都可能不了解或者沒有意識到它們的存在,就可以讓程序正常運行。

這就是自動配置的魔力——潤物細無聲。

那麼自動配置是怎麼實現的呢?本文就從POM文件和 @SpringBootApplication 註解來簡單分析一下自動配置原理。

真的只是簡單地,分析一下。

POM文件

環境

  • SpringBoot 2.0.6

父項目

在每一個 SpringBoot 項目一開始的 POM 文件中,就有一個父依賴:

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.0.6.RELEASE</version>
  <relativePath/> <!-- lookup parent from repository -->
</parent>

點進artifactId之後來到 spring-boot-starter-parent-2.0.6.RELEASE.pom 文件,這個文件還有一個父依賴:

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.0.6.RELEASE</version>
  <relativePath>../../spring-boot-dependencies</relativePath>
</parent>

再繼續點進去來到 spring-boot-dependencies-2.0.6.RELEASE.pom 文件中。在該文件的 <properties> 標籤中可以看到各種各樣的組件的版本信息, <dependencyManagement> 標籤中聲明了各個組件的maven坐標信息。其中,我數了一下,在 SpringBoot 2.0.6 中一個有177個帶有版本信息的組件名,包括了大家熟悉的 elasticsearch、activemq、redis、kafka等等。

這裏(spring-boot-dependencies-2.0.6.RELEASE.pom文件)稱為 SpringBoot 的“版本仲裁中心”,它是用來管理 SpringBoot 應用裏面的所有依賴版本的地方。 它包含了大部分開發中會用到的一些組件的版本,所以我們導入依賴默認是不需要寫版本號的,SpringBoot 會自動幫我們配置需要的版本。這樣在引入某個組件的時候不用考慮這個組件和系統中已有的組件兼不兼容之類的問題,避免了煩人的版本衝突問題。(當然,沒有在 dependencies裏面管理的依賴自然需要聲明版本號)

啟動器(Starters)

父項目做版本仲裁,那麼真正的 jar 包是從哪裡導入進來的呢?這就要說到我們的starters了。點開web-starter可以看到它幫我們導入了web模塊正常運行所依賴的組件,就不用我們一個一個手動導入了。

所以,所謂的Starters就是一系列依賴描述的組合,我們可以通過導入這些starters就會有相應的依賴了並可以基於他們進行相應的開發

Springboot把開發中中會遇到的場景都抽象成一個個的starter,比如(),只需要在項目裏面引入這些starter 相關場景的所有依賴都會導入進來。要用什麼功能就導入什麼場景的啟動器

@SpringBootApplication

一個典型的 springboot 項目的入口類如下所示:

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

只要運行main方法就能啟動該應用。main 中運行 run 方法時需要傳入一個類,這個類正是使用@SpringBootApplication註解標註的類,如果沒有這個註解程序是跑不起來的。

這個註解標註在某個類上就說明這個類是 springboot 的主配置類,springboot 就應該運行這個類的 main 方法來啟動springboot應用

點開這個註解的源碼,可以看到這是一個組合註解,最主要的是這兩個註解:

  • @SpringBootConfiguration

  • @EnableAutoConfiguration

@SpringBootConfiguration標註在某個類上,表示這是一個 SpringBoot 的配置類,它的底層是@Configuration,屬於spring的底層註解,標註了這個註解的類表名這個類是一個配置類,取代以前開發中的xml文件配置,同時也表明這個類是 spring 容器中的組件,受容器管理。

接下來是@EnableAutoConfiguration註解,它的用處是開啟自動配置,使用 spring 開發需要手動配置的東西,現在由 springboot 幫我們配置了,具體的實現就是通過這個註解來實現的。該組合註解有兩個。

1. @AutoConfigurationPackage

它的 註解中的核心代碼是 @Import({Registrar.class})。

@Import 的作用就是為容器中導入一個它指定的組件。

在 Registrar 這個類中有一個方法叫registerBeanDefinitions,用於註冊一些 bean 定義信息:

public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
     AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
}

打個斷點在這一行代碼上,運行

(new AutoConfigurationPackages.PackageImport(metadata)).getPackageName()

得到的結果是主配置類所在的包名,目的就是將主配置類所在包及下面所有子包裏面的所有組件掃描到Spring容器中。同時也說明了,如果在主配置所在包的上層包添加組件的話是不會被掃描到的、不起作用的。

2. @Import({AutoConfigurationImportSelector.class})

該註解導入了一個自動配置的選擇器,真正地給容器中導入 springboot 自動幫我們配置好的配置類。在 AutoConfigurationImportSelector 這個選擇器中的 getAutoConfigurationEntry 方法中,以全限定類名的方式把所有需要的配置類導入 springboot 容器中。這些全限定類型所在文件路徑為:

org/springframework/boot/spring-boot-autoconfigure/2.1.8.RELEASE/spring-boot-autoconfigure-2.1.8.RELEASE.jar!/META-INF/spring.factories

上述兩個註解一個負責掃描我們將要加容器中的類,一個加入 springboot 為我們自動配置好的類,springboot 通過這兩種方式省略了我們大量的配置工作。

總結

本文從用於maven項目管理的pom.xml文件和標註在啟動類上的@SpringBootApplication註解,簡單分析了springboot 自動配置的實現。前者通過版本仲裁中心為我們維護項目組件的版本,防止依賴衝突;後者通過在加載程序的時候導入數以百計的自動配置類實現自動配置。

原文發表於:https://pengcheng.site/2019/10/28/springboot-zi-dong-pei-zhi-qian-xi/

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

【其他文章推薦】

※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※南投搬家前需注意的眉眉角角,別等搬了再說!

比亞迪計畫在廣州增城設廠 組裝K9電動大巴

日前,有媒體報導稱,比亞迪高層與廣州市長洽談在廣州增城投資建廠事宜,屆時增城將雲集廣汽本田、北汽和比亞迪三大汽車品牌,成為廣州汽車產業最為集中的區域。

比亞迪向媒體確認,其總裁王傳福及高級副總裁吳經勝此前一段時間的確曾赴廣州,與廣州市市長陳建華洽談在廣州投資建廠事宜。此項目與比亞迪在陝西、雲南等地的模式類似,依舊是為其公交電動化做準備。目前比亞迪仍在積極籌備還有天津、昆明、武漢等地的建廠項目,也是其在新能源推廣的地方保護背景下的無奈之舉。

比亞迪的電動大巴在國內具有較強的優勢,目前已經出口到荷蘭、巴西、美國等地,目前在國內由於地方保護,只有在當地合資設廠,是打入當地市場的一個有效市場。而電動大巴的組裝線投資不大,也是比亞迪較能接受的方式。

為了推動電動車的發佈,比亞迪率先推出了“零元購車、零成本、零排放”城市公交電動化解決方案,為加速公交電動化進程開闢一條現實可行的道路。在深圳、西安、寶雞、韶關、荷蘭、新加坡、美國、丹麥、德國、英國倫敦等地成功實現電動車的規模化、商業化運營。

比亞迪總部所在廣州運行的電動公交大巴超過千輛,成為電動大巴運行最多的城市。而百公里之外的廣州,目前擁有超過1萬輛的公交大巴,其中新能源車為2000輛,少數為電動大巴,主要廣汽客車品牌;計程車約有3萬輛,電動車計程車幾乎為零,這給比亞迪很大的期望值。

比亞迪也在做廣州的工作。在2012廣州國際馬拉松賽上,作為本屆馬拉松比賽獨家汽車贊助商,比亞迪旗下的城市多功能SUV車型S6、T動力智慧新典範G6以及純電動車e6成為賽事的指定用車。

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

北汽新能源全新戰略:十三五末期產能達80萬輛以上

在產業佈局方面,北汽新能源將以“1(北京采育基地)+2(常州基地、青島基地)+I(北汽集團內部傳統乘用車生產基地)+P(社會合作夥伴生產資源)>80”為基礎,在十三五末期形成80萬輛以上生產能力,年產銷50萬輛規模,打造年營業收入600億元、上市市值1000億元的企業。

在研發方面,北汽新能源將推進“1496”戰略,1:打造1個世界級科技創新中心,具備正向開發能力;4:構建4層次研發體系;9:在研發總部建成9大研發中心;5:整合全球資源,組建5大海外研發中心。此外,在北汽新能源的目標中,其還將爭創新能源汽車行業最佳雇主品牌,價值鏈研發團隊人數達到5000以上,專業領域國際級人才占比達到10%以上,經營團隊中具有國際視野的複合型人才占比60%以上。

產品方面,北汽新能源將構建3大技術平臺(共用平臺、協同平臺、全新平臺),同樣將實現3大維度(大中小、高中低、234)全面發展,打造三款年銷量突破10萬輛的明星車型。基於國內當前新能源汽車市場發展特點,北汽新能源短期內聚焦A級以下車型,並根據市場發展適時啟動B、C級高端車型儲備開發。

今年,北汽新能源將推出首款純電動6萬元高性價比國民純電動車也將在2016年迎來投放,實現不靠地方補貼國內市場全覆蓋。2017年,公司將推出採用全鋁框架超輕量化車身設計的EX系列純電動精品微型小車。

根據規劃,北汽新能源未來每年將研發4-6款新車型,形成“2、3、4”(即續航里程200公里、300公里、400公里)、“高、中、低”(即高檔、中檔、標配三個級別)、“大、中、小”(即車體大小)完整新能源汽車產品組合,為消費者提供更加豐富的選擇。

密集的產品投放加上車型續航能力不斷升級,這些利好都將為北汽新能源持續保持國內純電動市場第一、全球前四的發展目標奠定堅實基礎。而在擴張產能的同時,有效利用起北汽集團以及合作夥伴的資源,則可以大幅縮減開支,獲得更高的利潤。

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

※網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦、台中網頁設計公司推薦專業設計師”嚨底家”!!

BYD:電動車銷量目標連三年翻倍

比亞迪(BYD)董事長兼總裁王傳福在接受中國媒體《財新網》訪談時表示,BYD看好電動車市場的發展,未來三年的銷售目標是逐年倍增,且到2020年時,電動車將取代傳統汽車成為汽車銷售的主力。

去年全球電動車市場火熱,中國市場的銷量提升了三倍,躍升為全球最大電動車市場。BYD的電動車也跟著成長了三倍,產值來到人民幣(下同)220億元。BYD同時發展私家車、電動巴士、電動卡車與電動計程車等產品,屬多角化經營;目前,全球已有160餘座城市看得到BYD電動公車的身影,包含倫敦、阿姆斯特丹、洛杉磯、京都與50多個中國城市。

王傳福指出,特斯拉(Tesla)專注於生產高端私家電動汽車,此一路線與BYD的多角化經營有所不同,因此不會出現直接競爭。此外,BYD會逐步減少傳統汽車的投資比例,未來將逐漸轉為以電動車為主。

王傳福認為,北京政府對電動車的補助確實有助於銷售成長,但中國電動車市場已具一定的規模效應,生產成本會繼續下降。加上民眾對電動車的認同度持續上升,因此即使未來補貼逐漸減少,電動車的銷量也不會受到太大的衝擊。

據統計,BYD的電動車銷售量在2015年12月來到1.09萬輛,月增41.4%。全年銷量6.17萬輛,已超越Nissan、Tesla成為全球電動車銷售冠軍。其中以插電式油電混和車「秦」與「唐」的銷量貢獻最多,達86%;其餘銷量則來自純電動車。

(照片來源:BYD)

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

【其他文章推薦】

※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計、台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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

上海推出新能源汽車充電樁綜合保險

近期,上海保險業與國家電網積極合作,推出充電樁綜合保險產品。該產品包括充電樁財產保險和充電樁用電安全責任保險。充電樁財產保險保額最高1萬元,充電樁用電安全責任保險保額3萬元。

合作初期,上海相關險企採取贈送方式,建立專項資金池,向新能源車主贈送推廣充電樁保險。同時也在國家電網營業廳設點,向預報樁客戶宣傳介紹充電樁綜合保險的相關產品和服務。

據瞭解,2015年7月1日起,上海正式實施《上海市電動汽車充電基礎設施建設管理暫行規定》。7月1日後,上海市民必須提供政府備案的充電服務機構出具的“充電設施已裝證明”,才能在銷售企業辦理購買新能源汽車手續,必須安裝好充電樁後才能獲得國家和地方政府補貼、並申請到新能源車免費牌照。此外,《規定》也明確充電設施所有權人應當承擔充電設施維修更新養護及侵害第三者權益責任。

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

【其他文章推薦】

※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

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

上汽、廣汽等十家車企聯合成立電動汽車產業聯盟

近日,上汽、一汽、東風、長安、廣汽、北汽、重汽、華晨、奇瑞、江淮這十家國內車企聯合成立了“電動汽車產業聯盟”,意在聯手攻克節能與新能源汽車在產業化過程中遇到的種種難關。

中國汽車工業協會秘書長董揚稱,聯盟是2015年7月11日成立的,銷售額排名居前十位的國內汽車企業集團“一把手”悉數參會,會上各方探討了電動汽車聯合行動的問題,並制定出針對十家企業的《電動汽車發展共同行動綱要》。

根據綱要內容,確定了“積極引領、聯合行動、突出重點、創新發展”十六字戰略方針,並且成立了T10電動汽車領導小組、電動汽車標準項目工作組,在統一標準方面著手研究,打算如具體工業專案一樣開展工作。

目前聯盟企業正在就關鍵零部件、關鍵總成聯合開發的問題進行調研,調研之後將形成統一規劃,今後的發展方式有可能是一部分技術難題大家共同攻關、投資生產,也有可能各自攻關,誰的成果好其他企業再去共用,儘量追求效益,聯合發展。

據悉,今後該聯盟還將統一制定新能源汽車的相關標準,比如對電池的規格、電機以及電控系統的統一標準,並且將考慮國內的資源情況,而不是像傳統汽車那樣照搬國際標準。

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

【其他文章推薦】

※想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

※不管是台北網頁設計公司、台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置,台北網頁設計,各種案例分享

廣西建成國首條無線供電車道 電動汽車可邊行駛邊充電

1月19日,廣西電網公司“面向智慧電網的無線電能傳輸關鍵技術研究”項目通過驗收,宣告國內首條為電動汽車無線供電的車道建成,車輛在其上行駛,可即時、無線獲得電能,目前已投入使用。

據瞭解,該專案是南方電網於2012年下達的科技項目,由廣西電網有限責任公司電力科學研究院負責實施,項目成功建設了國內首條電動汽車無線供電小型試驗車道,研製出國內首套輸配電線路監測終端無線供電系統。相關專家介紹,該專案攻克了電動汽車無線充電技術、無線供電技術和電能轉換3個技術難點。基於無線電能傳輸技術,電動汽車在行駛過程中,可通過車道直接給電動汽車電機供電,不需要車載電池;也可以同時為車載電池充電。目前,該項目已申請發明專利9件(均已實審,其中授權7件),授權實用新型專利20件。

目前,該車道提供的最大功率為30千瓦,以總電池容量為24千瓦時的電動汽車為例,如果以30千瓦的功率進行充電,充滿電所需時間不足1小時,續航里程100公里。推廣使用後,該車道可以根據需求安裝任意長度,甚至可以取代車載電池,有效解決目前電動汽車電池重、電池續航力低等難題,尤其適用於固定線路電動車輛如電動公交、景區迴圈車等。

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

【其他文章推薦】

※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※南投搬家前需注意的眉眉角角,別等搬了再說!

財政部等五部委發佈十三五新能源汽車充電基礎設施獎勵政策

1月19日從財政部獲悉,財政部、科技部、工業和資訊化部、發展改革委、國家能源局等五部委聯合發佈《關於“十三五”新能源汽車充電基礎設施獎勵政策及加強新能源汽車推廣應用的通知》,旨在加快推動新能源汽車充電基礎設施建設,培育良好的新能源汽車應用環境,2016-2020年中央財政將繼續安排資金對充電基礎設施建設、運營給予獎補。

通知規定獎補物件,中央財政充電基礎設施建設運營獎補資金是對充電基礎設施配套較為完善、新能源汽車推廣應用規模較大的省(區、市)政府的綜合獎補。

通知確定獎勵資金使用範圍,獎補資金應當專門用於支持充電設施建設運營、改造升級、充換電服務網路運營監控系統建設等相關領域。地方應充分利用財政資金杠杆作用,調動包括政府機關、街道辦事處和居委會、充電設施建設和運營企業、物業服務等在內的相關各方積極性,對率先開展充電設施建設運營、改造升級、解決充電難題的單位給予適當獎補,並優先用於支援《國務院辦公廳關於加快電動汽車充電基礎設施建設的指導意見》確定的相關重點任務。

通知設定新能源充電設施獎勵標準,對於大氣污染治理重點省市獎勵最高,2016年大氣污染治理重點省市推廣量3萬輛,獎補標準9000萬元,超出門檻部分獎補最高封頂1.2億元。2020年大氣污染治理重點省市獎勵門檻7萬輛,獎補標準1.26億元。

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

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦、台中網頁設計公司推薦專業設計師”嚨底家”!!