標籤: 銷售文案

  • 同是2.0T+7速雙離合,為何那麼多人放棄大眾買貴4萬的豪車?

    同是2.0T+7速雙離合,為何那麼多人放棄大眾買貴4萬的豪車?

    我曾經從一位一汽的裝配工口中聽過這樣一句話:大眾的車底盤焊縫要求不能伸進指甲就合格,但是。奧迪的要求是不能伸進頭髮。上面我們說到了兩車的造車成本以及測試種類,那就不免出現一個更加專業名詞——NVH,直白一點說就是車輛的胎噪,風噪和發動機噪音。

    在如今這個車水馬龍的城市中,滿大街都是車,而車一多,咱們買車選車就自然變得更加困難了。而且中國人買車需要的可不單單隻是性價比,更需要的是面子,並且一定要大。

    特別是當BBA這種豪華品牌不斷的將車型價格下探之後,吸引了更多的中國人可以花普通品牌的價錢買到掛着“豪華品牌商標”的汽車,這也是為什麼如今奔馳、寶馬、奧迪這些車會滿大街跑的原因。

    當然,相比起豪華品牌的紙醉金迷,普通品牌的優勢更多在於極高性價比。不過不可否認的是,豪華品牌的品牌溢價能力,的確是普通品牌無法比擬的。

    並且豪華車除了可以給人帶來身份的象徵,還帶來了更極佳的駕乘感受,畢竟“豪華”兩字已經說明了一切。如今各類豪華品牌的入門車型售價,儼然已經可以跟普通品牌針鋒相對了。

    那在面對預算有限的情況下,到底是咬咬牙弄台豪華品牌,享受一下豪車帶來的優越感呢,還是輕鬆的拿下普通品牌,得到其極具性價比配置的同時還能多買幾台蘋果XXX呢?

    如果有一天,你開着一輛頂配大眾高爾夫GTI去相親,對方父母只會認為你混得一般,但是當你開着一輛掛這“四個環”的丐版奧迪A3去相親,人家則會認為,這家孩子不錯,年紀輕輕就開得起奧迪了。

    一輛頂配的GTI比起低配的奧迪A3,怎麼著也得貴上好幾萬吧,但是為什麼人人都會認為奧迪更高級呢。

    奧迪嘛,在國人心目中一直存在着“官車”的形象,而大眾則是普羅大眾都消費得起的存在。而且再怎麼說你也只是大眾,我可是奧迪啊,世界公認的三大豪華品牌之一,買車看的不正是牌子,面子嘛!

    不過很多朋友都認為,A3其實就是換標高爾夫,而且那個標還不便宜。那這次咱們就以高爾夫GTI以及奧迪A3兩廂頂配來說話。到底用幾台蘋果XXX的錢去買一個四環標值不值得?

    目前兩車在廣州的裸車價基本是:奧迪A3頂配23萬左右,高爾夫GTI19萬左右,也就是四萬左右的差價,雖然不同地區情況不同,不過差價相信基本也是相差無幾。

    相信有了解過這兩款車的朋友都知道,兩者同為一汽旗下合資公司的產品、同是MQB平台、同一工廠生產、相同的EA888發動機、相同的變速箱。聽起來兩者的差異性極小,但是細分之後你就知道還是有區別的。

    相同的EA888發動機,在奧迪A3頂配身上只能輸出190匹馬力,但是在GTI身上則能爆發出220馬力。兩者都匹配了7速雙離合變速器,不過在大眾身上叫DSG,在奧迪身上喊S tronic而已。

    從平台上看,兩者均出於MQB橫置模塊化平台,這就不難讓人認為兩者的底盤變現以及操控性上是不是一致呢?事實卻並非如此。就好比一台計算機,平台就如同一個主板,再裝配不同的顯卡,聲卡,內存,CpU,不同搭配所得出的效果自然也是截然不同的。

    另外,雖然奧迪有着品牌溢價,但相對應的供應商也會與大眾有所區別,零配件的成本也會相應的提高,並且在裝配工藝上奧迪也高於大眾。。單單從用漆方面就可以看得出這點,奧迪A3大部分採用的均為高成本、高品質的珠光漆。

    還有奧迪需要做的測試種類,無論是整車測試還是零部件試驗的嚴苛程度,複雜性,精密度,都遠遠高於大眾。我曾經從一位一汽的裝配工口中聽過這樣一句話:大眾的車底盤焊縫要求不能伸進指甲就合格,但是………奧迪的要求是不能伸進頭髮!!!

    上面我們說到了兩車的造車成本以及測試種類,那就不免出現一個更加專業名詞——NVH,直白一點說就是車輛的胎噪,風噪和發動機噪音。從各大測評機構的測試結果都不難看出,奧迪A3對NVH的把控要比高爾夫好,另一方面也表明了,豪華品牌在駕乘感受上下的功夫要比大眾這種普通品牌更多。

    至於外觀,咱們就略過不說了,反正蘿蔔青菜各有所愛,奧迪自帶四環光環,高爾夫GTI則滿身情懷。內飾的話,GTI也不能說完敗,只能說那萬年不變的布局真的讓人又愛又恨,而A3的內飾那才真的稱得上人人喜愛,既精緻又簡潔還顯高級感,而且細看內飾的做工用料,無論是視覺的衝擊力還是觸感的細膩度上,相信什麼叫奧迪,什麼叫大眾自然一目瞭然了。

    不過在配置的差異上,奧迪A3比起GTI似乎真的閹割太多了,要知道那可是四萬塊的差價啊。此外,奧迪選裝配置的價錢也非常感人,一套真皮座椅就小一萬了,估計想選裝到GTI這麼豐富配置的時候你就得多掏幾百張毛爺爺了,不得不說奧迪這生意還真好賺!

    估計看到這裏,大家都不難得出一個結論:奧迪A3當然不是換標高爾夫那麼簡單。

    儘管GTI的馬力、配置都比A3更優越,但是從用料做工、質感品控、駕乘感受方面來看,A3比起GTI則是更勝一籌。終究是豪華品牌嘛,還是會有自己的擔當,哪怕是十幾萬起步的入門車型,他們都會用心去對待。所以與其說多花了幾萬塊錢要裝逼買個豪華商標,倒不如老老實實的承認“一分錢一分貨”這個道理。

    再舉個例子,如果奧迪A3賣着高爾夫的價錢,我估計你也不大敢買吧?又或者說,奧迪如果把A3的價格降低到高爾夫這個層次,我相信一定會大大衝擊高爾夫的銷量,而且奧迪作為豪華品牌的形象也會因此大打折扣。在汽車界,類似的例子比比皆是,所以單單拿平台來說事,似乎就有點無稽之談了。

    至於遇到類似的問題該怎麼去選擇,那就只能看個人需求以及兜里毛爺爺的多與少啦。反正有錢的話,那必須選豪華品牌,去酒吧把鑰匙往桌子一拍,嘿嘿,那效應老司機都懂都懂!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※教你寫出一流的銷售文案?

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

    ※回頭車貨運收費標準

    ※別再煩惱如何寫文案,掌握八大原則!

    ※超省錢租車方案

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 玩車的人都在捧的本田發動機,真的這麼神?

    玩車的人都在捧的本田發動機,真的這麼神?

    然而,這種熟悉的“爆tec”感覺沒有出現在最新的謳歌TLX-L上。謳歌是本田的豪華品牌,一切的用料,技術,做工等都用上高標準。就連本田被人詬病的隔音謳歌也能做得安靜。被拉高轉的本田發動機“爆tec”時的感覺聲音和動力都有着明顯變化,讓人為之興奮,但開着謳歌TLX-L卻沒有這種感覺。

    本田,一個無人不知無人不曉的一個汽車品牌。尤其是在廣東地區,更是本田粉活躍的地方,大街上跑的10輛車中,有4輛或5輛是本田車型。

    買本田車,除了買質量可靠性外,更多的是為了“買發動機送車”,而坊間也流傳着本田金句VTEC is the best。可想而知,本田發動機技術含量和民望有多高。

    本田發動機的看家技術VTEC和i-VTEC技術外,在近年也推出VTEC Turbo這渦輪組合技術。

    那本田技術牛在哪?主要除了有可變氣門正時外,還有氣門升程這一技術。氣門升程技術並不是很多發動機有,除了寶馬和英菲尼迪VQ37系列外,還真是寥寥無幾。

    開過本田的人未必知道什麼叫“爆tec”,但拉過高轉的人就知道。當發動機轉速拉到5000轉左右的時候,聲音突變,發動機也感覺顫抖一下。這就是“爆tec”給人的感覺。

    然而,這種熟悉的“爆tec”感覺沒有出現在最新的謳歌TLX-L上。謳歌是本田的豪華品牌,一切的用料,技術,做工等都用上高標準。就連本田被人詬病的隔音謳歌也能做得安靜。

    被拉高轉的本田發動機“爆tec”時的感覺聲音和動力都有着明顯變化,讓人為之興奮,但開着謳歌TLX-L卻沒有這種感覺。難道就因為高端車型而被“技術”隔絕?

    其實這種“爆tec”的感覺在自主品牌的渦輪車上有着“更好的體現”。只不過我們吐槽它而已,這一技術就是渦輪遲滯。體驗過大部分自主品牌渦輪車,輕踩油門不去,踩重一點像是被踢出去的感覺。這種感覺比“爆tec”還爽快。

    總結:高轉“爆tec”還是低轉渦輪介入明顯,都使得發動機動力輸出沒那麼“線性”。只是拉高轉的時候,亢奮的聲音和動力的凸顯使得“爆tec”的感覺被神化。渦輪介入明顯,其實就是“爆tec”時動力凸顯的一部分,只是沒有亢奮的聲音罷了。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※超省錢租車方案

    ※別再煩惱如何寫文案,掌握八大原則!

    ※回頭車貨運收費標準

    ※教你寫出一流的銷售文案?

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

  • 深入理解React:懶加載(lazy)實現原理

    目錄

    • 代碼分割
    • React的懶加載
      • import() 原理
      • React.lazy 原理
      • Suspense 原理
    • 參考

    1.代碼分割

    (1)為什麼要進行代碼分割?

    現在前端項目基本都採用打包技術,比如 Webpack,JS邏輯代碼打包後會產生一個 bundle.js 文件,而隨着我們引用的第三方庫越來越多或業務邏輯代碼越來越複雜,相應打包好的 bundle.js 文件體積就會越來越大,因為需要先請求加載資源之後,才會渲染頁面,這就會嚴重影響到頁面的首屏加載。

    而為了解決這樣的問題,避免大體積的代碼包,我們則可以通過技術手段對代碼包進行分割,能夠創建多個包並在運行時動態地加載。現在像 Webpack、 Browserify等打包器都支持代碼分割技術。

    (2)什麼時候應該考慮進行代碼分割?

    這裏舉一個平時開發中可能會遇到的場景,比如某個體積相對比較大的第三方庫或插件(比如JS版的PDF預覽庫)只在單頁應用(SPA)的某一個不是首頁的頁面使用了,這種情況就可以考慮代碼分割,增加首屏的加載速度。

    2.React的懶加載

    示例代碼:

    import React, { Suspense } from 'react';
    
    const OtherComponent = React.lazy(() => import('./OtherComponent'));
    
    function MyComponent() {
      return (
        <div>
          <Suspense fallback={<div>Loading...</div>}>
            <OtherComponent />
          </Suspense>
        </div>
      );
    }
    

    如上代碼中,通過 import() React.lazySuspense 共同一起實現了 React 的懶加載,也就是我們常說了運行時動態加載,即 OtherComponent 組件文件被拆分打包為一個新的包(bundle)文件,並且只會在 OtherComponent 組件渲染時,才會被下載到本地。

    那麼上述中的代碼拆分以及動態加載究竟是如何實現的呢?讓我們來一起探究其原理是怎樣的。

    import() 原理

    import() 函數是由TS39提出的一種動態加載模塊的規範實現,其返回是一個 promise。在瀏覽器宿主環境中一個import()的參考實現如下:

    function import(url) {
      return new Promise((resolve, reject) => {
        const script = document.createElement("script");
        const tempGlobal = "__tempModuleLoadingVariable" + Math.random().toString(32).substring(2);
        script.type = "module";
        script.textContent = `import * as m from "${url}"; window.${tempGlobal} = m;`;
    
        script.onload = () => {
          resolve(window[tempGlobal]);
          delete window[tempGlobal];
          script.remove();
        };
    
        script.onerror = () => {
          reject(new Error("Failed to load module script with URL " + url));
          delete window[tempGlobal];
          script.remove();
        };
    
        document.documentElement.appendChild(script);
      });
    }
    

    當 Webpack 解析到該import()語法時,會自動進行代碼分割。

    React.lazy 原理

    以下 React 源碼基於 16.8.0 版本

    React.lazy 的源碼實現如下:

    export function lazy<T, R>(ctor: () => Thenable<T, R>): LazyComponent<T> {
      let lazyType = {
        $$typeof: REACT_LAZY_TYPE,
        _ctor: ctor,
        // React uses these fields to store the result.
        _status: -1,
        _result: null,
      };
    
      return lazyType;
    }
    

    可以看到其返回了一個 LazyComponent 對象。

    而對於 LazyComponent 對象的解析:

    ...
    case LazyComponent: {
      const elementType = workInProgress.elementType;
      return mountLazyComponent(
        current,
        workInProgress,
        elementType,
        updateExpirationTime,
        renderExpirationTime,
      );
    }
    ...
    
    function mountLazyComponent(
      _current,
      workInProgress,
      elementType,
      updateExpirationTime,
      renderExpirationTime,
    ) { 
      ...
      let Component = readLazyComponentType(elementType);
      ...
    }
    
    // Pending = 0, Resolved = 1, Rejected = 2
    export function readLazyComponentType<T>(lazyComponent: LazyComponent<T>): T {
      const status = lazyComponent._status;
      const result = lazyComponent._result;
      switch (status) {
        case Resolved: {
          const Component: T = result;
          return Component;
        }
        case Rejected: {
          const error: mixed = result;
          throw error;
        }
        case Pending: {
          const thenable: Thenable<T, mixed> = result;
          throw thenable;
        }
        default: { // lazyComponent 首次被渲染
          lazyComponent._status = Pending;
          const ctor = lazyComponent._ctor;
          const thenable = ctor();
          thenable.then(
            moduleObject => {
              if (lazyComponent._status === Pending) {
                const defaultExport = moduleObject.default;
                lazyComponent._status = Resolved;
                lazyComponent._result = defaultExport;
              }
            },
            error => {
              if (lazyComponent._status === Pending) {
                lazyComponent._status = Rejected;
                lazyComponent._result = error;
              }
            },
          );
          // Handle synchronous thenables.
          switch (lazyComponent._status) {
            case Resolved:
              return lazyComponent._result;
            case Rejected:
              throw lazyComponent._result;
          }
          lazyComponent._result = thenable;
          throw thenable;
        }
      }
    }
    

    注:如果 readLazyComponentType 函數多次處理同一個 lazyComponent,則可能進入Pending、Rejected等 case 中。

    從上述代碼中可以看出,對於最初 React.lazy() 所返回的 LazyComponent 對象,其 _status 默認是 -1,所以首次渲染時,會進入 readLazyComponentType 函數中的 default 的邏輯,這裏才會真正異步執行 import(url)操作,由於並未等待,隨後會檢查模塊是否 Resolved,如果已經Resolved了(已經加載完畢)則直接返回moduleObject.default(動態加載的模塊的默認導出),否則將通過 throw 將 thenable 拋出到上層。

    為什麼要 throw 它?這就要涉及到 Suspense 的工作原理,我們接着往下分析。

    Suspense 原理

    由於 React 捕獲異常並處理的代碼邏輯比較多,這裏就不貼源碼,感興趣可以去看 throwException 中的邏輯,其中就包含了如何處理捕獲的異常。簡單描述一下處理過程,React 捕獲到異常之後,會判斷異常是不是一個 thenable,如果是則會找到 SuspenseComponent ,如果 thenable 處於 pending 狀態,則會將其 children 都渲染成 fallback 的值,一旦 thenable 被 resolve 則 SuspenseComponent 的子組件會重新渲染一次。

    為了便於理解,我們也可以用 componentDidCatch 實現一個自己的 Suspense 組件,如下:

    class Suspense extends React.Component {
      state = {
        promise: null
      }
    
      componentDidCatch(err) {
        // 判斷 err 是否是 thenable
        if (err !== null && typeof err === 'object' && typeof err.then === 'function') {
          this.setState({ promise: err }, () => {
            err.then(() => {
              this.setState({
                promise: null
              })
            })
          })
        }
      }
    
      render() {
        const { fallback, children } = this.props
        const { promise } = this.state
        return <>{ promise ? fallback : children }</>
      }
    }
    

    小結

    至此,我們分析完了 React 的懶加載原理。簡單來說,React利用 React.lazyimport()實現了渲染時的動態加載 ,並利用Suspense來處理異步加載資源時頁面應該如何显示的問題。

    3.參考

    代碼分割– React

    動態import – MDN – Mozilla

    proposal-dynamic-import

    React Lazy 的實現原理

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

    【其他文章推薦】

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

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

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

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

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

    ※教你寫出一流的銷售文案?

  • 隨機抽樣一致性(RANSAC)算法詳解

    隨機抽樣一致性(RANSAC)算法詳解

    隨機抽樣一致性(RANSAC)算法能夠有效的剔除特徵匹配中的錯誤匹配點。

    實際上,RANSAC能夠有效擬合存在噪聲模型下的擬合函數。實際上,RANSAC算法的核心在於將點劃分為“內點”和“外點”。在一組包含“外點”的數據集中,採用不斷迭代的方法,尋找最優參數模型,不符合最優模型的點,被定義為“外點”。這就是RANSAC的核心思想。

    RANSAC原理

    OpenCV中濾除誤匹配對採用RANSAC算法尋找一個最佳單應性矩陣H,矩陣大小為3×3。RANSAC目的是找到最優的參數矩陣使得滿足該矩陣的數據點個數最多,通常令h33=1來歸一化矩陣。由於單應性矩陣有8個未知參數,至少需要8個線性方程求解,對應到點位置信息上,一組點對可以列出兩個方程,則至少包含4組匹配點對

     

     

     RANSAC算法從匹配數據集中隨機抽出4個樣本並保證這4個樣本之間不共線,計算出單應性矩陣,然後利用這個模型測試所有數據,並計算滿足這個模型數據點的個數與投影誤差(即代價函數),若此模型為最優模型,則對應的代價函數最小。

    損失函數:

     

     

     也就是通過隨機抽樣求解得到一個矩陣,然後驗證其他的點是否符合模型,然後符合的點成為“內點”,不符合的點成為“外點”。下次依然從“新的內點集合”中抽取點構造新的矩陣,重新計算誤差。最後誤差最小,點數最多就是最終的模型。

    RANSAC算法步驟:

    RANSAC算法步驟: 

              1. 隨機從數據集中隨機抽出4個樣本數據 (此4個樣本之間不能共線),計算出變換矩陣H,記為模型M;

              2. 計算數據集中所有數據與模型M的投影誤差,若誤差小於閾值,加入內點集 I ;

              3. 如果當前內點集 I 元素個數大於最優內點集 I_best , 則更新 I_best = I,同時更新迭代次數k ;

              4. 如果迭代次數大於k,則退出 ; 否則迭代次數加1,並重複上述步驟;

      注:迭代次數k在不大於最大迭代次數的情況下,是在不斷更新而不是固定的;

     

     

     其中,p為置信度,一般取0.995;w為”內點”的比例 ; m為計算模型所需要的最少樣本數=4;
    關於RANSAC算法的思想,可以用下圖表示

     

     也就是RANSAC算法的本質是:在存在噪聲的數據中,我們求解一個模型,使得非噪聲數據可以用該模型表示,而噪聲數據被排除在外。

    分享三個講解RANSAC算法的網址:

    https://www.csdn.net/gather_2d/MtjaMg3sNDAwNS1ibG9n.html

    https://www.cnblogs.com/xrwang/archive/2011/03/09/ransac-1.html

    https://blog.csdn.net/yanghan742915081/article/details/83005442

     

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

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

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

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

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • C++值元編程

    C++值元編程

    ——永遠不要在OJ上使用值元編程,過於簡單的沒有優勢,能有優勢的編譯錯誤。

    背景

    2019年10月,我在學習算法。有一道作業題,輸入規模很小,可以用打表法解決。具體方案有以下三種:

    1. 運行時預處理,生成所需的表格,根據輸入直接找到對應項,稍加處理后輸出;

    2. 一個程序生成表格,作為提交程序的一部分,後續與方法1相同,這樣就省去了運行時計算的步驟;

    3. 以上兩種方法結合,編譯期計算表格,運行時直接查詢,即元編程(metaprogramming)。

    做題當然是用方法1或2,但是元編程已經埋下了種子。時隔大半年,我來補上這個坑。

    題目

    北京大學OpenJudge 百練4119 複雜的整數劃分問題

    描述

    將正整數 \(n\) 表示成一系列正整數之和,\(n = n_1 + n_2 + … + n_k\),其中 \(n_1 \geq n_2 \geq … \geq n_k \geq 1\)\(k \geq 1\)。正整數 \(n\) 的這種表示稱為正整數 \(n\) 的劃分。

    輸入

    標準的輸入包含若干組測試數據。每組測試數據是一行輸入數據,包括兩個整數 \(N\)\(K\)。( \(0 \le N \leq 50\)\(0 \le K \leq N\)

    輸出

    對於每組測試數據,輸出以下三行數據:

    第一行: \(N\) 劃分成 \(K\) 個正整數之和的劃分數目

    第二行: \(N\) 劃分成若干個不同正整數之和的劃分數目

    第三行: \(N\) 劃分成若干個奇正整數之和的劃分數目

    樣例輸入

    5 2
    

    樣例輸出

    2
    3
    3
    

    提示

    第一行: 4+1,3+2

    第二行: 5,4+1,3+2

    第三行: 5,1+1+3,1+1+1+1+1+1

    解答

    標準的動態規劃題。用dp[c][i][j]表示把i分成c個正整數之和的方法數,其中每個數都不超過j

    第一行。初始化:由 \(i \leq j\) 是否成立決定dp[1][i][j]的值,當 \(i \leq j\) 時為1,劃分為 \(i = i\),否則無法劃分,值為0

    遞推:為了求dp[c][i][j],對 \(i = i_1 + i_2 + … + i_c\)\(i_1 \geq i_2 \geq … \geq i_c\) 中的最大數 \(i_1\) 分類討論,最小為 \(1\),最大不超過 \(i – 1\),因為 \(c \geq 2\),同時不超過 \(j\),因為定義。最大數為 \(n\) 時,對於把 \(i – n\) 分成 \(c – 1\) 個數,每個數不超過 \(n\) 的劃分,追加上 \(n\) 可得 \(i\) 的一個劃分。\(n\) 只有這些取值,沒有漏;對於不同的 \(n\),由於最大數不一樣,兩個劃分也不一樣,沒有多。故遞推式為:

    \[dp[c][i][j] = \sum_{n=1}^{min\{i-1,j\}}dp[c-1][i-n][n] \]

    dp[K][N][N]即為所求ans1[K][N]

    第二行。可以把遞推式中的dp[c - 1][i - n][n]修改為dp[c - 1][i - n][n - 1]后重新計算。由於只需一個與c無關的結果,可以省去c這一維度,相應地改變遞推順序,每輪累加。

    另一種方法是利用已經計算好的ans1數組。設 \(i = i_1 + i_2 + … + + i_{c-1} + i_c\),其中 \(i_1 \ge i_2 \ge … \ge i_{c+1} \ge i_c \ge 0\),則 \(i_1 – \left( c-1 \right) \geq i_2 – \left( c-2 \right) \geq … \geq i_{c-1} – 1 \geq i_c \ge 0\),且 \(\left( i_1 – \left( c-1 \right) \right) + \left( i_2 – \left( c-2 \right) \right) + … + \left( i_{c-1} – 1 \right) + \left( i_c \right) = i – \frac {c \left( c-1 \right)} {2}\),故把i劃分成c個不同正整數之和的劃分數目等於ans[c][i - c * (c - 1) / 2],遍歷c累加即得結果。

    第三行。想法與第二行相似,也是找一個對應,此處從略。另外,數學上可以證明,第二行和第三行的結果一定是一樣的。

    #include <iostream>
    #include <algorithm>
    
    constexpr int max = 50;
    int dp[max + 1][max + 1][max + 1] = { 0 };
    int ans1[max + 1][max + 1] = { 0 };
    int ans2[max + 1] = { 0 };
    int ans3[max + 1] = { 0 };
    
    int main()
    {
        int num, k;
        for (int i = 1; i <= max; ++i)
            for (int j = 1; j <= max; ++j)
                dp[1][i][j] = i <= j;
        for (int cnt = 2; cnt <= max; ++cnt)
            for (int i = 1; i <= max; ++i)
                for (int j = 1; j <= max; ++j)
                {
                    auto min = std::min(i - 1, j);
                    for (int n = 1; n <= min; ++n)
                        dp[cnt][i][j] += dp[cnt - 1][i - n][n];
                }
        for (int cnt = 1; cnt <= max; ++cnt)
            for (int i = 1; i <= max; ++i)
                ans1[cnt][i] = dp[cnt][i][i];
        for (int i = 1; i <= max; ++i)
            for (int cnt = 1; cnt <= i; ++cnt)
            {
                int j = i - cnt * (cnt - 1) / 2;
                if (j <= 0)
                    break;
                ans2[i] += ans1[cnt][j];
            }
        for (int i = 1; i <= max; ++i)
            for (int cnt = 1; cnt <= i; ++cnt)
            {
                int j = i + cnt;
                if (j % 2)
                    continue;
                j /= 2;
                ans3[i] += ans1[cnt][j];
            }
        
        while (std::cin >> num)
        {
            std::cin >> k;
            std::cout << ans1[k][num] << std::endl;
            std::cout << ans2[num] << std::endl;
            std::cout << ans3[num] << std::endl;
        }
    }
    

    值元編程基礎

    元編程是指計算機程序能把其他程序作為它們的數據的編程技術。在目前的C++中,元編程體現為用代碼生成代碼,包括宏與模板。當我們使用了std::vector<int>中的任何一個名字時,std::vector類模板就用模板參數int, std::allocator<int>實例化為std::vector<int, std::allocator<int>>模板類,這是一種元編程,不過我們通常不這麼講。

    狹義的C++模板元編程(template metaprogramming,TMP)包括值元編程、類型元編程,以及兩者的相交。本文討論的是值元編程,即為編譯期值編程。

    在C++中有兩套工具可用於值元編程:模板和constexpr。C++模板是圖靈完全的,這是模板被引入C++以後才被發現的,並不是C++模板的初衷,因此用模板做計算在C++中算不上一等用法,導致其語法比較冗長複雜。constexpr的初衷是提供純正的編譯期常量,後來才取消對計算的限制,但不能保證計算一定在編譯期完成。總之,這兩套工具都不完美,所以本文都會涉及。

    嚴格來說,constexpr不符合上述對元編程的定義,但它確實可以提供運行時程序需要的數據,所以也歸入元編程的類別。

    constexpr式值元編程

    constexpr開始講,是因為它與我們在C++中慣用的編程範式——過程式範式是一致的。

    constexpr關鍵字在C++11中被引入。當時,constexpr函數中只能包含一條求值語句,就是return語句,返回值可以用於初始化constexpr變量,作模板參數等用途。如果需要分支語句,用三目運算符?:;如果需要循環語句,用函數遞歸實現。比如,計算階乘:

    constexpr int factorial(int n)
    {
        return n <= 1 ? 1 : (n * factorial(n - 1));
    }
    

    對於編譯期常量ifactorial(i)產生編譯期常量;對於運行時值jfactorial(j)產生運行時值,也就是說,constexpr可以視為對既有函數的附加修飾。

    然而,多數函數不止有一句return語句,constexpr對函數體的限制使它很難用於中等複雜的計算任務,為此C++14放寬了限制,允許定義局部變量,允許if-elseswitch-casewhilefor等控制流。factorial函數可以改寫為:

    constexpr int factorial(int n)
    {
        int result = 1;
        for (; n > 1; --n)
            result *= n;
        return result;
    }
    

    也許你會覺得factorial函數的遞歸版本比循環版本易懂,那是因為你學習遞歸時接觸的第一個例子就是它。對於C++開發者來說,大多數情況下首選的還是循環。

    計算單個constexpr值用C++14就足夠了,但是傳遞數組需要C++17,因為std::arrayoperator[]從C++17開始才是constexpr的。

    整數劃分問題的constexpr元編程實現需要C++17標準:

    #include <iostream>
    #include <utility>
    #include <array>
    
    constexpr int MAX = 50;
    
    constexpr auto calculate_ans1()
    {
        std::array<std::array<std::array<int, MAX + 1>, MAX + 1>, MAX + 1> dp{};
        std::array<std::array<int, MAX + 1>, MAX + 1> ans1{};
        constexpr int max = MAX;
        for (int i = 1; i <= max; ++i)
            for (int j = 1; j <= max; ++j)
                dp[1][i][j] = i <= j;
        for (int cnt = 2; cnt <= max; ++cnt)
            for (int i = 1; i <= max; ++i)
                for (int j = 1; j <= max; ++j)
                {
                    auto min = std::min(i - 1, j);
                    for (int n = 1; n <= min; ++n)
                        dp[cnt][i][j] += dp[cnt - 1][i - n][n];
                }
        for (int cnt = 1; cnt <= max; ++cnt)
            for (int i = 1; i <= max; ++i)
                ans1[cnt][i] = dp[cnt][i][i];
        return ans1;
    }
    
    constexpr auto calculate_ans2()
    {
        constexpr auto ans1 = calculate_ans1();
        std::array<int, MAX + 1> ans2{};
        constexpr int max = MAX;
        for (int i = 1; i <= max; ++i)
            for (int cnt = 1; cnt <= i; ++cnt)
            {
                int j = i - cnt * (cnt - 1) / 2;
                if (j <= 0)
                    break;
                ans2[i] += ans1[cnt][j];
            }
        return ans2;
    }
    
    int main()
    {
        constexpr auto ans1 = calculate_ans1();
        constexpr auto ans2 = calculate_ans2();
    
        for (int cnt = 1; cnt <= 10; ++cnt)
        {
            for (int i = 1; i <= 10; ++i)
                std::cout << ans1[cnt][i] << ' ';+
            std::cout << std::endl;
        }
        std::cout << std::endl;
        for (int i = 1; i <= 50; ++i)
            std::cout << ans2[i] << ' ';
        std::cout << std::endl;
    
        int num, k;
        while (std::cin >> num)
        {
            std::cin >> k;
            std::cout << ans1[k][num] << std::endl;
            std::cout << ans2[num] << std::endl;
            std::cout << ans2[num] << std::endl;
        }
    }
    

    模板式值元編程

    模板式與C++11中的constexpr式類似,必須把循環化為遞歸。事實上C++模板是一門函數式編程語言,對值元編程和類型元編程都是如此。

    程序控制流有三種基本結構:順序、分支與循環。

    順序

    在函數式編程中,數據都是不可變的,函數總是接受若干參數,返回若干結果,參數和結果是不同的變量;修改原來的變量是不允許的。對於C++模板這門語言,函數是類模板,也稱“元函數”(metafunction);參數是模板參數;運算結果是模板類中定義的靜態編譯期常量(在C++11以前,常用enum來定義;C++11開始用constexpr)。

    比如,對於參數 \(x\),計算 \(x + 1\)\(x ^ 2\) 的元函數:

    template<int X>
    struct PlusOne
    {
        static constexpr int value = X + 1;
    };
    
    template<int X>
    struct Square
    {
        static constexpr int value = X * X;
    };
    

    這裏假定運算數的類型為int。從C++17開始,可以用auto聲明非類型模板參數。

    順序結構,是對數據依次進行多個操作,可以用函數嵌套來實現:

    std::cout << PlusOne<1>::value << std::endl;
    std::cout << Square<2>::value << std::endl;
    std::cout << Square<PlusOne<3>::value>::value << std::endl;
    std::cout << PlusOne<Square<4>::value>::value << std::endl;
    

    或者藉助constexpr函數,回歸熟悉的過程式範式:

    template<int X>
    struct SquareAndIncrease
    {
        static constexpr int calculate()
        {
            int x = X;
            x = x * x;
            x = x + 1;
            return x;
        }
        static constexpr int value = calculate();
    };
    
    void f()
    {
        std::cout << SquareAndIncrease<5>::value << std::endl;
    }
    

    過程式方法同樣可以用於分支和循環結構,以下省略;函數式方法可以相似地用於值元編程與類型元編程,所以我更青睞(主要還是逼格更高)。

    分支

    C++模板元編程實現分支的方式是模板特化與模板參數匹配,用一個額外的帶默認值的bool類型模板參數作匹配規則,特化falsetrue的情形,另一種情形留給主模板。

    比如,計算 \(x\) 的絕對值:

    template<int X, bool Pos = (X > 0)>
    struct AbsoluteHelper
    {
        static constexpr int value = X;
    };
    
    template<int X>
    struct AbsoluteHelper<X, false>
    {
        static constexpr int value = -X;
    };
    

    如果你怕用戶瞎寫模板參數,可以再包裝一層:

    template<int X>
    struct Absolute : AbsoluteHelper<X> { };
    
    void g()
    {
        std::cout << Absolute<6>::value << std::endl;
        std::cout << Absolute<-7>::value << std::endl;
    }
    

    標準庫提供了std::conditional及其輔助類型std::conditional_t用於模板分支:

    template<bool B, class T, class F>
    struct conditional;
    

    定義了成員類型type,當B == true時為T,否則為F

    模板匹配實際上是在處理switch-case的分支,bool只是其中一種簡單情況。對於對應關係不太規則的分支語句,可以用一個constexpr函數把參數映射到一個整數或枚舉上:

    enum class Port_t
    {
        PortB, PortC, PortD, PortError,
    };
    
    constexpr Port_t portMap(int pin)
    {
        Port_t result = Port_t::PortError;
        if (pin < 0)
            ;
        else if (pin < 8)
            result = Port_t::PortD;
        else if (pin < 14)
            result = Port_t::PortB;
        else if (pin < 20)
            result = Port_t::PortC;
        return result;
    }
    
    template<int Pin, Port_t Port = portMap(Pin)>
    struct PinOperation;
    
    template<int Pin>
    struct PinOperation<Pin, Port_t::PortB> { /* ... */ };
    
    template<int Pin>
    struct PinOperation<Pin, Port_t::PortC> { /* ... */ };
    
    template<int Pin>
    struct PinOperation<Pin, Port_t::PortD> { /* ... */ };
    

    如果同一個模板有兩個參數分別處理兩種分支(這已經從分支上升到模式匹配了),或同時處理分支和循環的特化,總之有兩個或以上維度的特化,需要注意兩個維度的特化是否會同時滿足,如果有這樣的情形但沒有提供兩參數都特化的模板特化,編譯會出錯。見problem2::Accumulator,它不需要提供兩個參數同時特化的版本。

    循環

    如前所述,循環要化為遞歸,循環的開始與結束是遞歸的起始與終點或兩者對調,遞歸終點的模板需要特化。比如,還是計算階乘:

    template<int N>
    struct Factorial
    {
        static constexpr int value = N * Factorial<N - 1>::value;
    };
    
    template<>
    struct Factorial<0>
    {
        static constexpr int value = 1;
    };
    

    或許階乘的遞歸定義很大程度上來源於數學,那就再看一個平方和的例子:

    template<int N>
    struct SquareSum
    {
        static constexpr int value = SquareSum<N - 1>::value + N * N;
    };
    
    template<>
    struct SquareSum<0>
    {
        static constexpr int value = 0;
    };
    

    \(1^2 + 2^2 + \cdots + n^2 = \frac {n \left( n + 1 \right) \left( 2n + 1\right)} {6}\)

    好吧,還是挺數學的,去下面看實例感覺一下吧,那裡還有break——哦不,被我放到思考題中去了。

    加群是交換群,求和順序不影響結果,上面這樣的順序寫起來方便。有些運算符不滿足交換律,需要逆轉順序。還以平方和為例:

    template<int N, int Cur = 0>
    struct SquareSumR
    {
        static constexpr int value = Cur * Cur + SquareSumR<N, Cur + 1>::value;
    };
    
    template<int N>
    struct SquareSumR<N, N>
    {
        static constexpr int value = N * N;
    };
    

    遞歸

    遞歸在過程式中是一種高級的結構,它可以直接轉化為函數式的遞歸,後面會提到兩者的異同。

    比如,計算平方根,這個例子來源於C++ Templates: The Complete Guide 2e:

    // primary template for main recursive step
    template<int N, int LO = 1, int HI = N>
    struct Sqrt {
        // compute the midpoint, rounded up
        static constexpr auto mid = (LO + HI + 1) / 2;
        // search a not too large value in a halved interval
        using SubT = std::conditional_t<(N < mid * mid),
                                       Sqrt<N, LO, mid - 1>,
                                       Sqrt<N, mid, HI>>;
        static constexpr auto value = SubT::value;
    };
    // partial specialization for end of recursion criterion
    template<int N, int S>
    struct Sqrt<N, S, S> {
        static constexpr auto value = S;
    };
    

    這個遞歸很容易化為循環,有助於你對循環化遞歸的理解。

    存儲

    實際應用中我們可能不需要把所有計算出來的值存儲起來,但在打表的題目中需要。存儲一系列數據需要用循環,循環的實現方式依然是遞歸。比如,存儲階乘(Factorial類模板見上):

    template<int N>
    inline void storeFactorial(int* dst)
    {
        storeFactorial<N - 1>(dst);
        dst[N] = Factorial<N>::value;
    }
    
    template<>
    inline void storeFactorial<-1>(int* dst)
    {
        ;
    }
    
    void h()
    {
        constexpr int MAX = 10;
        int factorial[MAX + 1];
        storeFactorial<MAX>(factorial);
        for (int i = 0; i <= MAX; ++i)
            std::cout << factorial[i] << ' ';
        std::cout << std::endl;
    }
    

    多維數組同理,例子見下方。注意,函數模板不能偏特化,但有靜態方法的類模板可以,這個靜態方法就充當原來的模板函數。

    雖然我們是對數組中的元素挨個賦值的,但編譯器的生成代碼不會這麼做,即使不能優化成所有數據一起用memcpy,至少能做到一段一段拷貝。

    類內定義的函數隱式成為inline,手動寫上inline沒有語法上的意義,但是對於一些編譯器,寫上以後函數被內聯的可能性更高,所以寫inline是一個好習慣。

    解答

    #include <iostream>
    #include <algorithm>
    
    constexpr int MAX = 50;
    
    namespace problem1
    {
    
    template<int Count, int Num, int Max>
    struct Partition;
    
    template<int Count, int Num, int Loop>
    struct Accumulator
    {
        static constexpr int value = Accumulator<Count, Num, Loop - 1>::value + Partition<Count, Num - Loop, Loop>::value;
    };
    
    template<int Count, int Num>
    struct Accumulator<Count, Num, 0>
    {
        static constexpr int value = 0;
    };
    
    template<int Count, int Num, int Max = Num>
    struct Partition
    {
        static constexpr int value = Accumulator<Count - 1, Num, std::min(Num - 1, Max)>::value;
    };
    
    template<int Num, int Max>
    struct Partition<1, Num, Max>
    {
        static constexpr int value = Num <= Max;
    };
    
    template<int Count, int Num>
    struct Store
    {
        static inline void store(int* dst)
        {
            Store<Count, Num - 1>::store(dst);
            dst[Num] = Partition<Count, Num>::value;
        }
    };
    
    template<int Count>
    struct Store<Count, 0>
    {
        static inline void store(int* dst)
        {
            ;
        }
    };
    
    template<int Count>
    inline void store(int (*dst)[MAX + 1])
    {
        store<Count - 1>(dst);
        Store<Count, MAX>::store(dst[Count]);
    }
    
    template<>
    inline void store<0>(int (*dst)[MAX + 1])
    {
        ;
    }
    
    inline void store(int(*dst)[MAX + 1])
    {
        store<MAX>(dst);
    }
    
    }
    
    namespace problem2
    {
    
    template<int Num, int Count = Num, int Helper = Num - Count * (Count - 1) / 2, bool Valid = (Helper > 0)>
    struct Accumulator
    {
        static constexpr int value = Accumulator<Num, Count - 1>::value + problem1::Partition<Count, Helper>::value;
    };
    
    template<int Num, int Count, int Helper>
    struct Accumulator<Num, Count, Helper, false>
    {
        static constexpr int value = Accumulator<Num, Count - 1>::value;
    };
    
    template<int Num, int Helper, bool Valid>
    struct Accumulator<Num, 0, Helper, Valid>
    {
        static constexpr int value = 0;
    };
    
    template<int Num>
    inline void store(int* dst)
    {
        store<Num - 1>(dst);
        dst[Num] = Accumulator<Num>::value;
    }
    
    template<>
    inline void store<0>(int* dst)
    {
        ;
    }
    
    inline void store(int* dst)
    {
        store<MAX>(dst);
    }
    
    }
    
    int ans1[MAX + 1][MAX + 1];
    int ans2[MAX + 1];
    
    int main()
    {
        problem1::store(ans1);
        problem2::store(ans2);
        int num, k;
        while (std::cin >> num)
        {
            std::cin >> k;
            std::cout << ans1[k][num] << std::endl;
            std::cout << ans2[num] << std::endl;
            std::cout << ans2[num] << std::endl;
        }
    }
    

    請對照運行時版本自行理解。

    討論

    constexpr

    constexpr不保證計算在編譯期完成,大部分編譯器在Debug模式下把所有可以推遲的constexpr計算都推遲到運行時完成。但constexpr可以作為一個強有力的優化提示,原本在最高優化等級都不會編譯期計算的代碼,在有了constexpr后編譯器會儘力幫你計算。如果編譯器實在做不到,根據你是否強制編譯期求值,編譯器會給出錯誤或推遲到運行時計算。在不同的編譯器中,這類行為的表現是不同的——眾所周知MSVC對constexpr的支持不好。

    目前(C++17)沒有任何方法可以檢查一個表達式是否是編譯期求值的,但是有方法可以讓編譯器對於非編譯期求值表達式給出一個錯誤,把期望constexpr的表達式放入模板參數或static_assert表達式都是可行的:如果編譯期求值,則編譯通過;否則編譯錯誤。

    (C++20:constevalis_constant_evaluated

    模板

    如果我們把Sqrt中的遞歸替換為如下語句:

    static constexpr auto value = (N < mid * mid) ? Sqrt<N, LO, mid - 1>::value
                                                  : Sqrt<N, mid, HI>::value;
    

    顯然計算結果是相同的,看上去還更簡潔。但是問題在於,編譯器會把Sqrt<N, LO, mid - 1>Sqrt<N, mid, HI>兩個類都實例化出來,儘管只有一個模板類的value會被使用到。這些類模板實例繼續導致其他實例產生,最終將產生 \(O \left( n \log n \right)\) 個實例。相比之下,把兩個類型名字傳給std::conditional並不會導致類模板被實例化,std::conditional只是定義一個類型別名,對該類型求::value才會實例化它,一共產生 \(O \left( \log n \right)\) 個實例。

    還有一個很常見的工具是變參模板,我沒有介紹是因為暫時沒有用到,而且我怕寫出非多項式複雜度的元程序。如果我還有機會寫一篇類型元編程的話,肯定會包含在其中的。

    函數式

    循環的一次迭代往往需要上一次迭代的結果,對應地在遞歸中就是函數對一個參數的結果依賴於對其他 \(n\) 個參數的結果。有些問題用遞歸解決比較直觀,但是如果 \(n \geq 2\),計算過程就會指數爆炸,比如:

    int fibonacci(int n)
    {
        if (n <= 2)
            return 1;
        else
            return fibonacci(n - 2) + fibonacci(n - 1);
    }
    

    計算fibonacci(30)已經需要一點點時間了,而計算fibonacci(46)(4字節帶符號整型能容納的最大斐波那契數)就很慢了。把這種遞歸轉化為循環,就是設計一個動態規劃算法的過程。然而函數式中的遞歸與過程式中的循環可能有相同的漸近複雜度:

    template<int N>
    struct Fibonacci
    {
        static constexpr int value = Fibonacci<N - 2>::value + Fibonacci<N - 1>::value;
    };
    
    template<>
    struct Fibonacci<1>
    {
        static constexpr int value = 1;
    };
    
    template<>
    struct Fibonacci<2>
    {
        static constexpr int value = 1;
    };
    

    因為只有Fibonacci<1>Fibonacci<46>這46個類模板被實例化,是 \(O \left( n \right)\) 複雜度的。

    在題目中,由於表中的所有數據都有可能用到,並且運行時不能執行計算,所以要把所有數據都計算出來。實際問題中可能只需要其中一個值,比如我現在就想知道不同整數的劃分問題對 \(50\) 的答案是多少,就寫:

    std::cout << problem2::Accumulator<50>::value << std::endl;
    

    那麼problem1::PartitionCount參數就不會超過10,不信的話你可以加一句static_assert。實例化的模板數量一共只有2000多個,而在完整的問題中這個數量要翻100倍不止。這種性質稱為惰性求值,即用到了才求值。惰性求值是必需的,總不能窮盡模板參數的所有可能組合一一實例化出來吧?

    函數式編程語言可以在運行時實現這些特性。

    性能

    我愧對這個小標題,因為C++值元編程根本沒有性能,時間和空間都是。類型元編程也許是必需,至於值元編程,emm,做點簡單的計算就可以了,這整篇文章都是反面教材。

    思考題2用GCC編譯,大概需要10分鐘;用MSVC編譯,出現我聞所未聞的錯誤:

    因為編譯器是32位的,4GB內存用完了就爆了。

    停機問題

    一個很有趣的問題是編譯器對於死循環的行為。根據圖靈停機問題,編譯器無法判斷它要編譯的元程序是否包含死循環,那麼它在遇到死循環時會怎樣表現呢?當然不能跟着元程序一起死循環,constexpr的循環次數與模板的嵌套深度都是有限制的。在GCC中,可以用-fconstexpr-depth-fconstexpr-loop-limit-ftemplate-depth等命令行參數來控制。

    思考題

    1. problem2::AccumulatorCount == 0Count == Num都要實例化,但其實只需實例化到 \(O \left( \sqrt{n} \right)\) 就可以了,試改寫之。

    2. 洛谷 NOIp2016提高組D2T1 組合數問題,用元編程實現。

      • 只需完成 \(n \leq 100, m \leq 100\) 的任務點;

      • 使用64位編譯器(指編譯器本身而非目標代碼),給編譯器億點點時間;

      • 不要去網站上提交,我已經試過了,編譯錯誤。

      • 測試數據下載。

    題目描述

    組合數 \(\binom {n} {m}\) 表示的是從 \(n\) 個物品中選出 \(m\) 個物品的方法數。舉個例子,從 \(\left( 1, 2, 3 \right)\) 三個物品中選擇兩個物品可以有 \(\left( 1, 2 \right), \left( 1, 3 \right), \left( 2, 3 \right)\) 這三種選擇方法。根據組合數的定義,我們可以給出計算組合數 \(\binom {n} {m}\) 的一般公式

    \[\binom {n} {m} = \frac {n!} {m! \left( n-m \right) !} \,, \]

    其中 \(n! = 1 \times 2 \times \cdots \times n\);特別地,定義 \(0! = 1\)

    小蔥想知道如果給定 \(n\)\(m\)\(k\),對於所有的 \(0 \leq i \leq n, 0 \leq j \leq \min \left( i, m \right)\) 有多少對 \(\left( i, j \right)\) 滿足 \(k \mid \binom {i} {j}\)

    輸入格式

    第一行有個兩個整數 \(t, k\),其中 \(t\) 代表該測試點總共有多少組測試數據,\(k\) 的意義見問題描述。

    接下來 \(t\) 行每行兩個整數 \(n, m\),其中 \(n, m\) 的意義見問題描述。

    輸出格式

    \(t\) 行,每行一個整數代表所有的 \(0 \leq i \leq n, 0 \leq j \leq \min \left( i, m \right)\) 有多少對 \(\left( i, j \right)\) 滿足 \(k \mid \binom {i} {j}\)

    輸入輸出樣例

    【輸入#1】

    1 2
    3 3
    

    【輸出#1】

    1
    

    【輸入#2】

    2 5
    4 5
    6 7
    

    【輸出#2】

    0 7
    

    說明/提示

    【樣例1說明】

    在所有可能的情況中,只有 \(\binom {2} {1} = 2\) 一種情況是 \(2\) 的倍數。

    【子任務】

    測試點 \(n\) \(m\) \(k\) \(t\)
    1 \(\leq 3\) $ \leq 3$ \(= 2\) $ = 1$
    2 \(= 3\) \(\leq 10^4\)
    3 \(\leq 7\) $ \leq 7$ \(= 4\) $ = 1$
    4 \(= 5\) \(\leq 10^4\)
    5 \(\leq 10\) $ \leq 10$ \(= 6\) $ = 1$
    6 \(= 7\) \(\leq 10^4\)
    7 \(\leq 20\) $ \leq 100$ \(= 8\) $ = 1$
    8 \(= 9\) \(\leq 10^4\)
    9 \(\leq 25\) $ \leq 2000$ \(=10\) $ = 1$
    10 \(=11\) \(\leq 10^4\)
    11 \(\leq 60\) $ \leq 20$ \(=12\) $ = 1$
    12 \(=13\) \(\leq 10^4\)
    13 \(\leq 100\) $ \leq 25$ \(=14\) $ = 1$
    14 \(=15\) \(\leq 10^4\)
    15 $ \leq 60$ \(=16\) $ = 1$
    16 \(=17\) \(\leq 10^4\)
    17 \(\leq 2000\) $ \leq 100$ \(=18\) $ = 1$
    18 \(=19\) \(\leq 10^4\)
    19 $ \leq 2000$ \(=20\) $ = 1$
    20 \(=21\) \(\leq 10^4\)
    • 對於全部的測試點,保證 \(0 \leq n, m \leq 2 \times 10^3, 1 \leq t \leq 10^4\)

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

    【其他文章推薦】

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

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

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

    南投搬家公司費用需注意的眉眉角角,別等搬了再說!

    ※教你寫出一流的銷售文案?

  • C++ Primer Plus(一)

    完整閱讀C++ Primer Plus 

      系統重新學習C++語言部分,記錄重要但易被忽略的,關鍵但易被遺忘的。

     

    預備

      1、C++相對於C增加了最關鍵的兩項,面向對象和范型編程。

     

    處理數據

      2、對於變量明,C++沒有長度限制;同時,以兩個下劃線或一個下劃線和大寫字母開頭的名稱被保留給實現(編譯器及其使用的資源)使用;以一個下劃線開頭的名稱被保留給實現,用作全局標識符。

      3、C++11提供一種大括號初始化器,可以用它初始化任何類型。

    1 int ham = {24};
    2 int ems{7};
    3 int roc = {}; // 為0
    4 int rhs{};

      4、對於整型字面值,如果第一位為1~9則為十進制;如果第一位為0,第二位為1~7,則是八進制;如果前兩位為0x或0X,則為十六進制。如果希望使用cout輸出八進制或十六進制格式的整數,可以使用cout的控制符(這三個控制符都被包含在std命名空間)。

    1 cout << dec << a; // 10進制,默認的
    2 cout << oct << b; // 8進制
    3 cout << hex << c; // 16進制

      5、cout.put()、cin.get()的用法,可參照C語言中get()與put()的用法。

      6、C++有一種表示特殊字符的機制,他獨立於鍵盤,被稱作通用字符名。通用字符名以\u或\U開頭,前者後面是8個十六進制位,後者後面則是16個十六進制位。這些位表示的是ISO10646碼點。

      7、對於寬字符類型wcha_t,cin和cout無法很好的處理,此時應該使用wcinwcout

      8、C++11新增了char16_t和char32_t類型,C++11使用前綴u表示前者,U表示後者;並與形式為\u00F6和\U0000222B的通用字符名匹配。

    1 u'C' u“be good”   U'R' U”dirty rat”

      

    複合類型

      9、cin使用空白(空格、製表符、換行符)來確定字符串結束的位置,而cin.getline()可以依據換行符來讀取整行,並且可以制定最多讀取字符的數量。

      10、可以使用沒有名稱的結構類型,方法是省略名稱,同時定義一個結構類型和一個這種類型的變量,不常用,可用作臨時變量。

      11、C++允許對一個整數強制類型轉換為一個枚舉值,並參与賦值操作;同時可以有多個值相同的枚舉值,目前枚舉值也可以使用long,long long類型的值。對於較小的值編譯器會使用一個字節甚至更少的的字節,對於包含long類型的枚舉,會使用4個字節。

      12、在C中允許給指針直接賦字面值,但C++不允許,必須進行類型轉換。

     

    循環和關係表達式

      13、前綴遞增,前綴遞減,解除引用運算符的優先級相同,以從右往左的方式進行結合;後綴遞增,後綴遞減的優先級相同,但比前綴運算符的優先級高,以從左往右的方式結合。

      14、 cin.get(ch)和cin.get()的區別。

    屬性 cin.get(ch) cin.get()
    傳遞輸入字符的方式 賦值給參數ch 將函數返回值賦給ch
    用於字符輸入時函數的返回值  istream對象(執行bool轉換後為true)  int類型的字符編碼
    到達EOF時函數的返回值   istream對象(執行bool轉換後為false) EOF 

     

    函數——C++編程模塊

      15、如果數據類型本身並不是指針,則可以將const數據或者非const數據的地址賦給指向const的指針,但只能將非const數據的地址賦給非const指針

     

    函數探幽

      16、函數重載后,在調用函數時,如果沒有完全相同的參數類型,編譯器會做強制類型轉換進行匹配,但如果有多個可轉換的類型(也就是說有多個重載過的函數),C++將拒絕這種函數調用。

      17、函數重載的關鍵是函數的參數列表,也成為特徵標,以下兩個聲明互斥:

    1 long gronk(int n, float m);
    2 double gronk(int n, float m);

      C++不允許這種方式的重載,返回類型可以不同,但特徵標必須也不同。

      18、對於重載了引用參數的函數,C++將選擇調用最匹配的版本,這使得能夠根據參數是左值引用,const類型的左值引用還是右值引用來定製函數的行為。

      19、 函數模板並非函數定義,在使用模板時,編譯器會針對特定的類型生成函數實例,這種實例化方式被稱為隱式實例化

      20、C++允許顯式實例化,也就是說可以針對特定類型使用模板生成特定的實例。

    1 template void Swap<int>(int, int); // 用<>指定類型,在聲明前加template

      它的語義為“使用Swap()模板生成int類型的函數定義”。

      與顯式實例化不同的是,顯式具體化使用下面兩個等價的聲明之一:

    1 template<> void Swap<int>(int, int);
    2 template<> void Swap(int, int);

      它們的語義是,“不要使用Swap模板來生產函數定義,而應使用專門為int類型显示地定義的函數定義”。

      21、還可以在程序中創建顯式實例化:

    1 template <class T>
    2 T Add(T a, T b){ return a + b; }
    3 int m = 6;
    4 double n = 9.8;
    5 cout << Add<double>(m, n) << endl;

      由於這裏显示實例化中的特定類型為double,所以變量m會被強制類型轉換成double類型。

      22、對於函數重載函數模板函數模板重載,C++將選擇哪個版本?

      請看這裏———>   C++ 函數重載,函數模板和函數模板重載,選擇哪一個?

      23、C++11,在函數模板中,當無法得知一個變量的值時,可以使用decltype關鍵字來決定返回值類型:

    1 template<class T1, class T2>
    2 void ft(T1 x, T2 y)
    3 {
    4     decltype(x+y) xpy = x + y; // 此時xpy的類型就是x+y后的類型
    5 }

      24、decltype關鍵字本質上更複雜一些,編譯器必須遍歷一個核對錶去確定類型,現在有如下聲明:

    1 decltype(expression) var;

      第一,expression是一個沒有用括號括起來的標識符,則var的類型與該標識符相同,包括const等限定符。

      第二,如果expression是一個函數調用,則於與函數的返回值相同,這裏並不執行函數,只是查看返回值類型。

      第三,如果expression是一個左值,並且expression是被括號括起的,var會是引用類型,否則第一步就會處理。

      第四,到了這裏,var的類型只能與expression相同。

      25、C++11,在函數模板中,當無法得知返回值類型時,一般不可以直接使用關鍵字decltype來得到返回值類型,因為此時往往decltype後面表達式中的變量還不在作用域內,此時,需要使用後置返回類型

    1 template<class T1, class T2>
    2 auto gt(T1 x, T2 y) -> decltype(x+y) // 此時x,y已在作用域內
    3 {
    4     return x + y;
    5 }

      auto表示是一個佔位符,表示後置返回類型提供的類型。

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

    【其他文章推薦】

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

    ※別再煩惱如何寫文案,掌握八大原則!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

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

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 一篇文章搞懂filebeat(ELK)

    一篇文章搞懂filebeat(ELK)

    本文使用的filebeat是7.7.0的版本
    本文從如下幾個方面說明:

    • filebeat是什麼,可以用來幹嘛
    • filebeat的原理是怎樣的,怎麼構成的
    • filebeat應該怎麼玩

    一、filebeat是什麼

    1.1、filebeat和beats的關係

      首先filebeat是Beats中的一員。
      Beats在是一個輕量級日誌採集器,其實Beats家族有6個成員,早期的ELK架構中使用Logstash收集、解析日誌,但是Logstash對內存、cpu、io等資源消耗比較高。相比Logstash,Beats所佔系統的CPU和內存幾乎可以忽略不計。
    目前Beats包含六種工具:

    • Packetbeat:網絡數據(收集網絡流量數據)
    • Metricbeat:指標(收集系統、進程和文件系統級別的CPU和內存使用情況等數據)
    • Filebeat:日誌文件(收集文件數據)
    • Winlogbeat:windows事件日誌(收集Windows事件日誌數據)
    • Auditbeat:審計數據(收集審計日誌)
    • Heartbeat:運行時間監控(收集系統運行時的數據)

    1.2、filebeat是什麼

      Filebeat是用於轉發和集中日誌數據的輕量級傳送工具。Filebeat監視您指定的日誌文件或位置,收集日誌事件,並將它們轉發到Elasticsearch或 Logstash進行索引。

      Filebeat的工作方式如下:啟動Filebeat時,它將啟動一個或多個輸入,這些輸入將在為日誌數據指定的位置中查找。對於Filebeat所找到的每個日誌,Filebeat都會啟動收集器。每個收集器都讀取單個日誌以獲取新內容,並將新日誌數據發送到libbeat,libbeat將聚集事件,並將聚集的數據發送到為Filebeat配置的輸出。

           工作的流程圖如下:

     

    1.3、filebeat和logstash的關係

      因為logstash是jvm跑的,資源消耗比較大,所以後來作者又用golang寫了一個功能較少但是資源消耗也小的輕量級的logstash-forwarder。不過作者只是一個人,加入http://elastic.co公司以後,因為es公司本身還收購了另一個開源項目packetbeat,而這個項目專門就是用golang的,有整個團隊,所以es公司乾脆把logstash-forwarder的開發工作也合併到同一個golang團隊來搞,於是新的項目就叫filebeat了。

     

    二、filebeat原理是什麼

    2.1、filebeat的構成

      filebeat結構:由兩個組件構成,分別是inputs(輸入)和harvesters(收集器),這些組件一起工作來跟蹤文件並將事件數據發送到您指定的輸出,harvester負責讀取單個文件的內容。harvester逐行讀取每個文件,並將內容發送到輸出。為每個文件啟動一個harvester。harvester負責打開和關閉文件,這意味着文件描述符在harvester運行時保持打開狀態。如果在收集文件時刪除或重命名文件,Filebeat將繼續讀取該文件。這樣做的副作用是,磁盤上的空間一直保留到harvester關閉。默認情況下,Filebeat保持文件打開,直到達到close_inactive

    關閉harvester可以會產生的結果:

    • 文件處理程序關閉,如果harvester仍在讀取文件時被刪除,則釋放底層資源。
    • 只有在scan_frequency結束之後,才會再次啟動文件的收集。
    • 如果該文件在harvester關閉時被移動或刪除,該文件的收集將不會繼續

      一個input負責管理harvesters和尋找所有來源讀取。如果input類型是log,則input將查找驅動器上與定義的路徑匹配的所有文件,併為每個文件啟動一個harvester。每個input在它自己的Go進程中運行,Filebeat當前支持多種輸入類型。每個輸入類型可以定義多次。日誌輸入檢查每個文件,以查看是否需要啟動harvester、是否已經在運行harvester或是否可以忽略該文件

    2.2、filebeat如何保存文件的狀態

      Filebeat保留每個文件的狀態,並經常將狀態刷新到磁盤中的註冊表文件中。該狀態用於記住harvester讀取的最後一個偏移量,並確保發送所有日誌行。如果無法訪問輸出(如Elasticsearch或Logstash),Filebeat將跟蹤最後發送的行,並在輸出再次可用時繼續讀取文件。當Filebeat運行時,每個輸入的狀態信息也保存在內存中。當Filebeat重新啟動時,來自註冊表文件的數據用於重建狀態,Filebeat在最後一個已知位置繼續每個harvester。對於每個輸入,Filebeat都會保留它找到的每個文件的狀態。由於文件可以重命名或移動,文件名和路徑不足以標識文件。對於每個文件,Filebeat存儲唯一的標識符,以檢測文件是否以前被捕獲。

    2.3、filebeat何如保證至少一次數據消費

      Filebeat保證事件將至少傳遞到配置的輸出一次,並且不會丟失數據。是因為它將每個事件的傳遞狀態存儲在註冊表文件中。在已定義的輸出被阻止且未確認所有事件的情況下,Filebeat將繼續嘗試發送事件,直到輸出確認已接收到事件為止。如果Filebeat在發送事件的過程中關閉,它不會等待輸出確認所有事件后再關閉。當Filebeat重新啟動時,將再次將Filebeat關閉前未確認的所有事件發送到輸出。這樣可以確保每個事件至少發送一次,但最終可能會有重複的事件發送到輸出。通過設置shutdown_timeout選項,可以將Filebeat配置為在關機前等待特定時間

    三、filebeat怎麼玩

    3.1、壓縮包方式安裝

    本文採用壓縮包的方式安裝,linux版本,filebeat-7.7.0-linux-x86_64.tar.gz

    curl-L-Ohttps://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.7.0-linux-x86_64.tar.gz
    tar -xzvf filebeat-7.7.0-linux-x86_64.tar.gz

    配置示例文件:filebeat.reference.yml(包含所有未過時的配置項)
    配置文件:filebeat.yml

    3.2、基本命令

    詳情見官網:https://www.elastic.co/guide/en/beats/filebeat/current/command-line-options.html

    export   #導出
    run      #執行(默認執行)
    test     #測試配置
    keystore #秘鑰存儲
    modules  #模塊配置管理
    setup    #設置初始環境

    例如:./filebeat test config  #用來測試配置文件是否正確

    3.3、輸入輸出

    支持的輸入組件:

    Multilinemessages,Azureeventhub,CloudFoundry,Container,Docker,GooglePub/Sub,HTTPJSON,Kafka,Log,MQTT,NetFlow,Office365ManagementActivityAPI,Redis,s3,Stdin,Syslog,TCP,UDP(最常用的額就是log)

    支持的輸出組件:

    Elasticsearch,Logstash,Kafka,Redis,File,Console,ElasticCloud,Changetheoutputcodec(最常用的就是Elasticsearch,Logstash)

    3.4、keystore的使用

    keystore主要是防止敏感信息被泄露,比如密碼等,像ES的密碼,這裏可以生成一個key為ES_PWD,值為es的password的一個對應關係,在使用es的密碼的時候就可以使用${ES_PWD}使用

    創建一個存儲密碼的keystore:filebeat keystore create
    然後往其中添加鍵值對,例如:filebeatk eystore add ES_PWD
    使用覆蓋原來鍵的值:filebeat key store add ES_PWD–force
    刪除鍵值對:filebeat key store remove ES_PWD
    查看已有的鍵值對:filebeat key store list

    例如:後期就可以通過${ES_PWD}使用其值,例如:

    output.elasticsearch.password:”${ES_PWD}”

    3.5、filebeat.yml配置(log輸入類型為例)

    詳情見官網:https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-log.html

    type: log #input類型為log
    enable: true #表示是該log類型配置生效
    paths:     #指定要監控的日誌,目前按照Go語言的glob函數處理。沒有對配置目錄做遞歸處理,比如配置的如果是:
    - /var/log/* /*.log  #則只會去/var/log目錄的所有子目錄中尋找以".log"結尾的文件,而不會尋找/var/log目錄下以".log"結尾的文件。
    recursive_glob.enabled: #啟用全局遞歸模式,例如/foo/**包括/foo, /foo/*, /foo/*/* encoding:#指定被監控的文件的編碼類型,使用plain和utf-8都是可以處理中文日誌的
    exclude_lines: ['^DBG'] #不包含匹配正則的行
    include_lines: ['^ERR', '^WARN']  #包含匹配正則的行
    harvester_buffer_size: 16384 #每個harvester在獲取文件時使用的緩衝區的字節大小
    max_bytes: 10485760 #單個日誌消息可以擁有的最大字節數。max_bytes之後的所有字節都被丟棄而不發送。默認值為10MB (10485760)
    exclude_files: ['\.gz$']  #用於匹配希望Filebeat忽略的文件的正則表達式列表
    ingore_older: 0 #默認為0,表示禁用,可以配置2h,2m等,注意ignore_older必須大於close_inactive的值.表示忽略超過設置值未更新的
    文件或者文件從來沒有被harvester收集
    close_* #close_ *配置選項用於在特定標準或時間之後關閉harvester。 關閉harvester意味着關閉文件處理程序。 如果在harvester關閉
    後文件被更新,則在scan_frequency過後,文件將被重新拾取。 但是,如果在harvester關閉時移動或刪除文件,Filebeat將無法再次接收文件
    ,並且harvester未讀取的任何數據都將丟失。
    close_inactive  #啟動選項時,如果在制定時間沒有被讀取,將關閉文件句柄
    讀取的最後一條日誌定義為下一次讀取的起始點,而不是基於文件的修改時間
    如果關閉的文件發生變化,一個新的harverster將在scan_frequency運行后被啟動
    建議至少設置一個大於讀取日誌頻率的值,配置多個prospector來實現針對不同更新速度的日誌文件
    使用內部時間戳機制,來反映記錄日誌的讀取,每次讀取到最後一行日誌時開始倒計時使用2h 5m 來表示
    close_rename #當選項啟動,如果文件被重命名和移動,filebeat關閉文件的處理讀取
    close_removed #當選項啟動,文件被刪除時,filebeat關閉文件的處理讀取這個選項啟動后,必須啟動clean_removed
    close_eof #適合只寫一次日誌的文件,然後filebeat關閉文件的處理讀取
    close_timeout #當選項啟動時,filebeat會給每個harvester設置預定義時間,不管這個文件是否被讀取,達到設定時間后,將被關閉
    close_timeout 不能等於ignore_older,會導致文件更新時,不會被讀取如果output一直沒有輸出日誌事件,這個timeout是不會被啟動的,
    至少要要有一個事件發送,然後haverter將被關閉
    設置0 表示不啟動
    clean_inactived #從註冊表文件中刪除先前收穫的文件的狀態
    設置必須大於ignore_older+scan_frequency,以確保在文件仍在收集時沒有刪除任何狀態
    配置選項有助於減小註冊表文件的大小,特別是如果每天都生成大量的新文件
    此配置選項也可用於防止在Linux上重用inode的Filebeat問題
    clean_removed #啟動選項后,如果文件在磁盤上找不到,將從註冊表中清除filebeat
    如果關閉close removed 必須關閉clean removed
    scan_frequency #prospector檢查指定用於收穫的路徑中的新文件的頻率,默認10s
    tail_files:#如果設置為true,Filebeat從文件尾開始監控文件新增內容,把新增的每一行文件作為一個事件依次發送,
    而不是從文件開始處重新發送所有內容。
    symlinks:#符號鏈接選項允許Filebeat除常規文件外,可以收集符號鏈接。收集符號鏈接時,即使報告了符號鏈接的路徑,
    Filebeat也會打開並讀取原始文件。
    backoff: #backoff選項指定Filebeat如何积極地抓取新文件進行更新。默認1s,backoff選項定義Filebeat在達到EOF之後
    再次檢查文件之間等待的時間。
    max_backoff: #在達到EOF之後再次檢查文件之前Filebeat等待的最長時間
    backoff_factor: #指定backoff嘗試等待時間幾次,默認是2
    harvester_limit:#harvester_limit選項限制一個prospector并行啟動的harvester數量,直接影響文件打開數
    
    tags #列表中添加標籤,用過過濾,例如:tags: ["json"]
    fields #可選字段,選擇額外的字段進行輸出可以是標量值,元組,字典等嵌套類型
    默認在sub-dictionary位置
    filebeat.inputs:
    fields:
    app_id: query_engine_12
    fields_under_root #如果值為ture,那麼fields存儲在輸出文檔的頂級位置
    
    multiline.pattern #必須匹配的regexp模式
    multiline.negate #定義上面的模式匹配條件的動作是 否定的,默認是false
    假如模式匹配條件'^b',默認是false模式,表示講按照模式匹配進行匹配 將不是以b開頭的日誌行進行合併
    如果是true,表示將不以b開頭的日誌行進行合併
    multiline.match # 指定Filebeat如何將匹配行組合成事件,在之前或者之後,取決於上面所指定的negate
    multiline.max_lines #可以組合成一個事件的最大行數,超過將丟棄,默認500
    multiline.timeout #定義超時時間,如果開始一個新的事件在超時時間內沒有發現匹配,也將發送日誌,默認是5s
    max_procs #設置可以同時執行的最大CPU數。默認值為系統中可用的邏輯CPU的數量。
    name #為該filebeat指定名字,默認為主機的hostname
     

    3.6、實例一:logstash作為輸出

    filebeat.yml配置

    #=========================== Filebeat inputs =============================
    
    filebeat.inputs:
    
    # Each - is an input. Most options can be set at the input level, so
    # you can use different inputs for various configurations.
    # Below are the input specific configurations.
    
    - type: log
    
      # Change to true to enable this input configuration.
      enabled: true
    
      # Paths that should be crawled and fetched. Glob based paths.
      paths:  #配置多個日誌路徑
        - /var/logs/es_aaa_index_search_slowlog.log
        - /var/logs/es_bbb_index_search_slowlog.log
        - /var/logs/es_ccc_index_search_slowlog.log
        - /var/logs/es_ddd_index_search_slowlog.log
        #- c:\programdata\elasticsearch\logs\*
    
      # Exclude lines. A list of regular expressions to match. It drops the lines that are
      # matching any regular expression from the list.
      #exclude_lines: ['^DBG']
    
      # Include lines. A list of regular expressions to match. It exports the lines that are
      # matching any regular expression from the list.
      #include_lines: ['^ERR', '^WARN']
    
      # Exclude files. A list of regular expressions to match. Filebeat drops the files that
      # are matching any regular expression from the list. By default, no files are dropped.
      #exclude_files: ['.gz$']
    
      # Optional additional fields. These fields can be freely picked
      # to add additional information to the crawled log files for filtering
      #fields:
      #  level: debug
      #  review: 1
    
      ### Multiline options
    
      # Multiline can be used for log messages spanning multiple lines. This is common
      # for Java Stack Traces or C-Line Continuation
    
      # The regexp Pattern that has to be matched. The example pattern matches all lines starting with [
      #multiline.pattern: ^\[
    
      # Defines if the pattern set under pattern should be negated or not. Default is false.
      #multiline.negate: false
    
      # Match can be set to "after" or "before". It is used to define if lines should be append to a pattern
      # that was (not) matched before or after or as long as a pattern is not matched based on negate.
      # Note: After is the equivalent to previous and before is the equivalent to to next in Logstash
      #multiline.match: after
    
    
    #================================ Outputs =====================================
    
    #----------------------------- Logstash output --------------------------------
    output.logstash:
      # The Logstash hosts #配多個logstash使用負載均衡機制
      hosts: ["192.168.110.130:5044","192.168.110.131:5044","192.168.110.132:5044","192.168.110.133:5044"]  
      loadbalance: true  #使用了負載均衡
    
      # Optional SSL. By default is off.
      # List of root certificates for HTTPS server verifications
      #ssl.certificate_authorities: ["/etc/pki/root/ca.pem"]
    
      # Certificate for SSL client authentication
      #ssl.certificate: "/etc/pki/client/cert.pem"
    
      # Client Certificate Key
      #ssl.key: "/etc/pki/client/cert.key"

    ./filebeat -e   #啟動filebeat

    logstash的配置

    input {
      beats {
        port => 5044   
      }
    }
    
    output {
      elasticsearch {
        hosts => ["http://192.168.110.130:9200"] #這裏可以配置多個
        index => "query-%{yyyyMMdd}" 
      }
    }

     

    3.7、實例二:elasticsearch作為輸出

    filebeat.yml的配置:

    ###################### Filebeat Configuration Example #########################
    
    # This file is an example configuration file highlighting only the most common
    # options. The filebeat.reference.yml file from the same directory contains all the
    # supported options with more comments. You can use it as a reference.
    #
    # You can find the full configuration reference here:
    # https://www.elastic.co/guide/en/beats/filebeat/index.html
    
    # For more available modules and options, please see the filebeat.reference.yml sample
    # configuration file.
    
    #=========================== Filebeat inputs =============================
    
    filebeat.inputs:
    
    # Each - is an input. Most options can be set at the input level, so
    # you can use different inputs for various configurations.
    # Below are the input specific configurations.
    
    - type: log
    
      # Change to true to enable this input configuration.
      enabled: true
    
      # Paths that should be crawled and fetched. Glob based paths.
      paths:
        - /var/logs/es_aaa_index_search_slowlog.log
        - /var/logs/es_bbb_index_search_slowlog.log
        - /var/logs/es_ccc_index_search_slowlog.log
        - /var/logs/es_dddd_index_search_slowlog.log
        #- c:\programdata\elasticsearch\logs\*
    
      # Exclude lines. A list of regular expressions to match. It drops the lines that are
      # matching any regular expression from the list.
      #exclude_lines: ['^DBG']
    
      # Include lines. A list of regular expressions to match. It exports the lines that are
      # matching any regular expression from the list.
      #include_lines: ['^ERR', '^WARN']
    
      # Exclude files. A list of regular expressions to match. Filebeat drops the files that
      # are matching any regular expression from the list. By default, no files are dropped.
      #exclude_files: ['.gz$']
    
      # Optional additional fields. These fields can be freely picked
      # to add additional information to the crawled log files for filtering
      #fields:
      #  level: debug
      #  review: 1
    
      ### Multiline options
    
      # Multiline can be used for log messages spanning multiple lines. This is common
      # for Java Stack Traces or C-Line Continuation
    
      # The regexp Pattern that has to be matched. The example pattern matches all lines starting with [
      #multiline.pattern: ^\[
    
      # Defines if the pattern set under pattern should be negated or not. Default is false.
      #multiline.negate: false
    
      # Match can be set to "after" or "before". It is used to define if lines should be append to a pattern
      # that was (not) matched before or after or as long as a pattern is not matched based on negate.
      # Note: After is the equivalent to previous and before is the equivalent to to next in Logstash
      #multiline.match: after
    
    
    #============================= Filebeat modules ===============================
    
    filebeat.config.modules:
      # Glob pattern for configuration loading
      path: ${path.config}/modules.d/*.yml
    
      # Set to true to enable config reloading
      reload.enabled: false
    
      # Period on which files under path should be checked for changes
      #reload.period: 10s
    
    #==================== Elasticsearch template setting ==========================
    
    
    #================================ General =====================================
    
    # The name of the shipper that publishes the network data. It can be used to group
    # all the transactions sent by a single shipper in the web interface.
    name: filebeat222
    
    # The tags of the shipper are included in their own field with each
    # transaction published.
    #tags: ["service-X", "web-tier"]
    
    # Optional fields that you can specify to add additional information to the
    # output.
    #fields:
    #  env: staging
    
    #cloud.auth:
    
    #================================ Outputs =====================================
    
    
    #-------------------------- Elasticsearch output ------------------------------
    output.elasticsearch:
      # Array of hosts to connect to.
      hosts: ["192.168.110.130:9200","92.168.110.131:9200"]
    
      # Protocol - either `http` (default) or `https`.
      #protocol: "https"
    
      # Authentication credentials - either API key or username/password.
      #api_key: "id:api_key"
      username: "elastic"
      password: "${ES_PWD}"   #通過keystore設置密碼

    ./filebeat -e   #啟動filebeat

    查看elasticsearch集群,有一個默認的索引名字filebeat-%{[beat.version]}-%{+yyyy.MM.dd}

     

     

     

    3.8、filebeat模塊

    官網:https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-modules.html

    這裏我使用elasticsearch模式來解析es的慢日誌查詢,操作步驟如下,其他的模塊操作也一樣:

    前提: 安裝好Elasticsearch和kibana兩個軟件,然後使用filebeat

    具體的操作官網有:https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-modules-quickstart.html

    第一步,配置filebeat.yml文件

    #============================== Kibana =====================================
    
    # Starting with Beats version 6.0.0, the dashboards are loaded via the Kibana API.
    # This requires a Kibana endpoint configuration.
    setup.kibana:
    
      # Kibana Host
      # Scheme and port can be left out and will be set to the default (http and 5601)
      # In case you specify and additional path, the scheme is required: http://localhost:5601/path
      # IPv6 addresses should always be defined as: https://[2001:db8::1]:5601
      host: "192.168.110.130:5601"  #指定kibana
      username: "elastic"   #用戶
      password: "${ES_PWD}"  #密碼,這裏使用了keystore,防止明文密碼
    
      # Kibana Space ID
      # ID of the Kibana Space into which the dashboards should be loaded. By default,
      # the Default Space will be used.
      #space.id:
    
    #================================ Outputs =====================================
    
    # Configure what output to use when sending the data collected by the beat.
    
    #-------------------------- Elasticsearch output ------------------------------
    output.elasticsearch:
      # Array of hosts to connect to.
      hosts: ["192.168.110.130:9200","192.168.110.131:9200"]
    
      # Protocol - either `http` (default) or `https`.
      #protocol: "https"
    
      # Authentication credentials - either API key or username/password.
      #api_key: "id:api_key"
      username: "elastic"  #es的用戶
      password: "${ES_PWD}" # es的密碼
      #這裏不能指定index,因為我沒有配置模板,會自動生成一個名為filebeat-%{[beat.version]}-%{+yyyy.MM.dd}的索引

    第二步:配置elasticsearch的慢日誌路徑

    cd filebeat-7.7.0-linux-x86_64/modules.d
    

    vim  elasticsearch.yml

     

     

     

    第三步:生效es模塊

    ./filebeat modules elasticsearch

    查看生效的模塊

    ./filebeat modules list

     

     

     

    第四步:初始化環境

    ./filebeat setup -e

     

     

     

     

     第五步:啟動filebeat

    ./filebeat -e

    查看elasticsearch集群,如下圖所示,把慢日誌查詢的日誌都自動解析出來了:

     

     到這裏,elasticsearch這個module就實驗成功了

     

    參考

    官網:https://www.elastic.co/guide/en/beats/filebeat/current/index.html

     

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

    【其他文章推薦】

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※超省錢租車方案

    ※教你寫出一流的銷售文案?

    網頁設計最專業,超強功能平台可客製化

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • MySQL 性能優化之慢查詢

    MySQL 性能優化之慢查詢

    性能優化的思路

    1. 首先需要使用慢查詢功能,去獲取所有查詢時間比較長的SQL語句
    2. 其次使用explain命令去查詢由問題的SQL的執行計劃(腦補鏈接:點我直達1,點我直達2)
    3. 最後可以使用show profile[s] 查看由問題的SQL的性能使用情況
    4. 優化SQL語句

    介紹

      數據庫查詢快慢是影響項目性能的一大因素,對於數據庫,我們除了要優化SQL,更重要的是得先找到需要優化的SQL語句

      MySQL數據庫有一個“慢查詢日誌”功能,用來記錄查詢時間超過某個設定值的SQL,這將極大程度幫助我們快速定位到問題所在,以便對症下藥

    至於查詢時間的多少才算慢,每個項目、業務都有不同的要求。
        比如傳統企業的軟件允許查詢時間高於某個值,但是把這個標準方在互聯網項目或者訪問量大的網站上,估計就是一個Bug,甚至可能升級為一個功能缺陷。

      MySQL的慢查詢日誌功能,默認是關閉的,需要手動開啟

    開啟慢查詢功能

    查看是否開啟慢查詢功能

     

     

    參數說明:

    • slow_query_log:是否開啟慢查詢,on為開啟,off為關閉;
    • log-slow-queries:舊版(5.6以下版本)MySQL數據庫慢查詢存儲路徑,可以不設置該參數,系統則會給一個缺省的文件:host_name-slow.log
    • long_query_time:慢查詢閥值,當查詢時間多於設置的閥值時,記錄日誌,單位為秒。

    臨時開啟滿查詢功能

      在MySQL執行SQL語句設置,但是如果重啟MySQL的話會失效。

    set global slow_query_log=on;
    set global long_query_time=1;

    永久性開啟慢查詢

      修改:/etc/my.cnf,添加以下內容,然後重啟MySQL服務

    [mysqld]
    lower_case_table_names=1
    slow_query_log=ON
    slow_query_log_file=/usr/local/mysql/data/chenyanbindeMacBook-Pro-slow.log
    long_query_time=1

     

    查看滿查詢啟動狀態

    演示慢查詢

      為了演示方便,我們讓sql睡眠3秒!

    格式說明:

    • 第一行,SQL查詢執行的具體時間
    • 第二行,執行SQL查詢的連接信息,用戶和連接IP
    • 第三行,記錄了一些我們比較有用的信息,
      • Query_timme,這條SQL執行的時間,越長則越慢
      • Lock_time,在MySQL服務器階段(不是在存儲引擎階段)等待表鎖時間
      • Rows_sent,查詢返回的行數
      • Rows_examined,查詢檢查的行數,越長就越浪費時間
    • 第四行,設置時間戳,沒有實際意義,只是和第一行對應執行時間。
    • 第五行,執行的SQL語句記錄信息

    分析滿查詢日誌

    MySQL自帶的mysqldumpslow

     

     

     

    參數說明:

    • -s, 是表示按照何種方式排序,c、t、l、r分別是按照記錄次數、時間、查詢時間、返回的記錄數來排序,ac、at、al、ar,表示相應的倒敘;
    • -t, 是top n的意思,即為返回前面多少條的數據;
    • -g, 後邊可以寫一個正則匹配模式,大小寫不敏感的;

    MySQL性能fenix語句show profile(重要

    介紹

    • Query Profiler是MySQL自帶的一種query診斷分析工具,通過它可以分析出一條SQL語句性能瓶頸在什麼地方。
    • 通常使用explain,以及slow query log都無法做到精確分析,但是Query profiler卻可以定位出一條SQL執行的各種資源消耗情況,比如CPU、IO等,以及該SQL執行所耗費的時間等。不過該工具只有在MySQL5.0.37以上版本中才有實現
    • 默認的情況下,MySQL的該功能沒有打開,需要自己手動打開

    語句使用

    • show profileshow profiles語句可以展示當前會話(退出session后,profiling重置為0)中執行語句的資源使用情況。
    • show profiles:以列表形式显示最近發送到服務器上執行的語句的資源使用情況,显示的記錄數由變量:profiling_history_size控制,默認15條
    • show profile:只是最近一條語句執行的消息資源佔用信息,默認實現Status和Duration兩列

    開啟Profile功能

    • Profile功能由MySQL會話變量:profiling控制,默認是OFF關閉狀態。
    • 查看是否開啟了Profile功能
    select @@profiling;
    
    show variables like '%profil%';

     

    打開profiling功能

    set profiling=1;

     

    show profile用法

    SHOW PROFILE [type [, type] …… ] [FOR QUERY n] [LIMIT row_count [OFFSET offset]]
    
    type: { ALL | BLOCK IO | CONTEXT SWITCHES | CPU | IPC | MEMORY | PAGE FAULTS | SOURCE | SWAPS }

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

    【其他文章推薦】

    ※教你寫出一流的銷售文案?

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

    ※回頭車貨運收費標準

    ※別再煩惱如何寫文案,掌握八大原則!

    ※超省錢租車方案

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 【Spring】BeanDefinition&PostProcessor不了解一下嗎?

    水稻:這两天看了BeanDefinition和BeanFactoryPostProcessor還有BeanPostProcessor的源碼。要不要了解一下

    菜瓜:six six six,大佬請講

    水稻:上次我們說SpringIOC容器是一個典型的工廠模式

    • 假如我們把Spring比作一個生產模型的大工廠,那麼.class文件就是原材料。而BeanDefinition就是創建模型的模具。不管是傳統的XML還是後面的註解,Spring在啟動的時候都會創建一個掃描器去掃描指定目錄下的.class文件,並根據文件的註解,實現的接口以及成員變量將其封裝一個個的BeanDefinition。
      • 比較重要的屬性有id,class,構造函數封裝類,屬性封裝類,factoryMethod等
    • 在對象初始化之前Spring會完成BeanDefinition對象的解析並將其裝入List容器beanDefinitionNames中,然後開始遍歷該容器並根據BeanDefinition創建對象

    菜瓜:sodasinei,BeanDefinition我了解了。它是創建bean的模板,類似於java創建對象依賴的class一樣。那還有兩個很長的單詞是啥呢?

    水稻:忽略掉後面老長的後綴,我們看BeanFactory和Bean是不是很親切。PostProcessor被翻譯成後置處理器,暫且我們把它看成是處理器就行

    • BeanFactory是bean工廠,它可以獲取並修改BeanDefinition的屬性,進而影響後面創建的對象。
    • Bean就是Spring的對象,這些個處理器才是真正處理bean對象的各個環節的工序,包括屬性,註解,方法

    菜瓜:有了模糊的概念,不明覺厲

    水稻:來,看demo

    package com.vip.qc.postprocessor;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.MutablePropertyValues;
    import org.springframework.beans.factory.config.BeanDefinition;
    import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.stereotype.Component;
    
    /**
     * 獲取初始化好的BeanFactory,此時還未進行bean的實例化
     *
     * @author QuCheng on 2020/6/14.
     */
    @Component
    public class BeanFactoryPostProcessorT implements BeanFactoryPostProcessor {
    
        public static final String BEAN_NAME = "processorT";
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            BeanDefinition initializingBeanT = beanFactory.getBeanDefinition(BEAN_NAME);
            MutablePropertyValues propertyValues = initializingBeanT.getPropertyValues();
            String pName = "a";
            System.out.println("BeanFactoryPostProcessor a " + propertyValues.getPropertyValue(pName) + " -> 1");
            propertyValues.addPropertyValue(pName, "1");
        }
    }
    
    
    package com.vip.qc.postprocessor;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    import org.springframework.stereotype.Component;
    
    /**
     * @author QuCheng on 2020/6/14.
     */
    @Component
    public class BeanPostProcessorT implements BeanPostProcessor {
    
        public static final String beanNameT = "processorT";
    
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            if (beanNameT.equals(beanName)) {
                ProcessorT processorT = ((ProcessorT) bean);
                System.out.println("BeanPostProcessor BeforeInitialization  a:" + processorT.getA() + "-> 3");
                processorT.setA("3");
            }
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if (beanNameT.equals(beanName)){
                ProcessorT processorT = ((ProcessorT) bean);
                System.out.println("BeanPostProcessor AfterInitialization  a:" + processorT.getA() + "-> 4");
                processorT.setA("4");
            }
            return bean;
        }
    
    }
    
    
    package com.vip.qc.postprocessor;
    
    import org.springframework.stereotype.Component;
    
    /**
     * @author QuCheng on 2020/6/14.
     */
    @Component
    public class ProcessorT {
    
        public ProcessorT() {
            System.out.println("ProcessorT 無參構造 a:" + a + "-> 2" );
            a = "2";
        }
    
        private String a;
    
        public String getA() {
            return a;
        }
    
        public void setA(String a) {
            this.a = a;
        }
    
        @Override
        public String toString() {
            return "ProcessorT{" +
                    "a='" + a + '\'' +
                    '}';
        }
    }
    
    // 測試類
    @Test
    public void test() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.vip.qc.postprocessor");
        ProcessorT processorT = (ProcessorT) context.getBean("processorT");
        System.out.println(processorT);
    }
    
    // 結果
    BeanFactoryPostProcessor a null -> 1
    ProcessorT 無參構造 a:null-> 2
    BeanPostProcessor BeforeInitialization a:1-> 3
    BeanPostProcessor AfterInitialization a:3-> 4
    ProcessorT{a='4'}
    • BeanFactoryPostProcessor在對象還未初始化前可以拿到對象的BeanDefinition對其設置屬性值
    • 過程中我們分別對屬性a設置了1,2,3,4的值。最後我們拿到的值為4

    菜瓜:好像看懂了。BeanFactoryPostProcessor可以拿到BeanFactory對象,獲取裏面所有的BeanDefinition並可對其進行干預。BeanPostProcessor其實是在bean已經被創建完成之後進行加工操作

    水稻:沒錯。這是我們自己進行干預的demo。限於篇幅有限,你可以去看一下Spring自己對於這兩個接口的實現源碼。比較重要的推薦下面幾個

    • ConfigurationClassPostProcessor 實現BeanFactoryPostProcessor子接口
      • 完成對@Configuration、@Component、@ComponentScan、@Bean、@Import、@ImportSource註解的搜集和解析
      • @Bean註解會被封裝成所在Bean的BeanDefinition中的factoryMethod屬性中,單獨進行實例化
    • CommonAnnotationBeanPostProcessor 實現 BeanPostProcessor
      • 完成@PostConstruct@PreDestroy@Resource註解的搜集和解析工作
      • @PostConstruct會在對象初始化且屬性渲染完成後進行
      • @Resource註解(參照下面)
    • AutowiredAnnotationBeanPostProcessor 實現 BeanPostProcessor
      • 完成@Autowired@Value註解的搜集和解析工作
      • 在對象初始化完成之後會先進行註解的搜集,然後進行屬性渲染調用populateBean方法,使用策略模式調用實現接口對註解進行解析,有@Autowired和@Value註解會調用getBean方法發起對依賴屬性的注入
    • AbstractAutoProxyCreator的入口類也是實現的BeanPostProcessor

    菜瓜:你放心,我不會看的。這麼複雜的東西,聽着都費勁

    水稻:不愧是你!有機會聊bean的生命周期的時候咱們還會說到這些東西。到時候再刷一遍

     

    總結:

    • BeanDefinition是spring容器創建對象的模板,定義了bean創建的細節
    • BeanFactoryPostProcessor可以拿到整個容器對象,當然也能修改BeanDefinition,所以能直接操作bean的創建
    • BeanPostProcessor執行的時候bean已經創建完成了,我們可以拿到想要的對象進行干預和設值等操作

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

    【其他文章推薦】

    ※超省錢租車方案

    ※別再煩惱如何寫文案,掌握八大原則!

    ※回頭車貨運收費標準

    ※教你寫出一流的銷售文案?

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

  • 用 Explain 命令分析 MySQL 的 SQL 執行

    用 Explain 命令分析 MySQL 的 SQL 執行

    在上一篇文章《MySQL常見加鎖場景分析》中,我們聊到行鎖是加在索引上的,但是複雜的 SQL 往往包含多個條件,涉及多個索引,找出 SQL 執行時使用了哪些索引對分析加鎖場景至關重要。

    比如下面這樣的 SQL:

    mysql> delete from t1 where id = 1 or val = 1
    

    其中 id 和 val 都是索引,那麼執行時使用到了哪些索引,加了哪些鎖呢?為此,我們需要使用 explain 來獲取 MySQL 執行這條 SQL 的執行計劃。

    什麼是執行計劃呢?簡單來說,就是 SQL 在數據庫中執行時的表現情況,通常用於 SQL 性能分析、優化和加鎖分析等場景,執行過程會在 MySQL 查詢過程中由解析器,預處理器和查詢優化器共同生成。

    MySQL 查詢過程

    如果能搞清楚 MySQL 是如何優化和執行查詢的,不僅對優化查詢一定會有幫助,還可以通過分析使用到的索引來判斷最終的加鎖場景。

    下圖是MySQL執行一個查詢的過程。實際上每一步都比想象中的複雜,尤其優化器,更複雜也更難理解。本文只給予簡單的介紹。

    MySQL查詢過程如下:

    • 客戶端發送一條查詢給服務器。
    • 服務器先檢查查詢緩存,如果命中了緩存,則立刻返回存儲在緩存中的結果。否則進入下一階段。
    • 服務器端進行SQL解析、預處理,再由優化器生成對應的執行計劃。
    • MySQL根據優化器生成的執行計劃,再調用存儲引擎的API來執行查詢。
    • 將結果返回給客戶端。

    執行計劃

    MySQL會解析查詢,並創建內部數據結構(解析樹),並對其進行各種優化,包括重寫查詢、決定表的讀取順序、選擇合適的索引等。

    用戶可通過關鍵字提示(hint)優化器,從而影響優化器的決策過程。也可以通過 explain 了解 數據庫是如何進行優化決策的,並提供一個參考基準,便於用戶重構查詢和數據庫表的 schema、修改數據庫配置等,使查詢盡可能高效。

    下面,我們依次介紹 explain 中相關輸出參數,並以實際例子解釋這些參數的含義。

    select_type

    查詢數據的操作類型,有如下

    • simple 簡單查詢,不包含子查詢或 union,如下圖所示,就是最簡單的查詢語句。
    • primary 是 SQL 中包含複雜的子查詢,此時最外層查詢標記為該值。

    • derived 是 SQL 中 from 子句中包含的子查詢被標記為該值,MySQL 會遞歸執行這些子查詢,把結果放在臨時表。下圖展示了上述兩種類型。

    • subquery 是 SQL 在 select 或者 where 里包含的子查詢,被標記為該值。
    • dependent subquery:子查詢中的第一個 select,取決於外側的查詢,一般是 in 中的子查詢。
    • union 是 SQL 在出現在 union 關鍵字之後的第二個 select ,被標記為該值;若 union 包含在 from 的子查詢中,外層select 被標記為 derived。

    • union result 從 union 表獲取結果的 select。下圖展示了 union 和 union result 的 SQL 案例。

    • dependent union 也是 union 關鍵字之後的第二個或者後邊的那個 select 語句,和 dependent subquery 一樣,取決於外面的查詢。

    type

    表的連接類型,其性能由高到低排列為 system,const,eq_ref,ref,range,index 和 all。

    • system 表示表只有一行記錄,相當於系統表。如下圖所示,因為 from 的子查詢派生的表只有一行數據,所以 primary 的表連接類型為 system。
    • const 通過索引一次就找到,只匹配一行數據,用於常數值比較PRIMARY KEY 或者 UNIQUE索引。
    • eq_ref 唯一性索引掃描,對於每個索引鍵,表中只有一條記錄與之匹配,常用於主鍵或唯一索引掃描。對於每個來自前邊的表的行組合,從該表中讀取一行。它是除了 const 類型外最好的連接類型。

      如下圖所示,對錶 t1 查詢的 type 是 ALL,表示全表掃描,然後 t1 中每一行數據都來跟 t2.id 這個主鍵索引進行對比,所以 t2 表的查詢就是 eq_ref。

    • ref 非唯一性索引掃描,返回匹配某個單獨值的所有行,和 eq_ref 的區別是索引是非唯一索引,具體案例如下所示。
    • range 只檢查給定範圍的行,使用一個索引來選擇行,當使用 =, between, >, <, 和 in 等操作符,並使用常數比較關鍵列時。如下圖所示,其中 id 為唯一索引,而 val 是非唯一索引。
    • index 與 ALL 類型類似,唯一區別就是只遍歷索引樹讀取索引值,比 ALL 讀取所有數據行要稍微快一些,因為索引文件通常比數據文件小。這裏涉及 MySQL 的索引覆蓋

    • ALL 全表掃描,通常情況下性能很差,應該避免。

    possible_keys,key 和 key_len

    possible_key 列指出 MySQL 可能使用哪個索引在該表中查找。如果該列為 NULL,則沒有使用相關索引。需要檢查 where 子句條件來創建合適的索引提高查詢效率。

    key 列显示 MySQL 實際決定使用的索引。如果沒有選擇索引,則值為 NULL。

    key_len 显示 MySQL 決定使用索引的長度。如果鍵為 NULL,則本列也為 NULL,使用的索引長度,在保證精確度的情況下,越短越好。因為越短,索引文件越小,需要的 I/O次數也越少。

    由上圖可以看出,對於 select * from t2 where id = 1 or val = 1這個語句,可以使用 PRIMARY 或者 idx_t2_val 索引,實際使用了 idx_t2_val 索引,索引的長度為5。

    這些其實是我們分析加鎖場景最為關心的字段,後續文章會具體講解如何根據這些字段和其他工具一起判斷複雜 SQL 到底加了哪些鎖。

    ref

    ref 列表示使用其他表的哪個列或者常數來從表中選擇行。如下圖所示,從 t2 讀取數據時,要判斷 t2.id = t1.id,所以 ref 就是 mysql.t1.id

    rows 和 filtered

    rows 列显示 MySQL 認為它執行查詢時必須檢查的行數。

    filtered 列表明了 SQL 語句執行后返回結果的行數占讀取行數的百分比,值越大越好。MySQL 會使用 Table Filter 來讀取出來的行數據進行過濾,理論上,讀取出來的行等於返回結果的行數時效率最高,過濾的比率越多,效率越低。

    如上圖所示,t1表中有三條數據,rows 為 3,表示所有行都要讀取出來。根據 val = 3 這個 table filter 過濾,只返回一行數據,所以 filtered 比例為33.33%,

    extra

    包含不適合在其他列中显示但十分重要的額外信息。常見的值如下

    • using index 表示 select 操作使用了覆蓋索引,避免了訪問表的數據行,效率不錯。

    • using where 子句用於限制哪一行。也就是讀取數據后使用了 Table Filter 進行過濾。

      如下圖所示,因為 id 和 val 都是有索引的,所以 select * 也是可以直接使用覆蓋索引讀取數據,所以 extra 中有 using index。而因為只使用 val 索引讀取了3行數據,還是通過 where 子句進行過濾,filtered為 55%,所以 extra 中使用了 using where。

    • using filesort MySQL 會對數據使用一個外部的索引排序,而不是按照表內的索引順序進行讀取,若出現該值,應該優化 SQL 語句。如下圖所示,其中 val 列沒有索引,所以無法使用索引順序排序讀取。
    • using temporary 使用臨時表保存中間結果,比如,MySQL 在對查詢結果排序時使用臨時表,常用於 order by 和 group by,如果出現該值,應該優化 SQL。根據我的經驗,group by 一個無索引列,或者ORDER BY 或 GROUP BY 的列不是來自JOIN語句序列的第一個表,就會產生臨時表。

    • using join buffer 使用連接緩存。如下圖所示,展示了連接緩存和臨時表。關於連接緩存的內容,大家可以自行查閱,後續有時間在寫文章解釋。

    • distinct 發現第一個匹配后,停止為當前的行組合搜索更多的行

    後記

    通過 explain 了解到 SQL 的執行計劃后,我們不僅可以了解 SQL 執行時使用的索引,判斷加鎖場景,還可以針對其他信息對 SQL 進行優化分析,比如將 type 類型從 index 優化到 ref 等。

    個人博客,歡迎來玩

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

    【其他文章推薦】

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

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

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

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

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

    ※教你寫出一流的銷售文案?